Slide 1

Slide 1 text

Property Based Testing @mathiasverraes

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

function inc(x) { return x + 1; }

Slide 4

Slide 4 text

inc x = x + 1

Slide 5

Slide 5 text

-- Tests double 1 `should_be` 2 double 2 `should_be` 4 -- Implementation double x | x == 1 = 2 | x == 2 = 4

Slide 6

Slide 6 text

A property of double double_is_always_even :: Int -> Bool double_is_always_even x = even (double x)

Slide 7

Slide 7 text

> quickCheck double_is_always_even Failed: 0 (after 1 test) Exception: Non-exhaustive patterns in function double

Slide 8

Slide 8 text

double x = x * 2

Slide 9

Slide 9 text

quickCheck double_is_always_even Passed: 0 Passed: 1 Passed: -3 Passed: -1 (...) Passed: -58 Passed: 89 +++ OK, passed 100 tests.

Slide 10

Slide 10 text

More properties of double double_compare_to_input x | x > 0 = double x > x | x < 0 = double x < x | x == 0 = True double_minus_input x = double x - x == x

Slide 11

Slide 11 text

Distributivity Law reverse_is_distributive xs ys = reverse (xs++ys) == reverse xs ++ reverse ys

Slide 12

Slide 12 text

> quickCheck reverse_is_distributive Passed: [] [] Passed: [] [1] Passed: [1] [] Failed: [3,-3] [0,2,0] Passed: [] [0,2,0] Failed: [3] [0,2,0] Failed: [-3] [0,2,0] Failed: [0] [0,2,0] Failed: [0] [2,0] (...) Falsifiable (after 4 tests and 6 shrinks): [0] [1]

Slide 13

Slide 13 text

Oops... reverse_is_distributive xs ys = reverse (xs++ys) == reverse ys ++ reverse xs

Slide 14

Slide 14 text

Passed: [] [] Passed: [1,-2] [0] Passed: [-2] [] (...) Passed: [-34,44,-58,-41,-17,-53,-14,27,54,46,-10,-46,-20,46] [-9,-32,-47,50,43,-47,-43,-61,37,4,-59,48,34] +++ OK, passed 100 tests.

Slide 15

Slide 15 text

Split 1 -- define split :: Char -> String -> [String] -- so that split '@' "[email protected]" == ["foo","example.com"] split '/' "/usr/include" == ["", "usr", "include"] 1 https://www.schoolofhaskell.com/user/pbv/an-introduction-to-quickcheck-testing

Slide 16

Slide 16 text

-- splitting an empty list results in an empty list split char [] = [] split char str | null after = before : [] | otherwise = before : split char (tail after) where before = takeWhile (/=char) str after = dropWhile (/=char) str

Slide 17

Slide 17 text

Property: Splitting and unsplitting -- test unsplit '@' ["foo","example.com"] == "[email protected]" unsplit '/' ["", "usr", "include"] == "/usr/include" --implementation unsplit :: Char -> [String] -> String unsplit char = concat . intersperse [char]

Slide 18

Slide 18 text

-- property unsplit_inverses_split str = forAll (elements str) (\char -> unsplit char (split char str) == str)

Slide 19

Slide 19 text

> quickCheck unsplit_inverses_split Passed: "" Passed: "\252\210" '\252' Passed: "\163^\EOT" '\163' Passed: "v\RSs" 'v' Failed: "y" 'y' Failed: "a" 'a' Falsifiable (after 6 tests and 1 shrink): "a" 'a'

Slide 20

Slide 20 text

split 'a' "a" == [""] unsplit 'a' [""] == "" -- should be: split 'a' "a" == ["", ""] unsplit 'a' ["", ""] == "a"

Slide 21

Slide 21 text

Modify the definition of split split char [] = [] ... -- becomes split char [] = [""] ... > quickCheck unsplit_inverses_split +++ OK, passed 100 tests.

Slide 22

Slide 22 text

Property Based Testing → test lots of random cases cheaply → encourage thinking about general properties Thanks :-) @mathiasverraes