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))  
)