71
Property-Based Testing

Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Property-Based Testing

Page 2: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Kinds of Testing

Page 3: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Example-Based Tests

Page 4: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

+,  -­‐

Page 5: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

expect(1  +  2).to  eq  3

Page 6: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

expect(3  -­‐  2).to  eq  1

Page 7: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

expect(3  -­‐  2).to  eq  1

Page 8: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Property-Based Tests

Page 9: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

For  some  number,  n:  !

n  +  1  >  n

Page 10: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

For  some  number,  n:  !

n  +  1  >  n  n  -­‐  1  <  n

Page 11: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

For  some  number,  n:  !

n  +  1  >  n  n  -­‐  1  <  n  

n  +  1  -­‐  1  =  n      #

Page 12: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Practicalities

Page 13: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "describes  +  and  -­‐"  do      property_of  {          integer      }.check  {  |n|          expect(n  +  1  >  n).to  be  true          expect(n  -­‐  1  <  n).to  be  true          expect(n  +  1  -­‐  1).to  eq  n      }  end

Page 14: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "describes  +  and  -­‐"  do      property_of  {          integer      }.check  {  |n|          expect(n  +  1  >  n).to  be  true          expect(n  -­‐  1  <  n).to  be  true          expect(n  +  1  -­‐  1).to  eq  n      }  end

Page 15: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "describes  +  and  -­‐"  do      property_of  {          integer      }.check  {  |n|          expect(n  +  1  >  n).to  be  true          expect(n  -­‐  1  <  n).to  be  true          expect(n  +  1  -­‐  1).to  eq  n      }  end

Page 16: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "describes  +  and  -­‐"  do      property_of  {          integer      }.check  {  |n|          expect(n  +  1  >  n).to  be  true          expect(n  -­‐  1  <  n).to  be  true          expect(n  +  1  -­‐  1).to  eq  n      }  end

Page 17: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

success:  100  tests      describes  +  and  -­‐

Page 18: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Something that breaks

Page 19: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

For  some  string,  x:  !

x.split("  ").join("  ")  ==  x

Page 20: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "split/join  is  reversible"  do      property_of  {          string      }.check  {  |x|          expect(            x.split("  ").join("  ")        ).to  eq(x)      }  end

Page 21: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "split/join  is  reversible"  do      property_of  {          string      }.check  {  |x|          expect(            x.split("  ").join("  ")        ).to  eq(x)      }  end

Page 22: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "split/join  is  reversible"  do      property_of  {          string      }.check  {  |x|          expect(            x.split("  ").join("  ")        ).to  eq(x)      }  end

Page 23: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "split/join  is  reversible"  do      property_of  {          string      }.check  {  |x|          expect(            x.split("  ").join("  ")        ).to  eq(x)      }  end

Page 24: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

failure  after  7  tests,  on:  "  &2M1`"  found  a  reduced  failure  case:  "  &M1`"  found  a  reduced  failure  case:  "  M1`"  found  a  reduced  success:  "M1`"  minimal  failed  data  is:  "  M1`"      split/join  is  reversible  (FAILED  -­‐  1)

Page 25: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

failure  after  7  tests,  on:  "  &2M1`"  found  a  reduced  failure  case:  "  &M1`"  found  a  reduced  failure  case:  "  M1`"  found  a  reduced  success:  "M1`"  minimal  failed  data  is:  "  M1`"      split/join  is  reversible  (FAILED  -­‐  1)

Page 26: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

failure  after  7  tests,  on:  "  &2M1`"  found  a  reduced  failure  case:  "  &M1`"  found  a  reduced  failure  case:  "  M1`"  found  a  reduced  success:  "M1`"  minimal  failed  data  is:  "  M1`"      split/join  is  reversible  (FAILED  -­‐  1)

Page 27: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

failure  after  7  tests,  on:  "  &2M1`"  found  a  reduced  failure  case:  "  &M1`"  found  a  reduced  failure  case:  "  M1`"  found  a  reduced  success:  "M1`"  minimal  failed  data  is:  "  M1`"      split/join  is  reversible  (FAILED  -­‐  1)

Page 28: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

failure  after  7  tests,  on:  "  &2M1`"  found  a  reduced  failure  case:  "  &M1`"  found  a  reduced  failure  case:  "  M1`"  found  a  reduced  success:  "M1`"  minimal  failed  data  is:  "  M1`"      split/join  is  reversible  (FAILED  -­‐  1)

Page 29: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Three Things: Data Generation,

Testing with the Data, Data Reduction

Page 30: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Uses

Page 31: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Edge Cases

Page 32: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Treating the code like an adversary

Page 33: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Honest TDD;No fudging code to

pass an example test.

Page 34: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Kinds of Properties

Page 35: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Reversible

