title | author | patat | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Laws-based Testing |
James Santucci |
|
- verification that some expected behavior occurs under known conditions
- where do expectations come from?
add :: Int -> Int -> Int
add x y = x + y
- expectations come from examples we care about
- we really need to know that adding 3 and 4 yields 7
add 3 4 == 7
- expectations come from "properties"
- suppose
add
is actually complicated, but we have some other function that calculates a result we know is good- that other function we'll call
sum
- expectations come from equivalence relations that we think ought to be true for any values that satisfy our function
test :: int -> int -> TestResult
test x y = add x y == sum x y
- but we've just kicked the can -- where do equivalence relations come from?
- sometimes we have expectations about a function like add
- sometimes we have expectations about all things that share a property
- for when we have expectations about all things that share a property
- back to add:
combine :: (int -> int -> int) -> int -> int -> int
combine f x y = f x y
add :: int -> int -> int
add = combine (+)
- but also!
multiply :: int -> int -> int
multiply = combine (*)
- Things that share this behavior are
semigroups
- Semigroups are things with a binary associative operator
- All things that form semigroups have the associativity property for their binary associative operator
(x `combine` y) `combine` z == x `combine` (y `combine` z)
- so that's a law, and we can have tests like:
associativityTest :: Semigroup a => a -> a -> a -> TestResult
associativityTest x y z = (x `combine` y) `combine` z == x `combine` (y `combine` z)
- for semigroups, math
- for json serialization / deserialization in circe, consensus of open source contributors doing their best to write good software that won't break in surprising ways and will protect people from bad impulses (like throwing errors in deserialization)
- in Scala, with the
Discipline
library and trait - e.g. in Raster Foundry
class ScopeSpec
extends Discipline {
...
checkAll("Scope.MonoidLaws", MonoidTests[Scope].monoid)
checkAll("Scope.CodecTests", CodecTests[Scope].codec)
}