6.3. Unit Testing

A test case expresses requirements for a program, in a way that can be checked automatically. Specifically, a test asserts something about the state of the program at a particular point in its execution. A unit test is an automatic procedure used to validate that individual units of code are working properly. A function is one form of a unit. A collection of these unit tests is called a test suite.

We have previously suggested that it’s a good idea to first write down comments about what your code is supposed to do, before actually writing the code. It is an even better idea to write down some test cases before writing a program.

There are several reasons why it’s a good habit to write test cases.

One way to implement unit tests in Python is with assert.

Take a look at the way assert is used in the following code.

In the code above, we explicitly state some natural assumptions about how truncated division might work in python. It turns out that the second asumption is wrong: 9.0//5 produces 2.0, a floating point value!

The python interpreter does not enforce restrictions about the data types of objects that can be bound to particular variables; however, type checking could alert us that something has gone wrong in our program execution. If we are assuming at that x is a list, but it’s actually an integer, then at some point later in the program execution, there will probably be an error. We can add assert statements that will cause an error to be flagged sooner rather than later, which might make it a lot easier to debug.

Check your understanding

6.3.1. assert with for loops

Why would you ever want to write a line of code that can never compute anything useful for you, but sometimes causes a runtime error? For all the reasons we described above about the value of automated tests. You want a test that will alert that you that some condition you assumed was true is not in fact true. It’s much better to be alerted to that fact right away than to have some unexpected result much later in your program execution, which you will have trouble tracing to the place where you had an error in your code.

Why doesn’t assert print out something saying that the test passed? The reason is that you don’t want to clutter up your output window with the results of automated tests that pass. You just want to know when one of your tests fails. In larger projects, other testing harnesses are used instead of assert, such as the python unittest module. Those provide some output summarizing tests that have passed as well as those that failed. In this textbook, we will just use simple assert statements for automated tests.

In the code below, lst is bound to a list object. In python, not all the elements of a list have to be of the same type. We can check that they all have the same type and get an error if they are not. Notice that with lst2, one of the assertions fails.

6.3.2. Return Value Tests

Testing whether a function returns the correct value is the easiest test case to define. You simply check whether the result of invoking the function on a particular input produces the particular output that you expect. Take a look at the following code.

Because each test checks whether a function works properly on specific inputs, the test cases will never be complete: in principle, a function might work properly on all the inputs that are tested in the test cases, but still not work properly on some other inputs. That’s where the art of defining test cases comes in: you try to find specific inputs that are representative of all the important kinds of inputs that might ever be passed to the function.

You have attempted of activities on this page