Slide 1

Slide 1 text

Property-Based Testing with SwiftCheck 1

Slide 2

Slide 2 text

Tests are difficult to write 2

Slide 3

Slide 3 text

Tests are difficult to write 3

Slide 4

Slide 4 text

Test cases are difficult to write 4

Slide 5

Slide 5 text

How are test cases difficult to write? 5

Slide 6

Slide 6 text

How are test cases difficult to write? • We need to find all of the meaninful test cases 5

Slide 7

Slide 7 text

How are test cases difficult to write? • We need to find all of the meaninful test cases • We need to organize them in a meaningful way 5

Slide 8

Slide 8 text

How are test cases difficult to write? • We need to find all of the meaninful test cases • We need to organize them in a meaningful way • How many cases are enough? 5

Slide 9

Slide 9 text

How can we find test cases? 6

Slide 10

Slide 10 text

How can we find test cases? • Randomness 6

Slide 11

Slide 11 text

How can we find test cases? • Randomness • Reproducible 6

Slide 12

Slide 12 text

How can we find test cases? • Randomness • Reproducible • Simplify failing case (when possible) 6

Slide 13

Slide 13 text

7

Slide 14

Slide 14 text

Let's Get Testing public struct Rational { public let numerator : Int public let denominator : Int } 8

Slide 15

Slide 15 text

Arbitrary public protocol Arbitrary { /// The generator for this particular type. public static var arbitrary: SwiftCheck.Gen { get } } 9

Slide 16

Slide 16 text

Gen Gen represents a generator for random arbitrary values of a type. public struct Gen 10

Slide 17

Slide 17 text

Conforming to Arbitrary public static var arbitrary: SwiftCheck.Gen { return Gen.compose { comp in return Rational(comp.generate(), comp.generate()) } } 11

Slide 18

Slide 18 text

Conforming to Arbitrary return Rational(comp.generate(), comp.generate()) 12

Slide 19

Slide 19 text

Filtering Generated values Int.arbitrary.suchThat { $0 != 0 } // Denominator 13

Slide 20

Slide 20 text

Filtering Generated values extension Rational : Arbitrary { public static var arbitrary: SwiftCheck.Gen { return Gen.compose { comp in let denGen = Int.arbitrary.suchThat { $0 != 0 } return Rational( comp.generate(), comp.generate(using: denGen) } } 14

Slide 21

Slide 21 text

OK, What to test? 15

Slide 22

Slide 22 text

Properties! 16

Slide 23

Slide 23 text

Round Trip Properties property("round trip to string") <- forAll { (i: Int) in return Int(i.description)! == i } 17

Slide 24

Slide 24 text

Round Trip Properties // for all foo: [Int] foo.reverse().reverse() == foo 18

Slide 25

Slide 25 text

Commuting Diagram Properties • ----- | | | | | ! -----" 19

Slide 26

Slide 26 text

Commuting Diagram Properties property("base64 encoding commutes with Apple's ") <- forAll { (data: Data) in return data.base64Encoding == data.myBase64Encoding } 20

Slide 27

Slide 27 text

Properties func testAddition() { property("Addition is commutative") <- forAll { (i: Rational, j: Rational) in return (i + j) == (j + i) } } 21

Slide 28

Slide 28 text

Properties property("Addition is commutative 2") <- forAll { (i: Rational, j: Rational, k: Rational) in let caseOne = (i + j + k) let caseTwo = ((i + j) + k) let caseThree = (i + (j + k)) return (caseOne == caseTwo) && (caseTwo == caseThree) } 22

Slide 29

Slide 29 text

Replay 23

Slide 30

Slide 30 text

Replay let (seedl, seedr) = (1640744780, 1403884642) let replayArgs = CheckerArguments(replay: .some(StdGen(seedl, seedr), 3)) property("x / y", arguments: replayArgs) <- forAll { … 24

Slide 31

Slide 31 text

Mixed Testing func testEquality() { let oneOverTwo = Rational(1, 2)! let twoOverFour = Rational(2, 4)! let ichiUeNi = Rational(1, 2)! XCTAssertEqual(oneOverTwo == twoOverFour, true) XCTAssertEqual(twoOverFour == ichiUeNi, true) XCTAssertEqual(ichiUeNi == oneOverTwo, true) XCTAssertEqual(oneOverTwo ≡ twoOverFour, false) XCTAssertEqual(twoOverFour ≡ ichiUeNi, false) XCTAssertEqual(ichiUeNi ≡ oneOverTwo, true) let halfIntMax = (Int.max / 2) - 1 property("value holds") <- forAll { (value: Rational) in return ((value.numerator < halfIntMax) && (value.denominator < halfIntMax)) ==> { let doubledSame = Rational(value.numerator * 2, value.denominator * 2)! return (value == doubledSame) && ((value ≡ doubledSame) == false) } } } 25

Slide 32

Slide 32 text

Shrinking 26

Slide 33

Slide 33 text

So? 27

Slide 34

Slide 34 text

So? • Less time writing tests? 27

Slide 35

Slide 35 text

So? • Less time writing tests? • Better tests 27

Slide 36

Slide 36 text

So? • Less time writing tests? • Better tests • Easier diagnosis 27

Slide 37

Slide 37 text

So? • Less time writing tests? • Better tests • Easier diagnosis • You will and should still write test cases! 27

Slide 38

Slide 38 text

For Our Friends in Obj-C https://github.com/jeffh/Fox 28

Slide 39

Slide 39 text

TJ Usiyan @griotspeak 29