Slide 1

Slide 1 text

Property-based testing with Clojure test.check Reid Draper @reiddraper

Slide 2

Slide 2 text

(= a b )

Slide 3

Slide 3 text

(= [5 2 1] (reverse [1 2 5])) (= [1 2 3 4 5] (reverse [5 4 3 2 1])) (= [5] (reverse [5])) (= [] (reverse []))

Slide 4

Slide 4 text

(= [5 2 1] (reverse [1 2 5])) (= [1 2 3 4 5] (reverse [5 4 3 2 1])) (= [5] (reverse [5])) (= [] (reverse [])) (= [7 8 2 5] (reverse [5 2 8 7])) (= [55 54 53] (reverse [53 54 55])) (= [-15 15] (reverse [15 -15])) (= [true false] (reverse [false true]))

Slide 5

Slide 5 text

(def reverse-property (prop/for-all [a (gen/vector gen/int)] (reversed? a (reverse a))))

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Property-based testing is a powerful testing technique, embodied by Clojure test.check.

Slide 10

Slide 10 text

example-based testing property-based testing test.check

Slide 11

Slide 11 text

example-based testing property-based testing test.check

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

0 ‘() [] {} Integer/MAX_VALUE false -1

Slide 15

Slide 15 text

Our tests are only as mischievous as our imagination

Slide 16

Slide 16 text

