% title: Intro to ScalaCheck % subtitle: Indy Scala, April 6, 2015 % author: Ross A. Baker % author: Principal Cloud Engineer, CrowdStrike, @rossabaker % thankyou: Thanks everyone! % thankyou_details: And especially these people: % contact: hosts E-gineering % contact: meetup Brad Fritz of Fewer Hassles % favicon: http://www.stanford.edu/favicon.ico
title: How do we test? class: img-top-center build_lists: true
- We're all good developers here.
- We know it's not done until it's tested.
- But it's boring.
- And there's always pressure to deliver new features.
- And your tests miss corner cases anyway.
- Testing is hard. Let's go watch basketball.
title: The testing continuum class: img-top-center build_lists: true
- We are going to try to move right.
- Well, up to a point.
title: ¯\(°_o)/¯-based testing subtitle: A review build_lists: true
- I didn't have time to write this slide.
- Because I had a Sev 1 defect.
- You know, because I didn't test.
title: Example-based testing subtitle: A review build_lists: true
"sqrt(c) * sqrt(c) == c by example" in { sqrt(1.0) must be (1.0) sqrt(9.0) must be (3.0) sqrt(0.25) must be (0.5) }
title: Property testing subtitle: A preview build_lists: true
"sqrt(c) * sqrt(c) == c" in { forAll { (d: Double) => (sqrt(d) * sqrt(d)) must be (d) } }
title: Interactive theorem proving subtitle: A hand wave build_lists: true
theorem sqrt2_not_rational: "sqrt (real 2) ∉ ℚ" proof assume "sqrt (real 2) ∈ ℚ" then obtain m n :: nat where n_nonzero: "n ≠ 0" and sqrt_rat: "¦sqrt (real 2)¦ = real m / real n" and lowest_terms: "gcd m n = 1" .. from n_nonzero and sqrt_rat have "real m = ¦sqrt (real 2)¦ * real n" by simp then have "real (m²) = (sqrt (real 2))² * real (n²)" by (auto simp add: power2_eq_square) also have "(sqrt (real 2))² = real 2" by simp also have "... * real (m²) = real (2 * n²)" by simp finally have eq: "m² = 2 * n²" .. hence "2 dvd m²" .. with two_is_prime have dvd_m: "2 dvd m" by (rule prime_dvd_power_two) then obtain k where "m = 2 * k" .. with eq have "2 * n² = 2² * k²" by (auto simp add: power2_eq_square mult_ac) hence "n² = 2 * k²" by simp hence "2 dvd n²" .. with two_is_prime have "2 dvd n" by (rule prime_dvd_power_two) with dvd_m have "2 dvd gcd m n" by (rule gcd_greatest) with lowest_terms have "2 dvd 1" by simp thus False by arith qedhttp://en.wikipedia.org/wiki/Isabelle_%28proof_assistant%29
title: Comparing the approaches subtitle: Rigor of proof
- ¯\(°_o)/¯: proof by hope
- Examples: proof by example
- Properties: proof by lack of counterexample
- Theorems: proof by, like, math and stuff
title: Comparing the approaches subtitle: Time to market
- ¯\(°_o)/¯: fast
- Examples: moderate
- Properties: moderate
- Theorems: uh, we're still researching that
title: Comparing the approaches subtitle: Can I hire for this?
- ¯\(°_o)/¯: too easily
- Examples: easily
- Properties: maybe not, but you can learn it
- Theorems: uh, we're still researching that
title: Comparing the approaches subtitle: Can I do this in Scala?
- ¯\(°_o)/¯: of course!
- Examples: ScalaTest or Specs2
- Properties: ScalaCheck, optionally with ScalaTest or Specs2
- Theorems: Isabelle, I guess?
title: Property testing subtitle: A deeper look build_lists: true
"sqrt(c) * sqrt(c) == c" in { forAll { (d: Double) => (sqrt(d) * sqrt(d)) must be (d) } }
- Says what we mean
- ScalaCheck generates
d
... - ... a hundred times or more ...
- ... and finds your flawed assumptions.
title: Our first bug build_lists: true
[info] - sqrt(c) * sqrt(c) == c property *** FAILED *** [info] TestFailedException was thrown during property evaluation. [info] Message: NaN was not equal to -3.9898976436050957E-94 [info] Location: (SqrtSpec.scala:19) [info] Occurred when passed generated values ( [info] arg0 = -3.9898976436050957E-94 [info] )
- We forgot to do bounds checking.
- Our type system does not express the domain of the function. :(
- But our property can. :)
title: whenever build_lists: true
"sqrt(c) * sqrt(c) == c" in { forAll { (d: Double) => whenever (d >= 0) { (sqrt(d) * sqrt(d)) must be (d) } } }
- Still says what we mean
- ScalaCheck still generates
d
... - ... and discards
d
that don't fit.
title: Our second bug build_lists: true
[info] - sqrt(c) * sqrt(c) == c *** FAILED *** [info] TestFailedException was thrown during property evaluation. [info] Message: 2.2437640832940142E64 was not equal to 2.2437640832940145E64 [info] Location: (SqrtSpec.scala:20) [info] Occurred when passed generated values ( [info] arg0 = 2.2437640832940145E64 [info] )
- Meh. Close enough for government work.
- Our property overpromises.
- Let's fix it again.
title: tolerance build_lists: true
"sqrt(c) * sqrt(c) == c" in { def beWithinTolerance(d: Double) = { be >= d * 0.999 and be <= d * 1.001 } forAll { (d: Double) => whenever (d >= 0) { (sqrt(d) * sqrt(d)) must beWithinTolerance(d) } } }
title: victory build_lists: true
[info] SqrtSpec: [info] sqrt [info] - sqrt(c) * sqrt(c) == c by example [info] - sqrt(c) * sqrt(c) == c [info] ScalaTest [info] Run completed in 338 milliseconds. [info] Total number of tests run: 2 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [info] Passed: Total 2, Failed 0, Errors 0, Passed 2
title: Complex numbers
case class Complex(r: Double, i: Double) { def *(c: Complex) = Complex(r * c.r, i * c.i) def squared = this * this }
title: Generating complex types build_lists: true
val genComplex: Gen[Complex] = for { r <- arbitrary[Double] i <- arbitrary[Double] } yield Complex(r, i) implicit val arbComplex: Arbitrary[Complex] = Arbitrary(genComplex)
- Generate complex types (pun intended) from simple.
- ScalaCheck looks for an implicit
Arbitrary
instance.
title: Lots of arbitrary types built in build_lists: false
- AnyVal
- Array
- BigDecimal
- Bigint
- Boolean
- Byte
- Char
- Container
- Date
- Double
- Either
- Float
- Function{1,2,3,4,5}
- ImmutableMap
- Int
- Long
- MutableMap
- Number
- Option
- Short
- String
- Throwable
- Tuple{2,3,4,5,6,7,8,9}
- Unit
title: Testing our complex number build_lists: true
test("c * c == c.squared") in { forAll { (c: Complex) => (c * c) must be (c.squared) } }
- ScalaCheck really shines checking relations between methods.
title: Success
[info] ComplexSpec: [info] - c * c == c.squared
title: Lies, damned lies, and property testing
scala> Complex(0,1) * Complex(0,1) res1: org.indyscala.scalacheck.Complex = Complex(0.0,1.0)
- Well, that's not right at all.
$i * i = -1$
title: How can we fix this? subtitle: Add an example build_lists: true
[info] ComplexSpec: [info] complex [info] - c * c == c.squared [info] - i * i == -1 *** FAILED *** [info] Complex(0.0,1.0) was not equal to Complex(1.0,0.0) (ComplexSpec.scala:38)
- You don't have to leave example testing behind.
- Examples are a fine way to test your properties.
title: A corrected Complex
case class Complex(r: Double, i: Double) { def *(c: Complex) = Complex(r * c.r - i * c.i, r * c.i - i * c.r) def squared = this * this }
title: We found a boundary condition build_lists: true
[info] ComplexSpec: [info] - c * c == c.squared *** FAILED *** [info] TestFailedException was thrown during property evaluation. [info] Message: Complex(NaN,NaN) was not equal to Complex(NaN,NaN) [info] Location: (ComplexSpec.scala:26) [info] Occurred when passed generated values ( [info] arg0 = Complex(1.672687821964044E279,7.301512514377243E305) [info] ) [info] - i * i == -1
- We could fix that by putting a
whenever
on our values - Or we could switch our represenation to big decimals
- Either way, now we know how robust it is
title: Hey, doesn't our test look like the implementation? build_lists: true
def squared = this * this
(c * c) must be (c.squared)
- It's a trap: it contributed to our false positive
- It's useful: it's a regression suite if you optimize the impl
- Testing is still an art
title: I'm only dabbling in Scala. subtitle: Most major languages have a port!
- C
- C++
- Chicken Scheme
- Clojure
- Common Lisp
- D
- Elm
- Erlang
- F#
- Factor
- Haskell
- Io
- Java
- JavaScript
- Node.js
- Objective-C
- OCaml
- Perl
- Prolog
- Python
- R
- Ruby
- Scheme
- Smalltalk
- Standard ML
title: Choose Your Own Adventure subtitle: If time permits
- Exercise: Reimplement sqrt to return a Complex
- Dive deeper into generators
- Look at how cats tests with Discipline
- Go watch basketball
title: Some further reading