Skip to content

Instantly share code, notes, and snippets.

@jisantuc
Created January 16, 2020 02:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jisantuc/dbfb42308b62280301a89c33c54fbe68 to your computer and use it in GitHub Desktop.
Save jisantuc/dbfb42308b62280301a89c33c54fbe68 to your computer and use it in GitHub Desktop.
patat presentation on laws-based testing
title author patat
Laws-based Testing
James Santucci
wrap margins
true
left right
40
40

What's testing

  • verification that some expected behavior occurs under known conditions
  • where do expectations come from?

A function

add :: Int -> Int -> Int
add x y = x + y

Unit testing

  • expectations come from examples we care about
  • we really need to know that adding 3 and 4 yields 7
  • add 3 4 == 7

Property-based testing

  • 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

Property-based testing

  • 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

Laws-based testing

  • 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 (*)

Laws-based testing

  • 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)

Where do laws come from

  • 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)

How do we test laws

  • 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)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment