4 Testing
Doctest
Doctests looks at examples in the docstring and tests them. To run it we add this to the bottom of our python file.
if __name__ == '__main__' :
import doctest
doctest.testmod()
Pytest
The pytest
library allows us to create a test suite in another file so we can include more tests without cluttering code. Tests use the assert <expression>
statement to verify the correctness where the <expression>
should be a boolean.
Example:
from insert import insert_after
def test_insert_after_at_front() -> None:
"""Test insert_after with one occurrence of n1 at the front of lst.
"""
input_list = [0, 1, 2, 3]
insert_after(input_list, 0, 99)
expected = [0, 99, 1, 2, 3]
assert input_list == expected
def test_insert_after_at_back() -> None:
"""Test insert_after with one occurrence of n1 at the end of lst.
"""
input_list = [0, 1, 2, 3]
insert_after(input_list, 3, 99)
expected = [0, 1, 2, 3, 99]
assert input_list == expected
def test_insert_after_middle() -> None:
"""Test insert_after with one occurrence of n1 in the middle of lst.
"""
input_list = [0, 1, 2, 3]
insert_after(input_list, 1, 99)
expected = [0, 1, 99, 2, 3]
assert input_list == expected
if __name__ == '__main__':
import pytest
pytest.main(['test_insert.py'])
Test Cases
Should test around the properties of the inputs and the relationships between inputs:
Size - empty, smallest, several
Boundary - near thresholds
Dichotomy - opposite, ex: empty or full, pos or neg
Order - sorted, unsorted etc.
Property-based testing
Property based testing uses a library like hypothesis
to programmatically generate a large set of possible inputs to test. The trade off is that it is difficult to specify the corresponding outputs. assert
statements will check for properties like should output:
A certain type
Allowed values of the output
Relationships between input and output
Example
def insert_after(lst: list[int], n1: int, n2: int)-> None:
"""After each occurence of <n1> in <lst>, insert <n2>.
>>> lst = [5, 1, 2, 1, 6]
>>> insert_after(lst, 1, 99)
>>> lst
[5, 1, 99, 2, 1, 99, 6]
"""
So must always return None
and length of lst
should increase by the number of times that n1
occurs.
from hypothesis import given
from hypothesis.strategies import integers, lists
from insert import insert_after
# tell hypothesis to generate random inputs
@given(lists(integers()), integers(), integers())
def test_returns_none(lst: list[int], n1: int, n2: int) -> None:
"""Test that insert_after always returns None."""
assert insert_after(lst, n1, n2) is None
@given(lists(integers()), integers(), integers())
def test_new_item_count(lst: list[int], n1: int, n2: int) -> None:
"""Test that the correct number of items is added."""
num_n1_occurrences = lst.count(n1)
original_length = len(lst)
insert_after(lst, n1, n2)
final_length = len(lst)
assert final_length - original_length == num_n1_occurrences
from hypothesis import given, strategies as st
@given(
src_number=st.text(min_size=3, max_size=10), # Simulating phone numbers
dst_number=st.text(min_size=3, max_size=10),
call_time=st.datetimes(min_value=datetime.datetime(2020, 1, 1)),
duration=st.integers(min_value=1, max_value=3600), # 1 sec to 1 hour
src_loc=st.tuples(st.floats(min_value=-180, max_value=180), st.floats(min_value=-90, max_value=90)),
dst_loc=st.tuples(st.floats(min_value=-180, max_value=180), st.floats(min_value=-90, max_value=90))
)