Lecture Notes 7#
Testing#
Testing frameworks are based on the assert
statement. If the expressions following assert
is
True
: go on as usualFalse
: anAssertionError
makes a program stop or can be handled by a framework (some error report)
assert True
assert False
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[2], line 1
----> 1 assert False
AssertionError:
Testing with doctest#
Example: the fat mix#
Here we refactor the original code
import sys def main(): if len(sys.argv) > 1: required_volume = float(sys.argv[1]) else: print("Usage:", sys.argv[0], "") exit() required_fat_content = 3 # in percent available_fat_content = 1.5 cream_fat_content = 40 conversion_ratio = (required_fat_content - available_fat_content)/(cream_fat_content-available_fat_content) replaced_volume = conversion_ratio * required_volume print(f'Replaced volume: {replaced_volume:5.1g}') main()
by extracting the conversion_ratio calculation to a function and introduce a test in the docstring
import sysdef get_conversion_ratio(required_fat_content, available_fat_content, cream_fat_content): """ Calculate fat conversion ration >>> get_conversion_ratio(3, 3, 40) 0.0 >>> round(get_conversion_ratio(3, 1.5, 40), 2) 0.04 """ conversion_ratio = (required_fat_content - available_fat_content)/(cream_fat_content-available_fat_content) return conversion_ratiodef main(): if len(sys.argv) > 1: required_volume = float(sys.argv[1]) else: print("Usage:", sys.argv[0], "") exit() required_fat_content = 3 # in percent available_fat_content = 1.5 cream_fat_content = 40 conversion_ratio = get_conversion_ratio(required_fat_content, available_fat_content, cream_fat_content)replaced_volume = conversion_ratio * required_volume print(f'Replaced volume: {replaced_volume:5.1g}') print(f"__name__ = {__name__}") if __name__ == "__main__": main()
The docstring contains a copy-paste of an interactive session in the REPL
The doctest library recognizes these code snippets as tests and are executed with
!python -m doctest /home/python-lll/projects/recipe/green2red.py -v
__name__ = green2red
Trying:
get_conversion_ratio(3, 3, 40)
Expecting:
0.0
ok
Trying:
round(get_conversion_ratio(3, 1.5, 40), 2)
Expecting:
0.04
ok
2 items had no tests:
green2red
green2red.main
1 items passed all tests:
2 tests in green2red.get_conversion_ratio
2 tests in 3 items.
2 passed and 0 failed.
Test passed.
What does __name__ == "__main__"
mean?#
Observe how the special variable __name__
is used
The line
print(f"__name__ = {__name__}")
shows different output depending on context:
during import (what testing libraries do)
__name__ = green2red
during normal run
__name__ = __main__
We only want to execute the full code during a normal run, therefore we have the if clause in the end
if __name__ == "__main__":
main()
Testing with pytest#
An equivalent test for the pytest
framework means to define separate test functions for these cases in a separate file. They normally contain one or sometimes more assert statements to compare calculated vs expected values
import green2red
def test_conversion_ratio_trivial():
calculated = green2red.get_conversion_ratio(3, 3, 40)
expected = 0
assert calculated == expected
def test_conversion_ratio():
calculated = green2red.get_conversion_ratio(3, 1.5, 40)
expected = 0.039
assert abs(calculated - expected) < .001
!python -m pytest /home/python-lll/projects/recipe/test_green2red.py -v
============================= test session starts ==============================
platform linux -- Python 3.11.0, pytest-7.1.2, pluggy-1.0.0 -- /home/python-lll/miniconda3/envs/bb1000/bin/python
cachedir: .pytest_cache
rootdir: /home/python-lll
plugins: anyio-3.5.0
collecting ...
collected 2 items
../../projects/recipe/test_green2red.py::test_conversion_ratio_trivial PASSED [ 50%]
../../projects/recipe/test_green2red.py::test_conversion_ratio PASSED [100%]
============================== 2 passed in 0.00s ===============================