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 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_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 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_true
executed 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_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.