Assertions#
The main purpose of assertions is to catch logical errors: we need checks that verify that our code is working correctly.
Assertions can be defined by the keyword assert
. They take an expression that evaluates to a boolean and if its value is False
, then an AssertionError
is raised. We can set a message for the raised exception by providing it in the assertion.
You can think of assertion statements assert <condition>, <message>
as equivalent to:
if not condition:
raise AssertionError(message)
Assertions are very useful for unit testing your code. A unit can be seen as the smallest piece of code that can be individually tested. In our case, this would mean testing a Python function.
It is advisable to make one or multiple test functions that test your code and run those functions every time you make a modification to the codebase (i.e., all the code you develop). This can ensure that any new modifications to the existing code do not damage the previous functionalities.
Below you can find a few examples where we have correct assertions, using the function from above and the calculate_weight
function we’ve seen before. Therefore, no exception is occurring. Note that setting a message is not mandatory, but highly recommended for debugging purposes:
def calculate_weight(density, volume):
"""
Calculates weight of an object, given its density and volume.
Args:
density (int): Density of an object.
volume (int): Volume of an object.
Returns:
int: Weight of an object.
"""
return density * volume
assert calculate_weight(30, 10) == 300
assert calculate_weight(70, 4) == 280, "The weight of an object with density of 70 kg/m^3 and volume of 4 m^3 should be 280 kg."
assert calculate_weight(12, 500) == 6000, "The weight of an object with density of 12 kg/m^3 and volume of 500 m^3 should be 6000 kg."
Below, there is an example of a failing assertion for a function converting Kelvin to Celsius. This assertion should trigger the developer into adjusting the value of the zero_point.
def kelvin_to_celsius(kelvin_temperature):
"""
Conerts [K] into [°C]
Args:
kelvin_temperature (float): Temperature in [K]
Returns:
float: Temperature in [°C].
"""
zero_point = 273
return kelvin_temperature-zero_point
assert kelvin_to_celsius(0)==-273.15, "0 Kelvin are equal to -273.15 Celsius"
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[3], line 14
11 zero_point = 273
12 return kelvin_temperature-zero_point
---> 14 assert kelvin_to_celsius(0)==-273.15, "0 Kelvin are equal to -273.15 Celsius"
AssertionError: 0 Kelvin are equal to -273.15 Celsius
When writing tests we should focus on covering “boundary” values. Those are values that are part of a condition statement. For example, in the statement if weight > 0
, the boundary value is 0.
By a rule of thumb, it is customary, to have at least 4 tests for every condition:
one that is on the boundary of condition and evaluates it to
True
(i.e. settingweight
to 1)one that is on the boundary and evaluates it to
False
(i.e. settingweight
to 0)one that is not on the boundary and evaluates to
True
(i.e. settingweight
to 100)one that is not on the boundary and evaluates to
False
(i.e. settingweight
to -70)
Moreover, you should try testing with different types as well. For example, floats or strings to observe the behaviour of your code. Finally, you could also consider testing edge cases, such as: infinity, zero, NAN, empty strings, etc.
Below you can find several examples of tests for boundary values:
Exercise#
Try to change any of the values for density and volume to cause an assertion to fail. What do you observe?
def test_calculate_weight(calculate_weight):
assert calculate_weight(0, 3) == 0, "The weight of an object with density of 0 kg/m^3 and volume of 3 m^3 should be 0 kg."
assert calculate_weight(50, 0) == 0, "The weight of an object with density of 50 kg/m^3 and volume of 0 m^3 should be 0 kg."
assert calculate_weight(0, 0) == 0, "The weight of an object with density of 0 kg/m^3 and volume of 0 m^3 should be 0 kg."
assert calculate_weight(100, 1) == 100, "The weight of an object with density of 100 kg/m^3 and volume of 1 m^3 should be 100 kg."
assert calculate_weight(45, 2) == 90, "The weight of an object with density of 45 kg/m^3 and volume of 2 m^3 should be 90 kg."
test_calculate_weight(calculate_weight)
Exercise#
You are given the method calculate_area
again to calculate the shuttering area of a concrete rectangular column. The shuttering is a temporary arrangement done for vertical surfaces to support the wet concrete till it attains the desired strength (source: Civilread.com).
Write assertions in test_calculate_area
that can verify that method calculate_area
is free of bugs. Make sure you include meaningful messages in case a test fails. Try to write tests that test the boundary values of the if
statement. For example, tests that evaluate the condition to True
and/or False
.
def calculate_area(height, length, breadth):
"""
Calculates shuttering area of a concrete rectangular column.
Args:
height (int): Height of the column.
length (int): Length of the column.
breadth (int): Breadth of the column.
Returns:
int: The area or -1 if the input is invalid.
"""
if height <= 0 or length <= 0 or breadth <= 0:
return -1
else:
return (2 * breadth + 2 * length) * height
def test_calculate_area(calculate_area):
YOUR CODE HERE
test_calculate_area(calculate_area)
Solution
# Tests, which contain values on the boundary of a condition and evaluate it to True
assert calculate_area(0, 10, 20) == -1, "Zero height should result in invalid output -1."
assert calculate_area(50, 0, 70) == -1, "Zero length should result in invalid output -1."
assert calculate_area(35, 5, 0) == -1, "Zero breadth should result in invalid output -1."
# Tests, which do not contain values on the boundary of a condition and evaluate it to True
assert calculate_area(-20, 24, 54) == -1, "Negative height should result in invalid output -1."
assert calculate_area(49, -3, 5) == -1, "Negative length should result in invalid output -1."
assert calculate_area(47, 10, -70) == -1, "Negative breadth should result in invalid output -1."
# Tests, which contain values on the boundary of a condition and evaluate it to False
assert calculate_area(1, 25, 4) == 58, "Shuttering area for column with height 1cm, length 25cm and breadth 4cm should be 58cm^2."
assert calculate_area(36, 1, 12) == 936, "Shuttering area for column with height 36cm, length 1cm and breadth 12cm should be 936cm^2."
assert calculate_area(23, 13, 1) == 644, "Shuttering area for column with height 23cm, length 13cm and breadth 1cm should be 644cm^2."
# Tests, which do not contain values on the boundary of a condition and evaluate it to False
assert calculate_area(5, 10, 20) == 300, "Shuttering area for column with height 5cm, length 10cm and breadth 20cm should be 300cm^2."
assert calculate_area(26, 17, 41) == 3016, "Shuttering area for column with height 26cm, length 17cm and breadth 41cm should be 3016cm^2."
assert calculate_area(100, 20, 50) == 14000, "Shuttering area for column with height 100cm, length 20cm and breadth 50cm should be 14000cm^2."
Asserts and floating point numbers#
Suppose that we are dealing with floating point numbers in our calculations and we would like to run test on them. Try running the code below and think whether the result makes sense:
assert 0.1 + 0.2 == 0.3
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[24], line 1
----> 1 assert 0.1 + 0.2 == 0.3
AssertionError:
The assertion above fails due to the fact that we are dealing with floating point numbers. For example, we might be using 0.3
in our calculations, but in practice, 0.30004
could be used instead due to the way floating point numbers are stored in memory. (If interested, have a look at the following link: https://0.30000000000000004.com/).
To mitigate this issue, when making assertions for floating point numbers, you can calculate the absolute value of the difference between the expected and actual values and verify that this difference is smaller than some epsilon value:
epsilon = 1e-6 # exponential notation for 0.000001
assert abs((0.1 + 0.2) - 0.3) < epsilon