Achieving 100% Code Coverage with Python Unittest Library for Reliable Programs

Unit testing is an essential part of software development that helps developers ensure the correctness, reliability, and stability of their code. Python is a widely used programming language that provides several unit testing tools, with the most common being unittest. This article will introduce the Python unittest package, including how to write TestCase, execute tests, assert expected behavior, and use TestSuite. Additionally, the article will also explain the concept of test coverage and how to use unittest to calculate test coverage.

Introduction to Python Unittest

unittest is a built-in unit testing framework in Python’s standard library that helps developers write and execute unit tests. The unittest framework provides many useful features, including assertions, test cases, and test suites. The core of the unittest framework is the TestCase class, which is used to define unit tests. The TestCase class defines many test methods, and developers can inherit this class and write test methods to perform unit tests.

Here is a simple example:

import unittest

def add(a, b):
    return a + b

class TestAdd(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(0, 0), 0)

if __name__ == '__main__':
    unittest.main()

In this example, we define an add function and then write a test class called TestAdd. The TestAdd class inherits from the unittest.TestCase class and defines a test method called test_add. In this test method, we use the self.assertEqual assertion to compare the output of the add function to the expected output.

Defining Test Cases using unittest.TestCase

Writing test programs is a critical step in unit testing. A test program is a unit that tests a specific part of the code. In Python, we can define tests by inheriting from the unittest.TestCase class.

Here is a simple example:

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('hello'.upper(), 'HELLO')

    def test_isupper(self):
        self.assertTrue('HELLO'.isupper())
        self.assertFalse('Hello'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])

        with self.assertRaises(TypeError):
            s.split(2)

In this example, we define a test case called TestStringMethods and define three test methods: test_upper, test_isupper, and test_split. In these test methods, we use multiple different assertions to check whether the output of the function being tested matches the expected output.

For example, in the test_upper method, we use the self.assertEqual assertion to check whether the output of ‘hello’.upper() is equal to ‘HELLO’. In the test_isupper method, we use the self.assertTrue and self.assertFalse assertions to check whether the output of ‘HELLO’.isupper() and ‘Hello’.isupper() is True and False, respectively.

In the test_split method, we use the with self.assertRaises assertion to check whether s.split(2) raises a TypeError exception. This assertion ensures that the function being tested raises an exception for incorrect arguments, thereby ensuring the correctness of the function being tested.

Executing Tests using unittest.main()

After writing test programs, we need to execute these test cases and ensure that the output of the function being tested matches the expected output. In Python, we can execute tests by running unittest.main(). Here is a simple example:

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('hello'.upper(), 'HELLO')

    def test_isupper(self):
        self.assertTrue('HELLO'.isupper())
        self.assertFalse('Hello'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])

        with self.assertRaises(TypeError):
            s.split(2)

if __name__ == '__main__':
    unittest.main()

In this example, we execute unittest.main(), which automatically runs all the test methods in the TestStringMethods class. If we add the verbosity parameter to run this test, for example:

if __name__ == '__main__':
    unittest.main(verbosity=2)

unittest provides a detailed report that includes the name and status of each test in the report when we run tests with the verbosity parameter.

Assertions

Assertions are an important part of unit testing used to check whether the output of a function being tested matches the expected output. In Python, the unittest framework provides several assertion methods, including:

  • assertEqual: checks whether two values are equal and fails the test if they are not.
  • assertNotEqual: checks whether two values are not equal and fails the test if they are.
  • assertTrue: checks whether a value is True and fails the test if it is not.
  • assertFalse: checks whether a value is False and fails the test if it is not.
  • assertIs: checks whether two values are the same object and fails the test if they are not.
  • assertIsNot: checks whether two values are not the same object and fails the test if they are.
  • assertIn: checks whether a value is in another value and fails the test if it is not.
  • assertNotIn: checks whether a value is not in another value and fails the test if it is.
  • assertRaises: checks whether a function raises an exception and fails the test if it does not.

There are many other ways to use assertions, which can be found on this page. Here is a simple example of how to use assertions:

import unittest

def add(a, b):
    return a + b

