seminars.fb

Tools and Techniques → “Testing, Property Testing & hypothesis

Theme: Tools and Techniques

Topic: Testing, Property Testing & hypothesis

Presenter: James Powell james@dutc.io

Date: Friday, October 23, 2020

Time: 9 AM PST

Keywords: Python, data analysis, data engineering, numpy, pandas

print("Let's get started!")
from sys import version_info
print(f'{version_info = }')
from itertools import count
from time import perf_counter
start = perf_counter()
for x in count():
    print(f'{x = :>8} ({perf_counter() - start:.2f}s)')
def add(x, y):
    return x + y
def add(x, y):
    return x + y

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
def add(x, y):
    return x + y

def test_add():
    pass

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
def add(x, y):
    return x + y

def test_add():
    assert add(1, 1) == 2

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
def add(x, y):
    return x + y

def test_add():
    assert add(1, 1) == 2
    assert add(2, 2) == 4

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
def add(x, y):
    if x == 0 and y == 3:
        raise ValueError('undefined for 0 and 3')
    return x + y

def test_add():
    assert add(1, 1) == 2
    assert add(2, 2) == 4

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from random import randrange

def add(x, y):
    if x == 0 and y == 3:
        raise ValueError('undefined for 0 and 3')
    return x + y

@fixture
def x():
    return randrange(0, 10)

@fixture
def y():
    return randrange(0, 10)

def test_add(x, y):
    assert add(x, y) == ...

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from random import randrange

def add(x, y):
    if x == 0 and y == 3:
        raise ValueError('undefined for 0 and 3')
    return x + y

x = y = fixture(lambda: randrange(0, 10))

def test_add(x, y):
    assert add(x, y) == x + y

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture

def add(x, y):
    if x == 0 and y == 3:
        raise ValueError('undefined for 0 and 3')
    return x + y

x = fixture(lambda: 0)
y = fixture(lambda: 3)

def test_add(x, y):
    assert add(x, y) == x + y

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from hypothesis import given
from hypothesis.strategies import integers

def add(x, y):
    if x == 0 and y == 3:
        raise ValueError('undefined for 0 and 3')
    return x + y

@given(x=integers(), y=integers())
def test_add(x, y):
    assert add(x, y) == x + y

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from hypothesis import given
from hypothesis.strategies import integers

def add(x, y):
    if x == 0 and y == 3:
        raise ValueError('undefined for 0 and 3')
    return x + y

@given(x=integers(min_value=-10, max_value=10), y=integers(min_value=-10, max_value=10))
def test_add(x, y):
    assert add(x, y) == x + y

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from hypothesis import given
from hypothesis.strategies import integers

def add(x, y):
    if x == 0 and y == 3:
        return x - y
    return x + y

@given(x=integers(min_value=-10, max_value=10), y=integers(min_value=-10, max_value=10))
def test_add(x, y):
    assert add(x, y) == x + y

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from hypothesis import given
from hypothesis.strategies import integers

def add(x, y):
    if x == 0 and y == 3:
        return x - y
    return x + y

@given(x=(ints:=integers(min_value=-10, max_value=10)), y=ints)
def test_add(x, y):
    assert add(x, y) == add(y, x)

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from hypothesis import given
from hypothesis.strategies import integers

def add(x, y):
    if x == 0 and y == 3:
        return x - y
    return x + y

@given(x=(ints:=integers(min_value=-10, max_value=10)), y=ints, z=ints)
def test_add(x, y, z):
    assert add(x, y) == add(y, x)
    assert add(x, add(y, z)) == add(add(x, y), z)

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from hypothesis import given
from hypothesis.strategies import integers

def add(x, y):
    if x == 0 and y == 3:
        return x - y
    return x + y

@given(x=(ints:=integers(min_value=-10, max_value=10)), y=ints, z=ints)
def test_add(x, y, z):
    assert add(x, y) == add(y, x)
    assert add(x, add(y, z)) == add(add(x, y), z)
    assert add(x, 0) == x

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from hypothesis import given
from hypothesis.strategies import integers

def add(x, y):
    if x == 0 and y == 3:
        return x - y
    return x + y

@given(x=(ints:=integers(min_value=-10, max_value=10)), y=ints, z=ints)
def test_add(x, y, z):
    assert add(x, y) == add(y, x)
    assert add(x, add(y, z)) == add(add(x, y), z)
    assert add(x, 0) == x
    a, b, c = sorted([x, y, z])
    assert add(a, b) <= add(a, c)

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from hypothesis import given
from hypothesis.strategies import integers

def add(x, y):
    return x + y

@given(x=(ints:=integers(min_value=-10, max_value=10)), y=ints, z=ints)
def test_add(x, y, z):
    assert add(x, y) == add(y, x)
    assert add(x, add(y, z)) == add(add(x, y), z)
    assert add(x, 0) == x
    a, b, c = sorted([x, y, z])
    assert add(a, b) <= add(a, c)

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from hypothesis import given
from hypothesis.strategies import integers, floats, one_of

def add(x, y):
    return x + y

@given(x=(nums:=one_of(integers(min_value=-10, max_value=10), floats())), y=nums, z=nums)
def test_add(x, y, z):
    assert add(x, y) == add(y, x)
    assert add(x, add(y, z)) == add(add(x, y), z)
    assert add(x, 0) == x
    a, b, c = sorted([x, y, z])
    assert add(a, b) <= add(a, c)

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from hypothesis import given
from hypothesis.strategies import integers, floats, one_of

