Unit Testing in Python Using unittest Module
The goal of this article is to provide a quick introduction to Python’s unit testing module called unittest. It is the essence, the very basic information you need to quickly start unit testing in Python.
Introduction to the unittest Module
Key points about unit testing in Python:
- modules with tests should import
unittest
module, - tests should be defined inside a class extending the
unittest.TestCase
class, - every test should start with the
test
word, - if a test has a doc. comment (between a pair of
'''
, i. e. three apostrophes), the comment will be printed when the test is being run (if verbose mode was set), - tests can have
setUp
andtearDown
methods – those methods will be called, respectively, before and after each of the tests; there are also class-level set up and tear down methods, - to execute the tests when the module is run,
unittest.main()
should be called, - to see which tests are called with additional info, the
-v
(verbose) parameter should be specified when the module with tests is executed.
Example:
# file test_is_odd.py import unittest def is_odd(param): return param % 2 == 1 class TestOdd(unittest.TestCase): def test_is_odd(self): ''' 1 should be classified as odd ''' self.assertTrue(is_odd(1)) def test_is_not_odd(self): ''' 2 should not be classified as odd ''' self.assertFalse(is_odd(2)) if __name__ == '__main__': unittest.main()
Running the module:
Output:
test_is_not_odd (__main__.TestOdd) 2 should not be classified as odd ... ok test_is_odd (__main__.TestOdd) 1 should be classified as odd ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK
Running tests can be stopped after the first test fails using the -f
argument:
# file test_fail_fast.py import unittest class TestMul(unittest.TestCase): def test_first(self): ''' First failing test ''' self.assertTrue(False) def test_second(self): ''' Second failing test ''' self.assertTrue(False) if __name__ == '__main__': unittest.main()
Command:
Output:
First failing test ... FAIL ====================================================================== FAIL: test_first (__main__.TestMul) First failing test ---------------------------------------------------------------------- Traceback (most recent call last): File "test_fail_fast.py", line 7, in test_first self.assertTrue(False) AssertionError: False is not true ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (failures=1)
Success, Failure & Error
Tests can have one of three possible outcomes:
- Success – no error occurred during the test and none of the assertions failed.
- Failure – no error occurred during the test and one of the assertions failed.
- Error – an error occurred during the execution of the test (for example, an unexpected exception was thrown).
Example:
# file test_s_f_e.py import unittest class TestSFE(unittest.TestCase): def test_first(self): ''' Test that passes ''' self.assertTrue(True) def test_second(self): ''' Test that fails ''' self.assertTrue(False) def test_third(self): ''' Test during which an error occurs ''' raise Exception("Test error.") if __name__ == '__main__': unittest.main()
Output:
test_first (__main__.TestSFE) Test that passes ... ok test_second (__main__.TestSFE) Test that fails ... FAIL test_third (__main__.TestSFE) Test during which an error occurs ... ERROR ====================================================================== ERROR: test_third (__main__.TestSFE) Test during which an error occurs ---------------------------------------------------------------------- Traceback (most recent call last): File "test_s_f_e.py", line 15, in test_third raise Exception("Test error.") Exception: Test error. ====================================================================== FAIL: test_second (__main__.TestSFE) Test that fails ---------------------------------------------------------------------- Traceback (most recent call last): File "test_s_f_e.py", line 11, in test_second self.assertTrue(False) AssertionError: False is not true ---------------------------------------------------------------------- Ran 3 tests in 0.001s FAILED (failures=1, errors=1)
Fixtures
A fixture is a preparation and cleanup of the context and data for unit tests environment.
The preparation part is done by the setUp
method, which, if present, is called before each of the tests. If it fails for any reason, the test will not be run.
The cleanup is done in the tearDown
method. It is run regardless of whether the test succeeded or not.
There are also set up and tear down methods for the whole classes containing tests:
setUpClass
– executed before all tests,tearDownClass
– executed after all tests.
Both must be defined with the @classmethod
adnotation. Example:
# file test_setup.py import unittest def is_odd(param): return param % 2 == 1 class TestSetup(unittest.TestCase): @classmethod def setUpClass(cls): print("Setting up the class.") @classmethod def tearDownClass(cls): print("Tearing down the class.") def setUp(self): print("Setting up the test.") def tearDown(self): print("Tearing down the test.") def test_is_odd(self): ''' 1 should be classified as odd ''' self.assertTrue(is_odd(1)) def test_is_not_odd(self): ''' 2 should not be classified as odd ''' self.assertFalse(is_odd(2)) if __name__ == '__main__': unittest.main()
Output:
Setting up the class. test_is_not_odd (__main__.TestSetup) 2 should not be classified as odd ... Setting up the test. Tearing down the test. ok test_is_odd (__main__.TestSetup) 1 should be classified as odd ... Setting up the test. Tearing down the test. ok Tearing down the class. ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK
Test Discovery
Test discovery can be used to run all tests in a given directory. Modules’ file names should start with test
. If there was another test file in the directory, for example:
# file test_mul.py import unittest def mul(param1, param2): return param1 * param2 class TestMul(unittest.TestCase): def test_mul_by_0(self): ''' 1 * 0 should be 0 ''' self.assertEqual(mul(1, 0), 0) def test_square(self): ''' 2 * 2 should be 4 ''' self.assertEqual(mul(2, 2), 4) if __name__ == '__main__': unittest.main()
Tests from both files could be run using the following command (-v
parameter for verbose mode):
Output:
test_is_not_odd (test_is_odd.TestOdd) 2 should not be classified as odd ... ok test_is_odd (test_is_odd.TestOdd) 1 should be classified as odd ... ok test_mul_by_0 (test_mul.TestMul) 1 * 0 should be 0 ... ok test_square (test_mul.TestMul) 2 * 2 should be 4 ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.002s OK
Additional parameter can be specified when using the above command – starting location and a pattern to match files with tests, for example:
python -m unittest discover -v . „*.py”
Assertion methods
Most of the assert*
methods accept an optional msg
argument. The message it contains is printed when the test fails and may serve as a source of additional information about the failure.
Source of information about assertion methods: Python’s Documentation – unittest module.
Basic assertion methods
Method | Checks if |
---|---|
assertEqual(a, b) | a == b |
assertNotEqual(a, b) | a != b |
assertTrue(x) | bool(x) is True |
assertFalse(x) | bool(x) is False |
assertIs(a, b) | a is b |
assertIsNot(a, b) | a is not b |
assertIsNone(x) | x is None |
assertIsNotNone(x) | x is not None |
assertIn(a, b) | a in b |
assertNotIn(a, b) | a not in b |
assertIsInstance(a, b) | isinstance(a, b) |
assertNotIsInstance(a, b) | not isinstance(a, b) |
Note: assertEqual
automatically uses one of the assertion methods defined specifically for some of the types, for example, strings, lists, sets etc. – it is not required to call them directly.
Additional methods
There are additional assertion methods for checking, for example, if two floating-point numbers are equal when rounded to specified number of decimal places (7 is the default).
Method | Checks if |
---|---|
assertAlmostEqual(a, b) | round(a-b, 7) == 0 |
assertNotAlmostEqual(a, b) | round(a-b, 7) != 0 |
assertGreater(a, b) | a > b |
assertGreaterEqual(a, b) | a >= b |
assertLess(a, b) | a < b |
assertLessEqual(a, b) | a <= b |
assertRegex(s, r) | r.search(s) |
assertNotRegex(s, r) | not r.search(s) |
assertCountEqual(a, b) | a and b have the same elements in the same number, regardless of their order |
Expecting exceptions
It is also possible to assert that an exception or a warning will be thrown. The *Regex
versions allow to validate the message of the exception against the given regular expression.
Method | Checks if |
---|---|
assertRaises(exc, fun, *args, **kwds) | fun(*args, **kwds) raises exc |
assertRaisesRegex(exc, r, fun, *args, **kwds) | fun(*args, **kwds) raises exc and the message matches regex r |
assertWarns(warn, fun, *args, **kwds) | fun(*args, **kwds) raises warn |
assertWarnsRegex(warn, r, fun, *args, **kwds) | fun(*args, **kwds) raises warn and the message matches regex r |
In the below example, we’re passing a character to check if it is an odd number. The first test expects the ValueError exception to be thrown. The second one also checks if the message of the exception is in the expected format:
# file test_assert.py import unittest def is_odd(param): try: return int(param) % 2 == 1 except ValueError: raise ValueError("'{0}' is not a number!".format(param)) class TestRaises(unittest.TestCase): def test_raises_value_error(self): ''' "a" should cause a ValueError exception to be thrown ''' self.assertRaises(ValueError, is_odd, "a") def test_raises_value_error_regex(self): ''' "a" should cause a ValueError exception to be thrown with the specific message ''' self.assertRaisesRegex(ValueError, "'.*' is not a number!", is_odd, "a") if __name__ == '__main__': unittest.main()
Output:
test_raises_value_error (__main__.TestRaises) "a" should cause a ValueError exception to be thrown ... ok test_raises_value_error_regex (__main__.TestRaises) "a" should cause a ValueError exception to ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK
Further Reading & Useful Links
That was a quick look at the unit testing in Python. I’ve barely scratched the surface here. There are many other modules that help and enhance unit testing in Python.
Below you’ll find links to some useful resources on the Internet about topics covered in this article:
I hope you enjoyed my article. If you have found any errors in it (even typos), you think that I haven’t explained anything clearly enough or you have an idea how I could make the article better – please, do not hesitate to contact me, or leave a comment.