Feb 28th, 2020 - written by Kimserey with .
Unit tests ensure that the functionalities of the Software are maintained while developping new features. Every language provides library for writing unit tests, and any decent framework provides facilities to enable easy unit testing. In Python the core comes packed with unittest
, a module providing all the necessary functuonalities to unit test our application. In today’s post we will look at how we can setup unittest
for our Python application.
unittest
In order to create unit tests, we start first by creating a tests module.
1
2
3
/tests
__init__.py
__main__.py
And add the following in __main__.py
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"""Main"""
import os
import unittest
def suite():
"""Discover test suite."""
return unittest.defaultTestLoader.discover(
os.path.dirname(os.path.realpath(__file__))
)
def run():
"""Run the test suite."""
runner = unittest.TextTestRunner(verbosity=2)
test_result = runner.run(suite())
if test_result.errors or test_result.failures:
raise RuntimeError("There are test failures")
if __name__ == "__main__":
run()
The defaultTestLoader
allows us to discover the tests available in the module. Any test file matching the pattern test*
will be scanned.
We then use the TextTestRunner
to run our tests in the shell with a text output.
We can then run our tests with the following command:
1
2
3
4
5
6
$> python3 -m tests
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Having a tests
module setup this way will avoid us having to explicitely specify modules to be tested by unittest
. If we wanted to do so, we could have use directly unittest
:
1
python3 -m unittest tests.test_user
Or we could have done the following if we imported all our test cases in __init__.py
:
1
python3 -m unittest tests
Regardless, the tests
module provides us a easy way to discover test cases and run them. We now have a test runner working properly. The next step is to actually build test cases.
Test cases are created by inheriting from TestCase
class.
1
from unittest import TestCase
We start by creating a test case file test_user.py
with the following content:
1
2
3
4
5
6
7
8
from unittest import TestCase
class TestUser(TestCase):
"""User Test Case"""
def test_user_can_login(self):
"""Test that the user can login."""
self.assertTrue(True)
and each test is created by creating a function with a prefix test_
.
1
2
3
4
5
6
7
8
9
$> py -m tests
test_user_can_login (test_user.TestUser)
Test that the user can login. ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
When an assertion fails, a Failure
will be reported while if an exception occurs an Error
will be reported.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$> py -m tests
test_failure (test_user.TestUser)
Example of test failure. ... FAIL
test_user_can_login (test_user.TestUser)
Test that the user can login. ... ok
======================================================================
FAIL: test_failure (test_user.TestUser)
Example of test failure.
----------------------------------------------------------------------
Traceback (most recent call last):
File, line 12, in test_failure
self.assertEqual(1, 2)
AssertionError: 1 != 2
We also have access to decorators like skip
and expectedFailure
.
@unittest.skip("reason")
will skip the current test and @unittest.expectedFailure
will expect the test to fail. If the tests passes it will report a failure.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from unittest import TestCase, expectedFailure, skip
class TestUser(TestCase):
"""User Test Case"""
@skip("Example of skip")
def test_user_can_login(self):
"""Test that the user can login."""
self.assertTrue(True)
@expectedFailure
def test_failure(self):
"""Example of test failure."""
self.assertEqual(1, 2)
And the text runner will show us that we skipped one test and expected one failure successfully.
1
2
3
4
5
6
7
8
9
10
11
$> py -m tests
test_failure (test_user.TestUser)
Example of test failure. ... expected failure
test_user_can_login (test_user.TestUser)
Test that the user can login. ... skipped 'Example of skip'
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK (skipped=1, expected failures=1)
There are times where mutiple assertions are written in a test. In those cases we can make the assertions appear as different tests on the runner output using the subTest()
function.
1
2
3
4
5
6
7
8
9
10
11
12
from unittest import TestCase, expectedFailure, skip
class TestUser(TestCase):
"""User Test Case"""
def test_user_can_login(self):
"""Test that the user can login."""
self.assertTrue(True)
for i in range(0, 3):
with self.subTest(i=i):
self.assertEqual(i, 1)
This will allow us to have a separated error showed for every subtest:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$> py -m tests
test_user_can_login (test_user.TestUser)
Test that the user can login. ...
======================================================================
FAIL: test_user_can_login (test_user.TestUser) (i=0)
Test that the user can login.
----------------------------------------------------------------------
Traceback (most recent call last):
File, line 12, in test_user_can_login
self.assertEqual(i, 1)
AssertionError: 0 != 1
======================================================================
FAIL: test_user_can_login (test_user.TestUser) (i=2)
Test that the user can login.
----------------------------------------------------------------------
Traceback (most recent call last):
File, line 12, in test_user_can_login
self.assertEqual(i, 1)
AssertionError: 2 != 1
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=2)
We can see that the test reported the error for the separate value used 0
and 2
.
unittest
comes packed with a set of assertions. Here’s a list of the common assertions:
assertEqual
assertTrue
assertRaises
assertIs
assertIn
assertNone
assertIsInstance
Their negation counter part also exists, for example assertNotEqual
or assertFalse
.
There are also assertions used to check exceptions warnings and log messages:
assertRaises
assertRaisesRegex
assertWarns
assertWarnsRegex
assertLogs
And lastly assertion checking specific comparisons:
assertAlmostEqual
assertGreater
assertGreaterEqual
assertLess
assertLessEqual
assertRegex
assertCountEqual
The full list with the signature of each assertion is available on the official documentation. And that concludes today’s post!
Today we looked into unittest
, a module included in the Python core allowing us to perform unit tests. We started by looking at how we could set it up then we looked at how writing test cases looked like and we completed this post by looking at the build in assertions available. I hope youike this post and I see you on the next one!