def add(x, y):
    return x + y

@given(
    x=(nums:=one_of(integers(min_value=-10, max_value=10), floats(allow_nan=False, allow_infinity=False))),
    y=nums, z=nums
)
def test_add(x, y, z):
    assert add(x, y) == add(y, x)
    assert add(x, add(y, z)) == add(add(x, y), z)
    assert add(x, 0) == x
    a, b, c = sorted([x, y, z])
    assert add(a, b) <= add(a, c)

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from math import isclose
from hypothesis import given
from hypothesis.strategies import integers, floats, one_of

def add(x, y):
    return x + y

@given(
    x=(nums:=one_of(integers(min_value=-10, max_value=10), floats(allow_nan=False, allow_infinity=False))),
    y=nums, z=nums
)
def test_add(x, y, z):
    assert isclose(add(x, y), add(y, x))
    assert isclose(add(x, add(y, z)), add(add(x, y), z))
    assert isclose(add(x, 0), x)
    a, b, c = sorted([x, y, z])
    assert add(a, b) <= add(a, c)

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from math import isclose
from hypothesis import given
from hypothesis.strategies import integers, floats, one_of

def add(x, y):
    return x + y

@given(
    x=(nums:=one_of(integers(min_value=-10, max_value=10), floats(allow_nan=False, allow_infinity=False))),
    y=nums, z=nums
)
def test_add(x, y, z):
    assert isclose(add(x, y), add(y, x), rel_tol=1e-6)
    assert isclose(add(x, add(y, z)), add(add(x, y), z), rel_tol=1e-6)
    assert isclose(add(x, 0), x, rel_tol=1e-6)
    a, b, c = sorted([x, y, z])
    assert add(a, b) <= add(a, c)

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])
from pytest import fixture
from math import isclose
from hypothesis import given
from hypothesis.strategies import integers, floats, one_of

def add(x, y):
    return x + y

@given(
    x=(nums:=one_of(integers(min_value=-10, max_value=10), floats(allow_nan=False, allow_infinity=False))),
    y=nums, z=nums
)
def test_add(x, y, z):
    assert isclose(add(x, y), add(y, x), rel_tol=1e-6)
    assert isclose(add(x, add(y, z)), add(add(x, y), z), rel_tol=1e-6)
    assert isclose(add(x, 0), x, rel_tol=1e-6)
    a, b, c = sorted([x, y, z])
    assert add(a, b) <= add(a, c)

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])

Tip: generators are an API simplification mechanism!

Fibonacci sequence: Fib[n] = Fib[n-1] + Fib[n-2]

Fibonacci sequence: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …

def fib(n):
    if n == 0 or n == 1:
        return 1
    return fib(n-1) + fib(n-2)

print(f'{fib(10) = }')
def fib(n):
    rv = [1, 1]
    while True:
        if len(rv) == n:
            break
        rv.append( rv[-1] + rv[-2] )
    return rv

print(f'{fib(n=10) = }')
def fib(m):
    rv = [1, 1]
    while True:
        if rv[-1] + rv[-2] >= m:
            break
        rv.append( rv[-1] + rv[-2] )
    return rv

print(f'{fib(m=90) = }')
from time import perf_counter
def fib(t):
    rv = [1, 1]
    start = perf_counter()
    while True:
        if perf_counter() - start >= t:
            break
        rv.append( rv[-1] + rv[-2] )
    return rv

print(f'{fib(t=1e-5) = }')
from time import perf_counter
def fib(n=None, m=None, t=None):
    rv = [1, 1]
    if n is not None:
        while True:
            if len(rv) == n:
                break
            rv.append( rv[-1] + rv[-2] )
    elif m is not None:
        while True:
            if rv[-1] + rv[-2] >= m:
                break
            rv.append( rv[-1] + rv[-2] )
    elif t is not None:
        start = perf_counter()
        while True:
            if perf_counter() - start >= t:
                break
            rv.append( rv[-1] + rv[-2] )
    return rv

print(f'{fib(n=10)   = }')
print(f'{fib(m=90)   = }')
print(f'{fib(t=1e-5) = }')
def fib(a=1, b=1):
    while True:
        yield a
        a, b = b, a + b

for x in fib():
    print(f'{x = }')
def fib(a=1, b=1):
    while True:
        yield a
        a, b = b, a + b

from itertools import islice, takewhile
from time import perf_counter
def timed(g, t):
    start = perf_counter()
    for x in g:
        if perf_counter() - start >= t:
            break
        yield x

fibn = lambda n: islice(fib(), n)
fibm = lambda m: takewhile(lambda x: x < m, fib())
fibt = lambda t: timed(fib(), t)

print(f'{[*fibn(10)]   = }')
print(f'{[*fibm(90)]   = }')
print(f'{[*fibt(1e-5)] = }')
from hypothesis import given
from hypothesis.strategies import integers

def fib(a=1, b=1):
    while True:
        yield a
        a, b = b, a + b

# nwise(g, n): select overlapping windows of size N from G
from itertools import islice, tee
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))

from itertools import islice

@given(a=integers(), b=integers())
def test_fib(a, b):
    for x, y, z in islice(nwise(fib(a, b), 3), 10_000):
        assert x + y == z

from pytest import main
if __name__ == '__main__':
    main(['-q', __file__])