Using Python's unittest Module for Unit Testing, Reporting, and CI Integration
This article introduces Python's built‑in unittest framework, explains core concepts such as TestCase, TestSuite, TestLoader, and TestRunner, demonstrates how to write test cases, generate HTML and XML reports with HTMLTestRunner and xmlrunner, and shows parameterized testing with nose‑parameterized for CI pipelines.
Python provides a built‑in unit testing framework called unittest , which includes utilities for defining test cases ( TestCase ), grouping them into suites ( TestSuite ), loading them ( TestLoader ), and running them with a test runner ( TestRunner ) that records results in a TestResult object.
A simple example demonstrates a test class inheriting from unittest.TestCase with setUp , tearDown , class‑level setUpClass / tearDownClass , and two test methods using self.assertEqual :
import unittest
class MyTest(unittest.TestCase):
def setUp(self):
print('22222')
def tearDown(self):
print('111')
@classmethod
def setUpClass(cls):
print('33333')
@classmethod
def tearDownClass(cls):
print('4444444')
def test_a_run(self):
self.assertEqual(1, 1)
def test_b_run(self):
self.assertEqual(2, 2)
if __name__ == '__main__':
unittest.main()Common assertion methods such as assertEqual , assertNotEqual , assertTrue , assertFalse , assertIsNone , assertIn , etc., are listed for reference.
To generate an HTML test report, the third‑party HTMLTestRunner module can be installed and used. The following script creates a test suite, adds a specific test, and writes the results to res.html :
import HTMLTestRunner, unittest
class MyTest(unittest.TestCase):
def setUp(self):
print(22222)
def tearDown(self):
print('111')
def test_run(self):
self.assertIs(1, 1)
# additional test methods ...
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(MyTest('test_run'))
fp = open('res.html', 'wb')
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='API Test Report', description='Test Details')
runner.run(suite)When many modules contain numerous test files, unittest.defaultTestLoader.discover can automatically locate all files matching a pattern (e.g., test_*.py ) and add their cases to a suite:
import unittest, HTMLTestRunner
suite = unittest.TestSuite()
all_cases = unittest.defaultTestLoader.discover('.', pattern='test_*.py')
for case in all_cases:
suite.addTests(case)
fp = open('res.html', 'wb')
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='All Tests', description='Combined Test Results')
runner.run(suite)For continuous integration with Jenkins, an XML report is required. The xmlrunner package produces such reports, which Jenkins can consume:
import unittest, xmlrunner
class My(unittest.TestCase):
def test1(self, a, b, c):
self.assertEqual(a + b, c)
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(My))
runner = xmlrunner.XMLTestRunner(output='report')
runner.run(suite)Parameterized testing avoids writing many similar test methods. The third‑party nose‑parameterized module allows a list of argument tuples to be expanded into separate test runs:
pip install nose-parameterized import unittest
from nose_parameterized import parameterized
class My(unittest.TestCase):
@parameterized.expand([
(1, 2, 3),
(1, 2, 3),
(1, 2, 3),
(1, 2, 4)
])
def test1(self, a, b, c):
self.assertEqual(a + b, c)
if __name__ == '__main__':
unittest.main()The article concludes with screenshots of the generated HTML and XML reports and a QR code for sharing.
Test Development Learning Exchange
Test Development Learning Exchange
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.