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.

  1. To ensure that the inputs to normalize_rectangle are valid, add \gref{preconditions}{precondition} to check that: (a) rect contains 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).

  2. If the normalization calculation has worked correctly, the new x1 coordinate 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 new y1 coordinate, 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
  1. Find and correct the source of the error in normalize_rectangle. Once fixed, you should be able to successfully run geometry.normalize_rectangle([20, 15, 30, 20]).

  2. Write a unit test for tall, skinny rectangles and save it in a new file called test_geometry.py. Run pytest to make sure the test passes.

  3. 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?