Implementation¶
The following are the imports and magics for this notebook.
It turns out that getting pytest working inside a notebook requires some support helper magics in a package called ipytest. Note that some versions of pytest are incompatible with the latest ipytest package. Be sure to get the latest pytest.
Imports¶
import pytest
import ipytest.magics
import warnings
import math
import random
import sys
import os
import subprocess
import datetime
import platform
import datetime
Magics¶
lab_black will format python cells in a standardized way.
%load_ext lab_black
The lab_black extension is already loaded. To reload it, use: %reload_ext lab_black
watermark documents the current environment.
%load_ext watermark
The watermark extension is already loaded. To reload it, use: %reload_ext watermark
Setup¶
pytest works (in part) by rewriting assert statements: we chose to suppress the warning messages about this.
print('pytest version = ', pytest.__version__)
# pytest rewrites Abstact Syntax Tree. ignore warning about this
warnings.filterwarnings('ignore', category=UserWarning)
pytest version = 5.2.4
pytest magics needs to know the notebook file name.
# tell pytest our file name
__file__ = 'pytestnotebook.ipynb'
# trivial function with obvious error
def my_sum(a: float, b: float) -> float:
return a
# end my_sum
We run pytest, cleaning all existing test results, and asking for verbose results.
pytest finds the test_my_sum function, executes it, and catches the assert failures.
%%run_pytest[clean] -v
def test_my_sum():
assert 6==my_sum(6,0), 'Expected 6, got {}'.format(my_sum(6,0))
assert 6==my_sum(2,4), 'Expected 6, got {}'.format(my_sum(2,4))
================================================= test session starts =================================================
platform win32 -- Python 3.7.1, pytest-5.2.4, py-1.7.0, pluggy-0.13.1 -- D:\Anaconda3\envs\ac5-py37\python.exe
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('C:\\Users\\donrc\\Documents\\JupyterNotebooks\\PythonNotebookProject\\develop\\.hypothesis\\examples')
rootdir: C:\Users\donrc\Documents\JupyterNotebooks\PythonNotebookProject\develop
plugins: hypothesis-4.44.2, arraydiff-0.3, doctestplus-0.2.0, openfiles-0.3.1, remotedata-0.3.1
collecting ... collected 1 item
pytestnotebook.py::test_my_sum FAILED [100%]
====================================================== FAILURES =======================================================
_____________________________________________________ test_my_sum _____________________________________________________
def test_my_sum():
assert 6==my_sum(6,0), 'Expected 6, got {}'.format(my_sum(6,0))
> assert 6==my_sum(2,4), 'Expected 6, got {}'.format(my_sum(2,4))
E AssertionError: Expected 6, got 2
<ipython-input-25-f05edba4f4f5>:3: AssertionError
================================================== 1 failed in 0.07s ==================================================
If we run the same test, but minimize output, we get:
%%run_pytest[clean] -qq
def test_my_sum():
assert 6==my_sum(6,0)
assert 6==my_sum(2,4)
F [100%]
====================================================== FAILURES =======================================================
_____________________________________________________ test_my_sum _____________________________________________________
def test_my_sum():
assert 6==my_sum(6,0)
> assert 6==my_sum(2,4)
E AssertionError
<ipython-input-26-d901d1b3a70e>:3: AssertionError
# more complicated function
def quadratic_solve(
a: float, b: float, c: float
) -> (float, float):
# set small value for testing input coefficients
EPS = 1e-10
# test if real roots possible
if b * b < (4 * a * c):
raise ValueError(
'a={a}, b={b}, c={c}: b*b-4*a*c cannot be -ve'
)
# end if
# test if power of x*x too small (ie have linear equation)
if abs(a) > 1e-10:
# choose formulas that minize round off errors
if b > 0:
x1 = (-b - math.sqrt(b * b - 4 * a * c)) / (
2 * a
)
x2 = (2 * c) / (
-b - math.sqrt(b * b - 4 * a * c)
)
else: # b-nve
x1 = (-b + math.sqrt(b * b - 4 * a * c)) / (
2 * a
)
x2 = (2 * c) / (
-b + math.sqrt(b * b - 4 * a * c)
)
# endif
else:
# solve linear equation, if possible
if abs(b) > 1e-10:
x1 = -c / b
x2 = x1
else:
raise ValueError('a,b cannot both be zero')
# end if
# end if
return x1, x2
# end quadratic_solve
Informally test solver in a case where round-off might cause problems.
print(quadratic_solve(1, 1e8, 1))
(-100000000.0, -1e-08)
Now test that the correct exceptions get thrown.
%%run_pytest[clean]
# test throws right exception if complex roots solve quadratic
def test_nve_discriminant():
for n1 in range(1000):
a = random.randint(2, 1_000_000)
c = random.randint(2, 1_000_000)
b_max = int(math.sqrt(4 * a * c)) - 1
b = random.randint(-b_max, b_max + 1)
b = b * random.choice([-1, 1])
with pytest.raises(ValueError):
x1, x2 = quadratic_solve(a, b, c)
# end with
# end for
# end test_nve_discriminant
# test throws right exception if a,b both 0
def test_ab_zero():
for n1 in range(1000):
a = 0
b = 0
c = random.randint(-1_000_000, 1_000_000)
with pytest.raises(ValueError):
x1, x2 = quadratic_solve(a, b, c)
# end with
# end for
# end test_ab_zero
================================================= test session starts ================================================= platform win32 -- Python 3.7.1, pytest-5.2.4, py-1.7.0, pluggy-0.13.1 rootdir: C:\Users\donrc\Documents\JupyterNotebooks\PythonNotebookProject\develop plugins: hypothesis-4.44.2, arraydiff-0.3, doctestplus-0.2.0, openfiles-0.3.1, remotedata-0.3.1 collected 2 items pytestnotebook.py .. [100%] ================================================== 2 passed in 0.07s ==================================================
Run test on a single test case.
%%run_pytest -v
# test quadratic actually solves equation
def test_quadratic_solve2():
a = 1
b = 2
c = 1
x1, x2 = quadratic_solve(a, b, c)
assert x1 == -1 and x2 == -1
# end test_quadratic_solve2
================================================= test session starts =================================================
platform win32 -- Python 3.7.1, pytest-5.2.4, py-1.7.0, pluggy-0.13.1 -- D:\Anaconda3\envs\ac5-py37\python.exe
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('C:\\Users\\donrc\\Documents\\JupyterNotebooks\\PythonNotebookProject\\develop\\.hypothesis\\examples')
rootdir: C:\Users\donrc\Documents\JupyterNotebooks\PythonNotebookProject\develop
plugins: hypothesis-4.44.2, arraydiff-0.3, doctestplus-0.2.0, openfiles-0.3.1, remotedata-0.3.1
collecting ... collected 3 items
pytestnotebook.py::test_nve_discriminant PASSED [ 33%]
pytestnotebook.py::test_ab_zero PASSED [ 66%]
pytestnotebook.py::test_quadratic_solve2 PASSED [100%]
================================================== 3 passed in 0.11s ==================================================
Now run a test, chosing roots of the equation at random (with a normalized to 1).
%%run_pytest -v
def test_quadratic_solve3():
for i1 in range(1000):
n1 = random.randint(-1_000_000, 1_000_000)
n2 = random.randint(-1_000_000, 1_000_000)
a = 1
c = n1 * n2
b = n1 + n2
if b * b > 4 * a * c:
x1, x2 = quadratic_solve(a, b, c)
assert (
math.isclose(x1, -n1)
and math.isclose(x2, -n2)
) or (
math.isclose(x1, -n2)
and math.isclose(x2, -n1)
), f'{n1}, {n2} -> {x1}, {x2}'
# end if
# end for
# end test_quadratic_solve3
================================================= test session starts =================================================
platform win32 -- Python 3.7.1, pytest-5.2.4, py-1.7.0, pluggy-0.13.1 -- D:\Anaconda3\envs\ac5-py37\python.exe
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('C:\\Users\\donrc\\Documents\\JupyterNotebooks\\PythonNotebookProject\\develop\\.hypothesis\\examples')
rootdir: C:\Users\donrc\Documents\JupyterNotebooks\PythonNotebookProject\develop
plugins: hypothesis-4.44.2, arraydiff-0.3, doctestplus-0.2.0, openfiles-0.3.1, remotedata-0.3.1
collecting ... collected 4 items
pytestnotebook.py::test_nve_discriminant PASSED [ 25%]
pytestnotebook.py::test_ab_zero PASSED [ 50%]
pytestnotebook.py::test_quadratic_solve2 PASSED [ 75%]
pytestnotebook.py::test_quadratic_solve3 PASSED [100%]
================================================== 4 passed in 0.13s ==================================================
Run the test with no constraints on a.
%%run_pytest -v
def test_quadratic_solve4():
for i1 in range(1000):
n1 = random.randint(-1_000_000, 1_000_000)
n2 = random.randint(-1_000_000, 1_000_000)
n3 = random.randint(1, 1_000_000)
a = n3 * 1
c = n3 * n1 * n2
b = n3 * (n1 + n2)
if b * b > 4 * a * c:
x1, x2 = quadratic_solve(a, b, c)
assert (
math.isclose(x1, -n1)
and math.isclose(x2, -n2)
or math.isclose(x1, -n2)
and math.isclose(x2, -n1)
), f'{n1}, {n2} -> {x1}, {x2}'
# end if
# end for
# end test_quadratic_solve4
================================================= test session starts =================================================
platform win32 -- Python 3.7.1, pytest-5.2.4, py-1.7.0, pluggy-0.13.1 -- D:\Anaconda3\envs\ac5-py37\python.exe
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('C:\\Users\\donrc\\Documents\\JupyterNotebooks\\PythonNotebookProject\\develop\\.hypothesis\\examples')
rootdir: C:\Users\donrc\Documents\JupyterNotebooks\PythonNotebookProject\develop
plugins: hypothesis-4.44.2, arraydiff-0.3, doctestplus-0.2.0, openfiles-0.3.1, remotedata-0.3.1
collecting ... collected 5 items
pytestnotebook.py::test_nve_discriminant PASSED [ 20%]
pytestnotebook.py::test_ab_zero PASSED [ 40%]
pytestnotebook.py::test_quadratic_solve2 PASSED [ 60%]
pytestnotebook.py::test_quadratic_solve3 PASSED [ 80%]
pytestnotebook.py::test_quadratic_solve4 PASSED [100%]
================================================== 5 passed in 0.15s ==================================================
Test the case where a = 0 (i.e. we have a linear equation).
%%run_pytest -v
def test_quadratic_solve5():
for i1 in range(1000):
n1 = random.randint(-1_000_000, 1_000_000)
n2 = random.randint(-1_000_000, 1_000_000)
n3 = random.randint(1, 1_000_000)
a = 0
c = n2
b = n1
if b > 0:
x1, x2 = quadratic_solve(a, b, c)
assert math.isclose(
x1, -float(n2) / float(n1)
), f'{n1}, {n2} -> {x1}, {x2}'
# end if
# end for
# end test_quadratic_solve5
================================================= test session starts =================================================
platform win32 -- Python 3.7.1, pytest-5.2.4, py-1.7.0, pluggy-0.13.1 -- D:\Anaconda3\envs\ac5-py37\python.exe
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('C:\\Users\\donrc\\Documents\\JupyterNotebooks\\PythonNotebookProject\\develop\\.hypothesis\\examples')
rootdir: C:\Users\donrc\Documents\JupyterNotebooks\PythonNotebookProject\develop
plugins: hypothesis-4.44.2, arraydiff-0.3, doctestplus-0.2.0, openfiles-0.3.1, remotedata-0.3.1
collecting ... collected 6 items
pytestnotebook.py::test_nve_discriminant PASSED [ 16%]
pytestnotebook.py::test_ab_zero PASSED [ 33%]
pytestnotebook.py::test_quadratic_solve2 PASSED [ 50%]
pytestnotebook.py::test_quadratic_solve3 PASSED [ 66%]
pytestnotebook.py::test_quadratic_solve4 PASSED [ 83%]
pytestnotebook.py::test_quadratic_solve5 PASSED [100%]
================================================== 6 passed in 0.15s ==================================================
Reproducibility Details¶
%watermark --iversions
platform 1.0.8 pytest 5.2.4
%watermark
2019-12-02T14:27:40+10:00 CPython 3.7.1 IPython 7.2.0 compiler : MSC v.1915 64 bit (AMD64) system : Windows release : 10 machine : AMD64 processor : Intel64 Family 6 Model 94 Stepping 3, GenuineIntel CPU cores : 8 interpreter: 64bit
# show info to support reproducibility
theNotebook = __file__
def python_env_name():
envs = subprocess.check_output(
'conda env list'
).splitlines()
# get unicode version of binary subprocess output
envu = [x.decode('ascii') for x in envs]
active_env = list(
filter(lambda s: '*' in str(s), envu)
)[0]
env_name = str(active_env).split()[0]
return env_name
# end python_env_name
print('python version : ' + sys.version)
print('python environment :', python_env_name())
print('current wkg dir: ' + os.getcwd())
print('Notebook name: ' + theNotebook)
print(
'Notebook run at: '
+ str(datetime.datetime.now())
+ ' local time'
)
print(
'Notebook run at: '
+ str(datetime.datetime.utcnow())
+ ' UTC'
)
print('Notebook run on: ' + platform.platform())
python version : 3.7.1 (default, Dec 10 2018, 22:54:23) [MSC v.1915 64 bit (AMD64)] python environment : ac5-py37 current wkg dir: C:\Users\donrc\Documents\JupyterNotebooks\PythonNotebookProject\develop Notebook name: pytestnotebook.ipynb Notebook run at: 2019-12-02 14:27:44.703459 local time Notebook run at: 2019-12-02 04:27:44.703459 UTC Notebook run on: Windows-10-10.0.18362-SP0