Unit testing

Let's start our exploration with Python's built-in test library. This library provides a common object-oriented interface for unit tests. Unit tests focus on testing the least amount of code possible in any one test. Each one tests a single unit of the total amount of available code.

The Python library for this is called, unsurprisingly, unittest. It provides several tools for creating and running unit tests, the most important being the TestCase class. This class provides a set of methods that allow us to compare values, set up tests, and clean up when they have finished.

When we want to write a set of unit tests for a specific task, we create a subclass of TestCase and write individual methods to do the actual testing. These methods must all start with the name test. When this convention is followed, the tests automatically run as part of the test process. Normally, the tests set some values on an object and then run a method, and use the built-in comparison methods to ensure that the right results were calculated. Here's a very simple example:

import unittest


class CheckNumbers(unittest.TestCase):
def test_int_float(self):
self.assertEqual(1, 1.0)


if __name__ == "__main__":
unittest.main()

This code simply subclasses the TestCase class and adds a method that calls the TestCase.assertEqual method. This method will either succeed or raise an exception, depending on whether the two parameters are equal. If we run this code, the main function from unittest will give us the following output:

.
--------------------------------------------------------------
Ran 1 test in 0.000s

OK  

Did you know that floats and integers can be compared as equal? Let's add a failing test, as follows:

    def test_str_float(self): 
        self.assertEqual(1, "1") 

The output of this code is more sinister, as integers and strings are not
considered equal:

.F
============================================================
FAIL: test_str_float (__main__.CheckNumbers)
--------------------------------------------------------------
Traceback (most recent call last):
  File "first_unittest.py", line 9, in test_str_float
    self.assertEqual(1, "1")
AssertionError: 1 != '1'

--------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)  

The dot on the first line indicates that the first test (the one we wrote before) passed successfully; the letter F after it shows that the second test failed. Then, at the end, it gives us some informative output telling us how and where the test failed, along with a summary of the number of failures.

We can have as many test methods on one TestCase class as we like. As long as the method name begins with test, the test runner will execute each one as a separate, isolated test. Each test should be completely independent of other tests. Results or calculations from a previous test should have no impact on the current test. The key to writing good unit tests is keeping each test method as short as possible, testing a small unit of code with each test case. If our code does not seem to naturally break up into such testable units, it's probably a sign that the code needs to be redesigned.