Page 36: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

n  +  1  -­‐  1  ==  n

Reversible

Page 37: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

n  +  1  -­‐  1  ==  n  !

#  where  y  !=  "  "x.split(y).join(y)  ==  x

Reversible

Page 38: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

n  +  1  -­‐  1  ==  n  !

#  where  y  !=  "  "x.split(y).join(y)  ==  x  

!

decompress(compress(d))  ==  d

Reversible

Page 39: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

n  +  1  -­‐  1  ==  n  !

#  where  y  !=  "  "x.split(y).join(y)  ==  x  

!

decompress(compress(d))  ==  d  !

       t.to_zone('UTC')          .to_zone(t.zone)  ==  t.zone

Reversible

Page 40: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

n  +  1  -­‐  1  ==  n  !

#  where  y  !=  "  "x.split(y).join(y)  ==  x  

!

decompress(compress(d))  ==  d  !

       t.to_zone('UTC')          .to_zone(t.zone)  ==  t.zone

Reversible

I goofed this in the original; I've fixed this example. Pardon my onstage embarrassment. :-)

Page 41: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Repeatable

Page 42: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

list.sort.sort  ==  list.sortRepeatable

Page 43: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

list.sort.sort  ==  list.sort  !

handler.handle(evt).handle(evt)  ==  handler.handle(evt)

Repeatable

Page 44: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Unbreakable Rules

Page 45: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

   list.sort.count  ==  list.count

Unbreakable Rules

Page 46: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

   list.sort.count  ==  list.count  !

   list.sort.all?  {|x|          list.find_index(x)  !=  nil      }

Unbreakable Rules

Page 47: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Swapping the Ordering

Page 48: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

a.map{|n|  n  +  1}.sort  ==  

a.sort.map{|n|  n  +  1}

Swapping the Ordering

Page 49: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Prove a Small Part

Page 50: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

     pairs(list.sort).all?{|(x,y)|            x  <=  y        }      #  pairs([1,2,3])        #    =>  [[1,2],  [2,3]]  

Prove a Small Part

Page 51: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Hard to Solve, Easy to Check

Page 52: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

 solve(solvable_maze)      !=  nil    solve(unsolvable_maze)  ==  nil

Hard to Solve, Easy to Check

Page 53: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

 solve(solvable_maze)      !=  nil    solve(unsolvable_maze)  ==  nil

Hard to Solve, Easy to Check

I really punted on this example, particularly how you generate a maze you know is solvable. Sorry.

Page 54: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Consult an Oracle

Page 55: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

list.hypersort  ==  list.sort

Consult an Oracle

Page 56: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

list.hypersort  ==  list.sort  !

new_code(input)  ==  old_code(input)

Consult an Oracle

Page 57: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

SHOW US SOME REAL EXAMPLES

Page 58: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "can  round-­‐trip  last-­‐logged-­‐in"  do      property_of  {          (Time.current  -­‐  float.abs)      }.check  {  |time|          user  =  User.create(            username:  "Sam",              last_logged_in_at:  time,          )          expect(              User.find(user.id).last_logged_in_at          ).to  eq(time)      }  end

Page 59: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "can  round-­‐trip  last-­‐logged-­‐in"  do      property_of  {          (Time.current  -­‐  float.abs)      }.check  {  |time|          user  =  User.create(            username:  "Sam",              last_logged_in_at:  time,          )          expect(              User.find(user.id).last_logged_in_at          ).to  eq(time)      }  end

Page 60: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "can  round-­‐trip  last-­‐logged-­‐in"  do      property_of  {          (Time.current  -­‐  float.abs)      }.check  {  |time|          user  =  User.create(            username:  "Sam",              last_logged_in_at:  time,          )          expect(              User.find(user.id).last_logged_in_at          ).to  eq(time)      }  end

Page 61: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

failure:  0  tests,  on:  Sat,  13  Jun  2015  04:39:52  UTC  +00:00          can  round-­‐trip  last-­‐logged-­‐in  (FAILED  -­‐  1)  !Failures:  !    1)  User  round-­‐trip  can  round-­‐trip  last-­‐logged-­‐in            Failure/Error:  expect(User.find(user.id).last_logged_in_at).to  eq  time  !              expected:  2015-­‐06-­‐13  04:39:52.835645641  +0000                          got:  2015-­‐06-­‐13  04:39:52.835645000  +0000

Page 62: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "after_transition  args"  do      property_of  {          array  {  choose  boolean,  string,  integer}      }.check  {  |args|          test  =  -­‐>  (a)  {  expect(a).to  eq(args)  }  !        machine  =  Class.new  do              state_machine  initial:  :stopped  do                  event  :go  do                      transition  :stopped  =>  :going                  end                  after_transition(:stopped  =>  :going,                                                    :do  =>  proc  {  |machine,transition|                                                        test.call(transition.args)                                                    })              end          end  !        machine.new.go(*args)      }  end

