Introduction to unittest

  • Python standard library module.
  • Sometimes referred to as PyUnit.
  • Based on the XUnit framework designed by Kent Beck and Erich Gamma.
  • Pattern repeated in many other languages including C, perl, Java, and Smalltalk.

Writing Your First Test Case

Create a file named myfirsttest.py inside your virtual environment directory. It should contain the following:

import unittest

class MyFirstTestCase(unittest.TestCase):

    def test_true_is_really_true(self):
        self.assertTrue(True)

    def not_a_test(self):
        self.assertTrue(False)

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

What Is The Test Testing?

  • We’ve defined a test case, with a single test named test_true_is_really_true.
  • All methods of a TestCase must begin with ``test`` to be considered runnable.. The not_a_test method will therefore not be executed by the test runner when we go to execute it.

Running The Test Suite

Here’s how you run the test suite:

$VENV/bin/python myfirsttest.py

Here’s the expected output of the execution of the above:

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

OK

What Just Happened?

  • The if __name__ == '__main__': stanza was true.
  • It calls unittest.main().
  • unittest.main() discovers all of the test cases it can find in the __main__ module and creates a test suite from them.
  • unittest.main() is shorthand for unittest.main('__main__').
  • The test suite is run.

Interpreting Output

  • Stray speck of dust is a dot.
  • 1 test run successfully. The test_true_is_really_true test passed.
  • Note that the not_a_test method was not executed.

Let’s Break Things

Copy the file you just created to one named badfirsttest.py. Change the method named not_a_test to test_false_is_really_true.

import unittest

class MyFirstTestCase(unittest.TestCase):

    def test_true_is_really_true(self):
        self.assertTrue(False)

    def test_false_is_really_true(self):
        self.assertTrue(False)

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

Run this test suite:

$VENV/bin/python badfirsttest.py

This should be the output:

F.
======================================================================
FAIL: test_false_is_really_true (__main__.MyFirstTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "badfirsttest.py", line 9, in test_false_is_really_true
    self.assertTrue(False)
AssertionError: False is not true

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=1)

What Happened?

  • We changed the name of the not_a_test method of the test case to test_false_is_really_true, therefore the test was found by the test runner.
  • False is not true, therefore the test failed.
  • The output has an F, then a dot as the first line. This means that the first test executed and failed, and the second test executed and passed. (Note that tests in a test case are executed in alphabetical order by default, that’s why the “second” test test_false_is_really_true executed before the “first” test test_true_is_really_true).
  • There is output describing the failure and the line the offending code is on related to the failed test. It is in the form of a Python traceback.

Let’s Break Things Again

Copy the badfirsttest.py file to a file named excfirsttest.py. Add a method to it named test_divide_by_zero.

import unittest

class MyFirstTestCase(unittest.TestCase):

    def test_true_is_really_true(self):
        self.assertTrue(True)

    def test_false_is_really_true(self):
        self.assertTrue(False)

    def test_divide_by_zero(self):
        self.assertEqual(1/0, 0)

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

Run this test suite:

$VENV/bin/python excfirsttest.py

Here’s the output:

EF.
======================================================================
ERROR: test_divide_by_zero (__main__.MyFirstTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "excfirsttest.py", line 12, in test_divide_by_zero
    self.assertEqual(1/0, 0)
ZeroDivisionError: integer division or modulo by zero

======================================================================
FAIL: test_false_is_really_true (__main__.MyFirstTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "excfirsttest.py", line 9, in test_false_is_really_true
    self.assertTrue(False)
AssertionError: False is not true

----------------------------------------------------------------------
Ran 3 tests in 0.000s

FAILED (failures=1, errors=1)

What Just Happened

  • Our original test_false_is_really_true test continues to fail.
  • A new failure for test_divide_by_zero pops up.
  • The new failure is a different kind of failure. It’s an “error”.
  • An error means that the test code could not make an assertion before the code that is under test raised an exception.

Alternate Command-Line Features of unittest

unittest can be run as a -m target too.

Copy excfirsttest.py to cmdlinefirsttest.py and change the latter’s contents to look like this:

import unittest

class MyFirstTestCase(unittest.TestCase):

    def test_true_is_really_true(self):
        self.assertTrue(True)

    def test_false_is_really_true(self):
        self.assertTrue(False)

    def test_divide_by_zero(self):
        self.assertEqual(1/0, 0)

In other words, just remove the if __name__ == "__main__" stanza.

When we try to run this file, we get no output:

$ python cmdlinefirsttest.py

Produces nothing.

We can however run it using the Python -m command line feature:

$ python -m unittest cmdlinefirsttest

We see the same output as if we had run:

$ python excfirsttest.py

Note that we dropped the .py on cmdlinefirsttest.py because unittest when invoked via -m wants a module name rather than a file name.

Running A Single Test

We can run a single test using the -m feature by naming its module, class, and method name on the command line instead of just the module:

$ python -m unittest cmdlinefirsttest.MyFirstTestCase.test_divide_by_zero

Here’s that output:

E
======================================================================
ERROR: test_divide_by_zero (cmdlinefirsttest.MyFirstTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "cmdlinefirsttest.py", line 12, in test_divide_by_zero
    self.assertEqual(1/0, 0)
ZeroDivisionError: integer division or modulo by zero

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

FAILED (errors=1)

Note that only a single test ran.

Conclusions

  • Unit tests can be run via unittest.main().
  • Unit test methods must be attached to a TestCase.
  • Unit test methods must be begin with test.
  • Unit test output consists of either a dot, a “F”, or a “E” for each test succeeded, failed, or errored.
  • Output that includes failed or errored tests contains output about the reason for the failure or error in the form of a traceback.
  • We can run unit tests by running a file they’re contained in or via python -m unittest.
  • We can run a single unit test.