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_testmethod 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 forunittest.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_truetest passed. - Note that the
not_a_testmethod 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_testmethod of the test case totest_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” testtest_false_is_really_trueexecuted before the “first” testtest_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_truetest continues to fail. - A new failure for
test_divide_by_zeropops 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.