Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to test proper{l,t}y

How to test proper{l,t}y

Writing unit tests is pretty much established practice and in addition to that, property testing has caught up on popularity. Most functional languages have one, sometimes even many implementations. But "property testing" has a lot of aspects: randomized or exhaustive, minimization and generalization of counter examples, custom generators and filters, to name a few. Very often, property tests don't exploit all the features of the framework.

In this talk, I'll give an overview of the state of the art of property testing in Scala and Haskell and show some common use cases, techniques and pitfalls.

Lars Hupel

May 02, 2016
Tweet

More Decks by Lars Hupel

Other Decks in Programming

Transcript

  1. Property testing ▶ simple idea: specify a property with unknowns,

    let the framework insert values ▶ basic features: ▶ automatic and custom generators ▶ filtering of inputs ▶ shrinking of counterexamples ▶ classification of inputs ▶ input and property labelling 3
  2. Framework ecosystem ▶ Haskell ▶ QuickCheck ▶ SmallCheck ▶ Scala

    ▶ ScalaCheck ▶ scalaprops ▶ Java ▶ Javaslang ▶ Rust ▶ Quickcheck ▶ Erlang ▶ QuickCheck ▶ triq ▶ Python ▶ pytest-quickcheck ▶ Hypothesis ▶ Clojure ▶ simple-check ▶ test.check ▶ your favourite language ... 4
  3. Generating functions def getItemByName(string: String): Future[Int] = ??? ▶ assume:

    this function makes an expensive database access bad for testing ▶ we want to generate a lookup function randomly ▶ ScalaCheck can generate functions 8
  4. Theory Let’s count! ▶ assume: there are n different Ts

    ▶ assume: there are m different Rs ▶ pop quiz: how many T=>Rs are there? 9
  5. Theory Let’s count! ▶ assume: there are n different Ts

    ▶ assume: there are m different Rs ▶ pop quiz: how many T=>Rs are there? answer: mn 9
  6. Strategies How can we generate functions? precompute a table impractical

    make up values on the spot side-effecting constant functions not good enough 10
  7. Generating functions: ScalaCheck ≤ 1.12 def arbFn[T, R](a: Arbitrary[R]): Arbitrary[T

    => R] = Arbitrary( for (r <- a.arbitrary) yield (t: T) => r ) Pick a single value, produce a constant function 11
  8. Generating functions: ScalaCheck ≥ 1.13 ▶ function outputs must only

    depend on its inputs. ▶ hack: use inputs to perturb random generator 12
  9. Generating functions: ScalaCheck ≥ 1.13 ▶ function outputs must only

    depend on its inputs. ▶ hack: use inputs to perturb random generator RNG Gen[R] Prop seed value: R 12
  10. Generating functions: ScalaCheck ≥ 1.13 ▶ function outputs must only

    depend on its inputs. ▶ hack: use inputs to perturb random generator RNG Gen[R] Prop value: R Cogen[T] seed perturbed seed 12
  11. Generating functions: ScalaCheck ≥ 1.13 ▶ function outputs must only

    depend on its inputs. ▶ hack: use inputs to perturb random generator trait Cogen[T] { def perturb(seed: Seed, t: T): Seed } 12
  12. Generating functions: ScalaCheck ≥ 1.13 ▶ function outputs must only

    depend on its inputs. ▶ hack: use inputs to perturb random generator trait Cogen[T] { def perturb(seed: Seed, t: T): Seed def value[A](seed: Seed, t: T, g: Gen[A]) = g.value(perturb(seed, t)) } 12
  13. Randomly generate all the things? Advantages ▶ likely to find

    input you didn’t think about ▶ can easily cover wide variety of input 14
  14. Randomly generate all the things? Advantages ▶ likely to find

    input you didn’t think about ▶ can easily cover wide variety of input Disadvantages ▶ inherently non-reproducible (random failures) ▶ often high discard ratio ▶ existential properties are difficult 14
  15. SmallCheck Haskell implementation ▶ supports quantifiers: ∃, ∃1 , ∀

    ▶ supports implication ▶ supports quantifiers nested in implications 16
  16. What if my properties are polymorphic? def reverse[A](xs: List[A]): List[A]

    ▶ How should we instantiate the types? ▶ Should we come up with fresh types? 19
  17. What if my properties are polymorphic? def reverse[A](xs: List[A]): List[A]

    ▶ How should we instantiate the types? ▶ Should we come up with fresh types? 19
  18. What if types were first-class? Existential types to the rescue!

    sealed trait Type { type T val name: String def forAll(prop: T => Prop): Prop } 20
  19. What if types were first-class? Existential types to the rescue!

    Prop.forAll { typ: SmallType => Prop.forAll { xs: List[typ.T] => reverse(xs) == xs } } 20
  20. What if types were first-class? Existential types to the rescue!

    Prop.forAll { typ: SmallType => Prop.forAll { xs: List[typ.T] => reverse(xs) == xs } } ! Test.reverse: Falsified after 157 passed tests. > ARG_0: Type[small.Two] > ARG_1: List(”B”, ”A”) 20
  21. Minimization on steroids (xs zip ys).length == xs.length Easy to

    find a small counterexample: val xs = 1 :: Nil val ys = Nil ... but: tells us nothing about the structure 23
  22. Minimization on steroids (xs zip ys).length == xs.length Mathematicians would

    write: val xs = 1 :: xs0 // for arbitrary xs0 val ys = Nil Tells us about classes of failures! 23
  23. Signature exploration ▶ writing properties by hand is tedious ▶

    Why not automate? ▶ Idea: give a signature, try to compose well-typed terms, run QuickCheck 24