Exercises - Testing#
1) Explaining assertions#
Given a list of numbers,
the function total returns the total:
total([1, 2, 3, 4])
10
The function only works on numbers:
total(['a', 'b', 'c'])
ValueError: invalid literal for int() with base 10: 'a'
Explain in words what the assertions in this function check, and for each one, give an example of input that will make that assertion fail.
def total(values):
assert len(values) > 0
for element in values:
assert int(element)
values = [int(element) for element in values]
total = sum(values)
assert total > 0
return total
2) Rectangle normalization#
A rectangle can be described using a tuple of four cartesian coordinates (x0, y0, x1, y1),
where (x0, y0) represents the lower left corner and (x1, y1) the upper right.
In order to do some calculations,
suppose we need to be able to normalize rectangles so that the lower left corner
is at the origin (i.e., (x0, y0) = (0, 0)) and the longest side is 1.0 units long.
This function does that:
def normalize_rectangle(rect):
"""Normalizes a rectangle so that it is at the origin
and 1.0 units long on its longest axis. Input should be
(x0, y0, x1, y1), where (x0, y0) and (x1, y1) define the
lower left and upper right corners of the rectangle."""
# insert preconditions
x0, y0, x1, y1 = rect
# insert preconditions
dx = x1 - x0
dy = y1 - y0
if dx > dy:
scaled = float(dx) / dy
upper_x, upper_y = 1.0, scaled
else:
scaled = float(dx) / dy
upper_x, upper_y = scaled, 1.0
# insert postconditions here
return (0, 0, upper_x, upper_y)
In order to answer the following questions,
cut and paste the normalize_rectangle function into a new file called geometry.py
(outside of your zipf project) and
save that file in a new directory called exercises.
To ensure that the inputs to
normalize_rectangleare valid, add \gref{preconditions}{precondition} to check that: (a)rectcontains 4 coordinates, (b) the width of the rectangle is a positive, non-zero value (i.e.,x0 < x1), and (c) the height of the rectangle is a positive, non-zero value (i.e.,y0 < y1).If the normalization calculation has worked correctly, the new
x1coordinate will lie between 0 and 1 (i.e.,0 < upper_x <= 1.0). Add a \gref{postcondition}{postcondition} to check that this is true. Do the same for the newy1coordinate,upper_y.
Running normalize_rectangle for a short, wide rectangle should pass your new
preconditions and postconditions:
import geometry
geometry.normalize_rectangle([2, 5, 3, 10])
(0, 0, 0.2, 1.0)
but will fail for a tall, skinny rectangle:
geometry.normalize_rectangle([20, 15, 30, 20])
AssertionError Traceback (most recent call last)
<ipython-input-3-f4e8cdf7f69d> in <module>
----> 1 geometry.normalize_rectangle([20, 15, 30, 20])
~/Desktop/exercises/geometry.py in normalize_rectangle(rect)
19
20 assert 0 < upper_x <= 1.0, \
21 'Calculated upper X coordinate invalid'
---> 22 assert 0 < upper_y <= 1.0, \
23 'Calculated upper Y coordinate invalid'
24
25 return (0, 0, upper_x, upper_y)
AssertionError: Calculated upper Y coordinate invalid
Find and correct the source of the error in
normalize_rectangle. Once fixed, you should be able to successfully rungeometry.normalize_rectangle([20, 15, 30, 20]).Write a unit test for tall, skinny rectangles and save it in a new file called
test_geometry.py. Runpytestto make sure the test passes.Add a couple more unit tests to
test_geometry.py. Explain the rationale behind each test.
3) Testing with randomness#
Programs that rely on random numbers are impossible to test because there’s (deliberately) no way to predict their output. Luckily, computer programs don’t actually use random numbers: they use a pseudo-random number generator (PRNG) that produces values in a repeatable but unpredictable way. Given the same initial seed, a PRNG will always produce the same sequence of values.
How can we use this fact when testing programs that rely on pseudo-random numbers?
4) Testing with relative error#
If E is the expected result of a function and A is the actual value it produces, the relative error is abs((A-E)/E).
This means that if we expect the results of tests to be 2, 1, and 0, and we actually get 2.1, 1.1, and 0.1 the relative errors are 5%, 10%, and infinity.
Why does this seem counter-intuitive, and what might be a better way to measure error in this case?