Questions
General
- How do you create a test function? What identifies the function as a test?
- How do you create a file to write all your unit tests?
- What does the assert statement do?
- Explain the difference between a use case and an edge case. Give an example of both within a function.
- If a function passes all of its associated unit tests, then the function is implemented correctly for all possible inputs (True/False).
Unit Test Writing and Classifying
- Suppose you have the following function, designed to return the index of the first even number in a list.
def find_even(nums: list[int]) -> int:
idx: int = 0
while idx < len(nums):
if nums[idx] % 2 == 0:
return idx
idx += 1
return -1Fill in this unit test with a use case.
def test_find_even_use_case() -> None:
"""Put code here."""- Suppose you have the following function, designed to calculate the sum of the elements in a list.
def sum_numbers(numbers: list[int]) -> int:
if len(numbers) == 0:
raise Exception("Empty list - no elements to add")
total: int = numbers[0]
for i in range(1, len(numbers)):
total += numbers[i]
return totalFill in this unit test with a use case.
def test_list_sum_use_case() -> None:
"""Put code here."""- Suppose you have the following function, designed to determine if a number is prime.
def is_prime(n: int) -> bool:
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return TrueFill in this unit test with a use case.
def test_is_prime_use_case() -> None:
"""Put code here."""- Suppose you want to test that a list of dictionaries will be mutated correctly. Here’s a function that mutates a list of dictionaries by adding a new key-value pair to each dictionary in the list.
def add_key_to_dicts(dicts: list[dict], key: str, value: int) -> None:
for d in dicts:
d[key] = valueFill in this unit test with a use case to verify that the list of dictionaries is mutated correctly.
def test_add_key_to_dicts_use_case() -> None:
"""Put code here."""- Suppose you want to test that a dictionary will be mutated correctly. Here’s a function that mutates a dictionary by incrementing the value of a given key by 1.
def increment_dict_value(d: dict[str, int], key: str) -> None:
if key in d:
d[key] += 1
else:
d[key] = 1Fill in this unit test with a use case to verify that the dictionary is mutated correctly.
def test_increment_dict_value_use_case() -> None:
"""Put code here."""- Suppose you have the following function, designed to sum the elements in a dictionary of list values and return the key with the largest summed value.
def max_sum_dict(d: dict[str, list[int]]) -> str:
keys = []
for key in d:
keys.append(key)
values_list_1 = d[keys[0]]
values_list_2 = d[keys[1]]
total_1 = 0
for value in values_list_1:
total_1 += value
total_2 = 0
for value in values_list_2:
total_2 += value
if total_1 > total_2:
return keys[0]
else:
return keys[1]Fill in this unit test with a use case to verify that the function returns the key with the largest summed value.
def test_max_sum_dict_use_case() -> None:
"""Put code here."""- Write three unit tests for the following function, two testing use cases and one testing an edge case.
def divide_list(input_list: list[int], divisor: int) -> list[float]:
"""Returns a new list where each value is the value from input_list divided by divisor"""
result: list[int] = []
for num in input_list:
result.append(num / divisor)
return result- Consider the following code snippet:
def fill_list(num: int, length: int) -> list[int]:
"""Fill a list with a single value."""
result: list[int] = []
i: int = 0
while i < length:
result.append[num]
i += 1
return result
list_A: list[int] = fill_list(4, 19)
list_B: list[int] = fill_list(55, -2)
list_C: list[int] = fill_list(1, 110)Which function calls would be considered a use case of this function (list the associated variable name e.g. list_A)? Which would be considered edge cases? If there are any edge cases, what result would you get in the associated variable(s)?
Solutions
Conceptual Solutions
A test function is created just like any other Python function, but it is identified as a test by starting its name with
test_. In frameworks like pytest, any function that starts withtest_is automatically detected and run as a test.Create a new Python file, often named
<module_name>_test.py, in the same directory as your module. Write all your test functions in this file.The assert statement checks if a condition is true. If the condition is false, an AssertionError is raised, indicating that the test has failed.
A use case is a typical scenario where the function is expected to work as intended. For example, in a function that sums a list, a use case would be passing a list like
[1, 2, 3]. An edge case is a situation where the function might struggle or behave differently, like passing an empty list[]to a sum function.This is False, as unit tests themselves can be incorrect so all tests passing is no guarantee of correctness even for the inputs the unit tests are testing for. Even if the unit tests are correct, there can still be certain inputs that they do not test for and therefore the unit tests cannot assure you that a function will always work properly. Unit tests are a helpful tool that can work well when implemented over a wide range of test inputs, but they must be accompanied by thoughtful implementation of the original function.
Unit Test Writing
- Solution below:
def test_find_even_use_case() -> None:
nums = [1, 3, 5, 4, 7]
assert find_even(nums) == 3- Solution below:
def test_list_sum_use_case() -> None:
# Test case 1: Normal list of positive numbers
assert sum_numbers([1, 2, 3, 4, 5]) == 15
# Test case 2: List with negative numbers
assert sum_numbers([-1, -2, -3, -4, -5]) == -15
# Test case 3: Mixed positive and negative numbers
assert sum_numbers([1, -1, 2, -2, 3, -3]) == 0
# Test case 4: List with a single element
assert sum_numbers([10]) == 10
# Do not worry about handling the exception!
# That is out of the scope of the class :)- Solution below:
def test_is_prime_use_case() -> None:
assert is_prime(7) is True
assert is_prime(8) is False- Solution below:
def test_add_key_to_dicts_use_case() -> None:
dicts = [{"a": 1}, {"b": 2}]
add_key_to_dicts(dicts, "c", 3)
assert dicts == [{"a": 1, "c": 3}, {"b": 2, "c": 3}]- Solution below:
def test_increment_dict_value_use_case() -> None:
d = {"a": 1, "b": 2}
increment_dict_value(d, "a")
assert d["a"] == 2
increment_dict_value(d, "c")
assert d["c"] == 1- Solution below:
def test_max_sum_dict_use_case() -> None:
d = {"a": [1, 2, 3], "b": [4, 5]}
assert max_sum_dict(d) == "b"list_Aandlist_Cwould be use cases since this is how we would expect this function to be used andlist_Bwould be an edge case as this is essentially attempting to make a function call that would construct a list of negative length since ourlengthargument is -2. In this edge case the result would be an empty list since we would never enter thewhileloop.Note: These are just some examples of what you could test for, but they will likely not be the same as what you wrote as there are many correct answers.
The most straightforward use case test would be ensuring that on a normal input that the output is what you expect:
def test_normal_divide_list() -> None: classes: list[int] = [110, 210, 301, 455] assert divide_list(classes, 10) = [11.0, 21.0, 30.1, 45.5]Another unit test for an edge case might be to ensure that the original list was not mutated:
def test_no_mutate_divide_list() -> None: classes: list[int] = [110, 210, 301, 455] divide_list(classes, 10) # We don't need to store the result assert classes = [110, 210, 301, 455]Finally, an example of an edge case for this function would be a divisor of zero, which we should expect to result in an error. We can test to ensure that an error occurs like this:
def test_div_zero_error_divide_list() -> None: classes: list[int] = [110, 210, 301, 455] with pytest.raises(ZeroDivisionError): divide_list(classes, 0)