Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Escape from automanual testing with Hypothesis!

Escape from automanual testing with Hypothesis!

Avatar for PyLondinium18

PyLondinium18

June 09, 2018
Tweet

More Decks by PyLondinium18

Other Decks in Programming

Transcript

  1. 3 “The sum of a list of integers is greater

    than the largest element in the list” * of how to test, not what to test! A Simple Example* def test_sum_above_max_small(): xs = [1, 2, 3] assert sum(xs) > max(xs), ... def test_sum_above_max_large(): xs = [10, 20, 30] assert sum(xs) > max(xs), ... Escape from automanual testing with Hypothesis!
  2. 4 “The sum of a list of integers is greater

    than the largest element in the list” A Simple Example import pytest @pytest.mark.parametrize('xs', [ [1, 2, 3], [10, 20, 30], ... ]) def test_sum_above_max(xs): assert sum(xs) > max(xs), ... Escape from automanual testing with Hypothesis!
  3. 5 “The sum of a list of integers is greater

    than the largest element in the list” What do we learn? A Simple Example from hypothesis import given from hypothesis.strategies import lists, integers @given(lists(integers())) def test_sum_above_max(xs): assert sum(xs) > max(xs), ... Escape from automanual testing with Hypothesis!
  4. 6 “The sum of a list of integers is greater

    than the largest element in the list” What do we learn? • Error to call max([]) A Simple Example from hypothesis import given from hypothesis.strategies import lists, integers @given(lists(integers())) def test_sum_above_max(xs): assert sum(xs) > max(xs), ... Falsifying example: test_sum_above_max(xs=[]) Traceback (most recent call last): ... ValueError: max() arg is an empty sequence Escape from automanual testing with Hypothesis!
  5. 7 “The sum of a list of integers is greater

    than the largest element in the list” What do we learn? • Error to call max([]) A Simple Example from hypothesis import given from hypothesis.strategies import lists, integers @given(lists(integers(), min_size=1)) def test_sum_above_max(xs): assert sum(xs) > max(xs), ... Escape from automanual testing with Hypothesis!
  6. 8 “The sum of a list of integers is greater

    than the largest element in the list” What do we learn? • Error to call max([]) • Need greater or equal for 1-lists A Simple Example from hypothesis import given from hypothesis.strategies import lists, integers @given(lists(integers(), min_size=1)) def test_sum_above_max(xs): assert sum(xs) > max(xs), ... Falsifying example: test_sum_above_max(xs=[0]) Traceback (most recent call last): ... AssertionError: xs=[0], sum(xs)=0, max(xs)=0 Escape from automanual testing with Hypothesis!
  7. 9 “The sum of a list of integers is greater

    than the largest element in the list” What do we learn? • Error to call max([]) • Need greater or equal for 1-lists Escape from automanual testing with Hypothesis! A Simple Example from hypothesis import given from hypothesis.strategies import lists, integers @given(lists(integers(), min_size=1)) def test_sum_above_max(xs): assert sum(xs) >= max(xs), ...
  8. 10 “The sum of a list of integers is greater

    than the largest element in the list” What do we learn? • Error to call max([]) • Need greater or equal for 1-lists • Can’t have negative integers A Simple Example from hypothesis import given from hypothesis.strategies import lists, integers @given(lists(integers(), min_size=1)) def test_sum_above_max(xs): assert sum(xs) >= max(xs), ... Falsifying example: test_sum_above_max(xs=[0, -1]) Traceback (most recent call last): ... AssertionError: xs=[0, -1], sum(xs)=-1, max(xs)=0 Escape from automanual testing with Hypothesis!
  9. 11 “The sum of a list of integers is greater

    than the largest element in the list” What do we learn? • Error to call max([]) • Need greater or equal for 1-lists • Can’t have negative integers And this version works! A Simple Example from hypothesis import given from hypothesis.strategies import lists, integers @given(lists(integers(min_value=0), min_size=1)) def test_sum_above_max(xs): assert sum(xs) >= max(xs), ... Escape from automanual testing with Hypothesis!
  10. First-party support for common types • None, bool, numbers, strings,

    times… • min_value and max_value args • type-specific args floats(allow_infinity=False) times(..., timezones=...) Collections are composed: • From elements and sizes • From keys or indices fancier options are later in the talk 13 See https://hypothesis.readthedocs.io/en/latest/data.html Values and Collections Escape from automanual testing with Hypothesis!
  11. 14 .map(f) - applies function f to example - minimises

    before mapping .filter(f) - retry unless f(ex) truthy - mostly for edge cases Transforming Strategies Escape from automanual testing with Hypothesis! s = integers() s.map(str) # strings of digits s.map(lambda x: x * 2) # even integers s.filter(lambda x: x % 2) # odd ints, slowly # Lists with >= 2 unique numbers lists(s, 2).filter(lambda x: len(set(x)) >=2) See https://hypothesis.readthedocs.io/en/latest/data.html
  12. 15 Recursive data • Several ways to recur, e.g.: Interactive

    data • Run part of a test, then get more input • Useful with complex dataflow Custom strategies • Similar to interactive data in tests Advanced Options strat = deferred( lambda: integers() | lists(strat)) @given(data()) def test_something(data): i = data.draw(integers(...)) @composite def str_and_index(draw, min_size): s = draw(text(min_size=min_size)) i = draw(integers(min_size, len(s)) return (s, i) See https://hypothesis.readthedocs.io/en/latest/data.html Escape from automanual testing with Hypothesis!
  13. 16 • Strings matching a regular expression • Function arguments

    from type hints • Objects from Attrs classes • Values for Numpy datatype • Data from a Django model • Many, many 3rd-party extensions Strong data validation makes testing easier! Inferring Data from Code Escape from automanual testing with Hypothesis!
  14. 18 • Verbosity and debug info • How many inputs

    to test • Cache location • Deterministic mode • and much more! Configure in code or from pytest Define and load custom profiles Configuring Hypothesis from hypothesis import given, settings, database from hypothesis.strategies import none settings.database = database.ExampleDatabase( "/path/to/cache/dir/" # or ":memory:" ) @settings(derandomize=True, max_examples=1000)) @given(...) def test_with_settings(x): ... See https://hypothesis.readthedocs.io/en/latest/settings.html Escape from automanual testing with Hypothesis!
  15. Hypothesis is fast! > pytest --hypothesis-show-statistics - mean time per

    example - % time generating data (~0 to ~20%) - define custom events Hypothesis runs a lot of tests! If your test is slow, running it hundreds of times will be really slow. (but you need fewer tests) Hypothesis runs your code under coverage - ~2x to ~5x slowdown - still improves bugs per unit time - can be disabled in pathological cases 19 Performance tips Escape from automanual testing with Hypothesis!
  16. Integrate shrinking with generation! • Start with an arbitrary bytestream

    • Interpret as value according to strategy • Shrink bytestream, not value • More efficient than shrinking values • Track lots of metadata on targets • Use heuristics for better coverage 20 Secrets of Shrinking Escape from automanual testing with Hypothesis!
  17. Integrate shrinking with generation! • Start with an arbitrary bytestream

    • Interpret as value according to strategy • Shrink bytestream, not value 21 Secrets of Shrinking Escape from automanual testing with Hypothesis! lists(booleans(), min_size=3) stream = [0, 1, 0, 1, 1, 0, ... out = [False, True, False, True]
  18. Shrink bitstream in “shortlex” order • Start by deleting later

    elements • Then reduce bytes • Real shrinker is smarter Let’s shrink out, with any(out) == True 22 Secrets of Shrinking Escape from automanual testing with Hypothesis! lists(booleans(), min_size=3) stream = [0, 1, 0, 1, 1] out = [False, True, False, True]
  19. Shrink bitstream in “shortlex” order • Start by deleting later

    elements • Then reduce bytes • Real shrinker is smarter Let’s shrink out, with any(out) == True 23 Secrets of Shrinking Escape from automanual testing with Hypothesis! lists(booleans(), min_size=3) stream = [0, 1, 0, 1, 0] out = [False, True, False, False]
  20. Shrink bitstream in “shortlex” order • Start by deleting later

    elements • Then reduce bytes • Real shrinker is smarter Let’s shrink out, with any(out) == True 24 Secrets of Shrinking Escape from automanual testing with Hypothesis! lists(booleans(), min_size=3) stream = [0, 1, 0, 0, 0] out = [False, True, False]
  21. Shrink bitstream in “shortlex” order • Start by deleting later

    elements • Then reduce bytes • Real shrinker is smarter Let’s shrink out, with any(out) == True 25 Secrets of Shrinking Escape from automanual testing with Hypothesis! lists(booleans(), min_size=3) stream = [0, 1, 0] out = [False, True, False]
  22. Shrink bitstream in “shortlex” order • Start by deleting later

    elements • Then reduce bytes • Real shrinker is smarter Let’s shrink out, with any(out) == True 26 Secrets of Shrinking Escape from automanual testing with Hypothesis! lists(booleans(), min_size=3) stream = [0, 1, 1] out = [False, True, True]
  23. Shrink bitstream in “shortlex” order • Start by deleting later

    elements • Then reduce bytes • Real shrinker is smarter Let’s shrink out, with any(out) == True 27 Secrets of Shrinking Escape from automanual testing with Hypothesis! lists(booleans(), min_size=3) stream = [0, 0, 1] out = [False, False, True]
  24. 28 - Mozilla Public License - Contributors mentored! - Training

    available - Sponsor new features Q&A time Escape from automanual testing with Hypothesis! Zac Hatfield-Dodds
  25. 30 The Test Oracle Compare to another version: new_hotness(x) ==

    legacy(x) fancy_algorithm(x) == brute_force(x) foo(x, threads=10) == foo(x, threads=1) Escape from automanual testing with Hypothesis! See https://fsharpforfunandprofit.com/posts/property-based-testing-2/
  26. 31 There and back again “inverse functions” • add /

    subtract • json.dumps / json.loads or just related: • set_x / get_x • list.append / list.index Escape from automanual testing with Hypothesis! See https://fsharpforfunandprofit.com/posts/property-based-testing-2/
  27. 32 Hard to prove, easy to verify Find prime factors,

    then… • multiply factors to get input Tokenise a string, then… • check each token is valid • concatenate to get input Escape from automanual testing with Hypothesis! See https://fsharpforfunandprofit.com/posts/property-based-testing-2/
  28. 33 Some things never change aka “invariants” e.g. sorting a

    list does not change the element counts (half of the definition of sorting!) Escape from automanual testing with Hypothesis! See https://fsharpforfunandprofit.com/posts/property-based-testing-2/
  29. 34 You can stop now “idempotence” • set(x) == set(set(x))

    Good for filtering, databases, etc. Escape from automanual testing with Hypothesis! See https://fsharpforfunandprofit.com/posts/property-based-testing-2/
  30. 35 - Mozilla Public License - Contributors mentored! - Training

    available - Sponsor new features Final Q&A time Escape from automanual testing with Hypothesis! Zac Hatfield-Dodds