Page 63: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "after_transition  args"  do      property_of  {          array  {  choose  boolean,  string,  integer}      }.check  {  |args|          test  =  -­‐>  (a)  {  expect(a).to  eq(args)  }  !        machine  =  Class.new  do              state_machine  initial:  :stopped  do                  event  :go  do                      transition  :stopped  =>  :going                  end                  after_transition(:stopped  =>  :going,                                                    :do  =>  proc  {  |machine,transition|                                                        test.call(transition.args)                                                    })              end          end  !        machine.new.go(*args)      }  end

Page 64: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "after_transition  args"  do      property_of  {          array  {  choose  boolean,  string,  integer}      }.check  {  |args|          test  =  -­‐>  (a)  {  expect(a).to  eq(args)  }  !        machine  =  Class.new  do              state_machine  initial:  :stopped  do                  event  :go  do                      transition  :stopped  =>  :going                  end                  after_transition(:stopped  =>  :going,                                                    :do  =>  proc  {  |machine,transition|                                                        test.call(transition.args)                                                    })              end          end  !        machine.new.go(*args)      }  end

Page 65: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

it  "after_transition  args"  do      property_of  {          array  {  choose  boolean,  string,  integer}      }.check  {  |args|          test  =  -­‐>  (a)  {  expect(a).to  eq(args)  }  !        machine  =  Class.new  do              state_machine  initial:  :stopped  do                  event  :go  do                      transition  :stopped  =>  :going                  end                  after_transition(:stopped  =>  :going,                                                    :do  =>  proc  {  |machine,transition|                                                        test.call(transition.args)                                                    })              end          end  !        machine.new.go(*args)      }  end

Page 66: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

failure:  1  tests,  on:  ["y}K'ID",  "aR/-­‐xm",  "^H:/_B",  true,  false,  true]  found  a  reduced  failure  case:  ["y}K'D",  "aR/-­‐xm",  "^H:/_B",  true,  false,  true]  found  a  reduced  failure  case:  ["}K'D",  "aR/-­‐xm",  "^H:/_B",  true,  false,  true]  ...  found  a  reduced  failure  case:  ["",  "",  "",  true,  false,  true]  found  a  reduced  failure  case:  ["",  "",  "",  true,  false]  found  a  reduced  failure  case:  ["",  "",  "",  false]  found  a  reduced  success:  ["",  "",  ""]  minimal  failed  data  is:  ["",  "",  "",  false]

Page 67: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

failure:  1  tests,  on:  ["y}K'ID",  "aR/-­‐xm",  "^H:/_B",  true,  false,  true]  found  a  reduced  failure  case:  ["y}K'D",  "aR/-­‐xm",  "^H:/_B",  true,  false,  true]  found  a  reduced  failure  case:  ["}K'D",  "aR/-­‐xm",  "^H:/_B",  true,  false,  true]  ...  found  a  reduced  failure  case:  ["",  "",  "",  true,  false,  true]  found  a  reduced  failure  case:  ["",  "",  "",  true,  false]  found  a  reduced  failure  case:  ["",  "",  "",  false]  found  a  reduced  success:  ["",  "",  ""]  minimal  failed  data  is:  ["",  "",  "",  false]

Page 68: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

failure:  1  tests,  on:  ["y}K'ID",  "aR/-­‐xm",  "^H:/_B",  true,  false,  true]  found  a  reduced  failure  case:  ["y}K'D",  "aR/-­‐xm",  "^H:/_B",  true,  false,  true]  found  a  reduced  failure  case:  ["}K'D",  "aR/-­‐xm",  "^H:/_B",  true,  false,  true]  ...  found  a  reduced  failure  case:  ["",  "",  "",  true,  false,  true]  found  a  reduced  failure  case:  ["",  "",  "",  true,  false]  found  a  reduced  failure  case:  ["",  "",  "",  false]  found  a  reduced  success:  ["",  "",  ""]  minimal  failed  data  is:  ["",  "",  "",  false]

Page 69: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Random vs

Exhaustive

Page 70: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Refs, Credits and Things to Look At

• fsharpforfunandprofit.com(Property-Based Testing Posts)

• github.com/charleso/property-testing-preso (LambdaJam talk)

• Rantly (Ruby, used in examples)

• QuickCheck, SmallCheck (Haskell)

• Hypothesis (Python)

Page 71: Property-Based Testing - robhoward.id.au · it"describes+and"do property_of{integer}.check{|n| expect(n+1>n).tobetrue expect(n1

Fin.

Rob Howard

@damncabbage robhoward.id.au