{:3aBA "~e", ({"" false} \") ("r" "" {{} 0, :y {0 \+}}), [2 {} [[] {0 :v7}]] -1/4, (1 2 \') {}, :4 ["" ({0 true})], {false {}, {(0) (\^)} 0, ("m" [false]) ((true) {}), [[\t] ()] {}} -3}

Slide 17

Slide 17 text

(= a b )

Slide 18

Slide 18 text

A change in perspective is worth 80 IQ points. - Alan Kay

Slide 19

Slide 19 text

example-based testing property-based testing test.check

Slide 20

Slide 20 text

generative testing random testing

Slide 21

Slide 21 text

never forgets edge-cases

Slide 22

Slide 22 text

thousands of tests

Slide 23

Slide 23 text

encodes a function’s invariants directly

Slide 24

Slide 24 text

(concat a b) ;; Returns a lazy seq representing the concatenation of the elements in the supplied colls.

Slide 25

Slide 25 text

(prop/for-all [a (gen/vector gen/any) b (gen/vector gen/any)] (= (count (concat a b)) (+ (count a) (count b))))

Slide 26

Slide 26 text

(prop/for-all [a (gen/vector gen/any) b (gen/vector gen/any)] (= (count (concat a b)) (+ (count a) (count b)))) universal-quantification

Slide 27

Slide 27 text

(prop/for-all [a (gen/vector gen/any) b (gen/vector gen/any)] (= (count (concat a b)) (+ (count a) (count b)))) binding

Slide 28

Slide 28 text

(prop/for-all [a (gen/vector gen/any) b (gen/vector gen/any)] (= (count (concat a b)) (+ (count a) (count b)))) property

Slide 29

Slide 29 text

(tc/quick-check 100 my-property) ;; => {:result true, ;; :num-tests 100, ;; :seed 1395110930836}

Slide 30

Slide 30 text

thinking in properties

Slide 31

Slide 31 text

roundtrip trusted-implementation input/output relation

Slide 32

Slide 32 text

roundtrip

Slide 33

Slide 33 text

(prop/for-all [a gen/any-printable] (= a (-> a prn-str edn/read-string))) serialize de-serialize

Slide 34

Slide 34 text

trusted-implementation

Slide 35

Slide 35 text

(def transient-property (prop/for-all [a (gen/vector gen-action)] (= (apply-actions #{} a) (apply-actions #{} (filter-transients a)))))

Slide 36

Slide 36 text

input/output relation

Slide 37

Slide 37 text

(prop/for-all [a (gen/vector gen/any) b (gen/vector gen/any)] (= (count (concat a b)) (+ (count a) (count b))))

Slide 38

Slide 38 text

example-based testing property-based testing test.check

Slide 39

Slide 39 text

github.com/clojure/ test.check

Slide 40

Slide 40 text

clojure clojurescript pedestal core.matrix collection-check byte-streams

Slide 41

Slide 41 text

quickcheck Haskell (QuickCheck) 2000 Erlang (EQC) 2006 Scala (ScalaCheck) ~2008

Slide 42

Slide 42 text

let's see an example

Slide 43

Slide 43 text

(-> #{} (conj 109) (conj -110) transient (disj! -110) persistent! (conj -110))

Slide 44

Slide 44 text

(-> #{} (conj 109) (conj -110) transient (disj! -110) persistent! (conj -110))

Slide 45

Slide 45 text

(def transient-property (prop/for-all [a (gen/vector gen-action)] (= (apply-actions #{} a) (apply-actions #{} (filter-transients a)))))

Slide 46

Slide 46 text

(tc/quick-check 1e5 ;; 100,000 transient-property)

Slide 47

Slide 47 text

{:result false, :failing-size 92, :num-tests 2893, :fail "...", :shrunk {:total-nodes-visited 440 :depth 83 :result false :smallest "..."}}

Slide 48

Slide 48 text

[[:conj 109] [:conj -110] [:transient ] [:disj -110] [:persistent!] [:conj -110]]

Slide 49

Slide 49 text

[[:disj -108] [:disj -24] [:disj 89] [:transient] [:conj 12] [:disj -136] [:transient] [:conj -145] [:conj -8] [:persistent!] [:persistent!] [:conj 25] [:conj 142] [:conj 146] [:persistent!] [:transient] [:conj 95] [:persistent!] [:disj -86] [:transient] [:persistent!] [:transient] [:persistent!] [:conj -109] [:transient] [:transient] [:persistent!] [:disj -6] [:transient] [:persistent!] [:conj -50] [:persistent!] [:conj -166] [:transient] [:conj 136] [:persistent!] [:persistent!] [:transient] [:disj 155] [:persistent!] [:transient] [:transient] [:disj 143] [:persistent!] [:persistent!] [:transient] [:conj -10] [:transient] [:conj -59] [:conj -34] [:transient] [:transient] [:disj 111] [:transient] [:disj -33] [:transient] [:conj -96] [:transient] [:disj 93] [:disj -130] [:conj 88] [:disj 14] [:transient] [:disj 9] [:persistent!] [:disj 86] [:conj 109] [:conj -45] [:persistent!] [:persistent!] [:disj -141] [:conj -14] [:persistent!] [:persistent!] [:conj -104] [:conj -83] [:persistent!] [:persistent!] [:conj -73] [:conj 13] [:persistent!] [:persistent!] [:persistent!] [:disj -14] [:conj -129] [:disj -53] [:transient] [:conj -110] [:conj 60] [:conj 11] [:disj -128] [:disj -42] [:disj 18] [:disj 73] [:disj 33] [:transient] [:persistent!] [:persistent!] [:transient] [:persistent!] [:transient] [:disj -123] [:disj -141] [:conj -26] [:conj -92] [:conj -116] [:conj 101] [:disj -133] [:conj 58] [:transient] [:disj -110] [:persistent!] [:disj 105] [:conj 26] [:conj -110] [:persistent!] [:transient] [:persistent!] [:persistent!] [:disj -5] [:conj 117] [:transient] [:persistent!] [:disj 96] [:persistent!] [:disj 52] [:persistent!] [:disj -132] [:transient] [:transient] [:conj -108] [:disj 121] [:persistent!]]

Slide 50

Slide 50 text

[[:conj 109] [:conj -110] [:transient ] [:disj -110] [:persistent!] [:conj -110]]

Slide 51

Slide 51 text

CLJ-1285

Slide 52

Slide 52 text

fixed in clojure 1.6

Slide 53

Slide 53 text

github.com/reiddraper/ clojure-transient-test

Slide 54

Slide 54 text

generators

Slide 55

Slide 55 text

(prop/for-all [a (gen/vector gen/any) b (gen/vector gen/any)] (= (count (concat a b)) (+ (count a) (count b)))) generators

Slide 56

Slide 56 text

any any-printable boolean byte bytes char char- alpha-numeric char-ascii hash-map int keyword list map nat neg-int pos-int ratio s-neg-int s-pos- int string string-alpha-numeric string-ascii tuple vector

Slide 57

Slide 57 text

(gen/sample gen/boolean) (false false false false false true false false true true) boolean

Slide 58

Slide 58 text

(gen/sample gen/int) (0 0 -2 -3 1 3 -2 0 0 -9) int

Slide 59

Slide 59 text

(gen/sample (gen/vector gen/int)) ([] [1] [2 0] [1 2 -1] [] [0] [-4] [] [-8] [4 0 2 -8 -9]) vector

Slide 60

Slide 60 text

(last (gen/sample gen/any-printable)) {:3aBA "~e", ({"" false} \") ("r" "" {{} 0, :y {0 \+}}), [2 {} [[] {0 :v7}]] -1/4, (1 2 \') {}, :4 ["" ({0 true})], {false {}, {(0) (\^)} 0, ("m" [false]) ((true) {}), [[\t] ()] {}} -3} any

Slide 61

Slide 61 text

bind choose elements frequency no-shrink not- empty resize return sample sample-seq shrink-2 sized such-that

Slide 62

Slide 62 text

(gen/sample (gen/return :foo)) (:foo :foo :foo :foo :foo :foo :foo :foo :foo :foo) return

Slide 63

Slide 63 text

(gen/sample (gen/choose 5 10)) (7 8 9 6 6 5 8 9 10 10) choose

Slide 64

Slide 64 text

(gen/sample (gen/elements [:clojure :haskell :erlang :python :ruby])) (:clojure :haskell :haskell :clojure :clojure :python :cloj ure :clojure :ruby :ruby) elements

Slide 65

Slide 65 text

(gen/sample (gen/one-of [gen/boolean gen/byte])) (119 true -24 120 -93 true false 27 false false) one-of

Slide 66

Slide 66 text

(gen/sample (gen/frequency [[5 (gen/return :weekday)] [2 (gen/return :weekend)]])) (:weekday :weekday :weekend :weekday :weekday :weekend :wee kend :weekend :weekday :weekday) frequency

Slide 67

Slide 67 text

(gen/sample (gen/such-that #(not= 0 %) gen/int)) (-1 -2 -1 2 3 -5 -6 6 -5 -1) such-that

Slide 68

Slide 68 text

(gen/sample (gen/fmap odd? gen/int)) (false true false true true false false false false false) fmap

Slide 69

Slide 69 text

(gen/sample (gen/fmap odd? gen/int)) (false true false true true false false false false false) apply odd? to every generated value

Slide 70

Slide 70 text

(gen/sample (gen/bind (gen/not-empty (gen/vector gen/int)) #(gen/tuple (gen/return %) (gen/elements %)))) bind

Slide 71

Slide 71 text

([[0] 0] [[1 -2] 1] [[2 2] 2] [[-3 -3] -3] [[4 -4] -4] [[2 1] 2] [[-6 -6 -5 -1] -5] [[8 -5 6 -6 -6 -5 7 -7] 8] [[-3 2 -5 -4] -5] [[7 5] 5]) tuples of vector and chosen element

Slide 72

Slide 72 text

example-based testing property-based testing test.check

Slide 73

Slide 73 text

[org.clojure/test.check "0.6.1"]

Slide 74

Slide 74 text

A change in perspective is worth 80 IQ points. - Alan Kay

Slide 75

Slide 75 text

github.com/clojure/ test.check github.com/reiddraper/ clojure-transient-test