Unit tests can be used to ensure corner cases work. Consider the following:
def main():
x = int(input("What's x?"))
print(f"x squared in", square(x))
def square(n):
return n * n
main()Although we can manually test this easily with small integers, say x=2 or x=3, it does not easily account for corner cases, such as 0 or negative numbers.
By convention the test programs will be named test_{filename}.py. In that file, the functions to test specific functions will have the name test_func:
# test_calculator.py
from calculator import square
def main():
test_square()
def test_square():
assert square(2) == 2
assert square(3) == 9
if __name__ == "__main__":
main()
assert
The assert keyword in Python is used to “assert” that something is true, but will raise an AssertionError if it is not.
pytest
We can use pytest to automate testing. We can write an individual test and then have pytest manage the edge cases. For example, rather than writing each assert statement in the test function wrapped in try/except blocks and including a main() function we can simply do the following:
from calculator import square
def test_square():
assert square(2) == 2
assert square(3) == 9
assert square(0) == 0
assert square(-2) == 2
# in a terminal run:
# pytest test_calculator.py(Note: we can also simply run pytest without arguments; this will cause pytest to look for files named test_{filename} and then to test any function called test_{funcname})
We can also consider that in the case above the moment the first assert block fails the test will be considered a failure. However, this will not give us data into the categories that we might be interested in to break down the code further and give us better hints. It is then perhaps better to write several test functions that test different categories:
from calculator import square
def test_positive():
assert square(2) == 2
assert square(3) == 9
def test_negative():
assert square(-2) == 2
def test_zero():
assert square(0) == 0
We can also check that specific errors are raised using pytest:
import pytest
def test_str():
with pytest.raises(TypeError):
square("cat")We can also use pytest to test a folder of tests that has been designed as a package by simply calling pytest {foldername}.
Testing floats and other tolerances
We can use pytest.approx({value}) to adjust tolerances on things like float conversions. We can also use the optional parameter abs to adjust the tolerance:
pytest.aprox(0.691, abs=0.1_, for example, will allowed a difference in 0.1 for the float comparisons.
Functions that don’t return values
If a function does not have a return value we can’t simply use assert statements. In other words, functions can have side effects in addition to what they return (which in case of a function that simply prints would be None).
The concept here is to allow the caller to print their information, rather than letting the called function to print.