class TestAdd(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertNotEqual(add(0, 1), 0)

    def test_add_negative(self):
        self.assertEqual(add(-1, -2), -3)
        self.assertNotEqual(add(-1, -1), 0)

if __name__ == '__main__':
    unittest.main()

In this example, we define an add function and write a test class called TestAdd. In the TestAdd class, we define two test methods, test_add and test_add_negative. In these test methods, we use different assertions to check whether the output of the add function matches the expected output.

In the test_add method, we use the self.assertEqual assertion to check whether the output of add(1, 2) is equal to 3 and whether the output of add(0, 1) is not equal to 0. In the test_add_negative method, we use the same assertion method to check whether the output of add(-1, -2) is equal to -3 and whether the output of add(-1, -1) is not equal to 0.

Using appropriate assertions to validate the output of the function being tested can help developers improve the accuracy of testing and better detect and fix errors in the code.

Using unittest.TestSuite() for Test Suites

When the number of test cases increases, we need to group these test cases and execute them in a specific order. In Python, we can create a test suite using the unittest.TestSuite class and add multiple test cases to the test suite. Here is an example code:

import unittest

def add(a, b):
    return a + b

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('hello'.upper(), 'HELLO')

    def test_isupper(self):
        self.assertTrue('HELLO'.isupper())
        self.assertFalse('Hello'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])

        with self.assertRaises(TypeError):
            s.split(2)

class TestAdd(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(0, 0), 0)

    def test_add_negative(self):
        self.assertEqual(add(-1, -2), -3)
        self.assertEqual(add(-1, 1), 0)

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(TestStringMethods))
    suite.addTest(TestAdd('test_add'))
    unittest.TextTestRunner(verbosity=2).run(suite)

In this example, we define two test case classes, TestStringMethods and TestAdd. In the main function, we create a test suite called suite and add TestStringMethods and TestAdd test cases to the suite using addTest(). This example also uses unittest.makeSuite to run all the test cases in TestStringMethods. Additionally, the suite only runs the test_add test case in TestAdd. Finally, we use the unittest.TextTestRunner class to execute the test suite, and set the verbosity=2 parameter to display a more detailed report.

Using a test suite can help group and execute multiple test cases in a specific order, making it easier to manage and run tests.

Code Coverage

Code coverage is an indicator of the quality of unit tests. It refers to the percentage of the total lines of code that have been tested in the tested code. In Python, we can use the coverage module to calculate test coverage. The coverage package can be installed using the following command:

pip install coverage

Once installed, we can use coverage to run our test suite and generate a coverage report. Here is an example code:

import unittest
import coverage

cov = coverage.Coverage()
cov.start()

def add(a, b):
    return a + b

class TestAdd(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(0, 0), 0)

    def test_add_negative(self):
        self.assertEqual(add(-1, -2), -3)
        self.assertEqual(add(-1, 1), 0)

cov.stop()
cov.save()

if __name__ == '__main__':
    cov.html_report()

In this example, we create a coverage object cov using the coverage.Coverage class, and call cov.start() to begin calculating the test coverage. In the TestAdd test class, we write two test methods, test_add and test_add_negative. After the tests finish, we call cov.stop() to stop the test coverage calculation, and then use cov.save() to save the test coverage result to a file. Finally, we use cov.html_report() to generate an HTML report that includes detailed information about the test coverage.

Using test coverage can help developers better understand the testing situation of the code under test, identify and fix untested code. Test coverage is an important indicator to improve the quality of unit testing.

Summary

In conclusion, this article introduced the unittest module in Python, including how to write test cases, execute tests, use assertions, and use test suites. Additionally, the concept of code test coverage was discussed, along with how to calculate code coverage using the coverage module. By learning and using the unittest module, developers can better ensure the correctness, reliability, and stability of their code, and improve the quality and efficiency of software development.

Xponentia
Xponentia

Hello! I'm a Quantum Computing Scientist based in Silicon Valley with a strong background in software engineering. My blog is dedicated to sharing the tools and trends I come across in my research and development work, as well as fun everyday anecdotes.

Articles: 22

Leave a Reply