A Quick Start Guide to Python unittest (Part 1)

Simplified
4 min readAug 13, 2018

--

Unit testing your code is an essential part of the software development life cycle. It also forms the basis for regression testing whenever new functionalities are added or existing functionalities are modified; unit test cases make sure that system still behaves as expected in terms of the old functionality.

In this post, I am going to demonstrate the basic idea of unit testing a single class. However, in a practical scenario, you will be writing many test cases and collect them into a test suite and run together. We will explore managing test cases in a follow up post.

Today, we are going to focus on backend-code testing. That is, a developer has implemented some specification (e.g. Calculator.py) and your task is to make sure that the developed code follows the specification (e.g. TestCalculator.py).

Let’s say that you have written a class called Calculator to do basic calculator functionality — addition, subtraction, multiplication and division.

Here’s the code for it: (Calculator.py)

#A simple calculator
class Calculator:
#empty constructor
def __init__(self):
pass
#add method - given two numbers, return the addition
def add(self, x1, x2):
return x1 + x2
#multiply method - given two numbers, return the
#multiplication of the two
def multiply(self, x1, x2):
return x1 * x2
#subtract method - given two numbers, return the value
#of first value minus the second
def subtract(self, x1, x2):
return x1 - x2
#divide method - given two numbers, return the value
#of first value divided by the second
def divide(self, x1, x2):
if x2 != 0:
return x1/x2

Now, want to unit test it so that the functionality in the above class works as expected.

Usually Python comes with package unittest installed. If it is not installed in your system, use pip to install it.

A unit test case has the following structure.

Structure of a Test Case

setUp() and tearDown() are standard methods that come with unittest framework (they are defined in unittest.TestCase class). Depending on your test case, you may or may not have overwritten these two default methods.

It’s time to look at the test case code.

Here’s the test case: (TestCalculator.py)

import unittest
from Calculator import Calculator
#Test cases to test Calulator methods
#You always create a child class derived from unittest.TestCase
class TestCalculator(unittest.TestCase):
#setUp method is overridden from the parent class TestCase
def setUp(self):
self.calculator = Calculator()
#Each test method starts with the keyword test_
def test_add(self):
self.assertEqual(self.calculator.add(4,7), 11)
def test_subtract(self):
self.assertEqual(self.calculator.subtract(10,5), 5)
def test_multiply(self):
self.assertEqual(self.calculator.multiply(3,7), 21)
def test_divide(self):
self.assertEqual(self.calculator.divide(10,2), 5)
# Executing the tests in the above test case class
if __name__ == "__main__":
unittest.main()

While it is not required as a convention, I usually create the test class with the prefix Test (in this case TestCalculator). The key requirement is that this class should be a subclass of unittest.TestCase.

Whenever this test case is executed, setUp() method gets executed first. In our case, we simply create an object of the Calculator class and save it as a class attribute. There are a few other default methods in the parent class, which we will explore later.

For now, you will do is to write test_xxx methods to test each method in the Calculator class. Notice that all test methods start with the prefix test_. This tells Python (via unittest framework) that these are test methods.

In each of the test methods, I have used a built-in method assertEqual in order to check if the Calculator methods returns the expected value. If the returned value is equal to the expected value, the test is successful, otherwise, it fails.

There are a bunch of built-in assert methods which we ill explore later.

The last line in the above code simply runs the test case TestCalculator. It execute each test method defined inside the class and gives us the result.

python TestCalculator.py -v

You should see an output similar to the following:

test_add (__main__.TestCalculator) ... ok
test_divide (__main__.TestCalculator) ... ok
test_multiply (__main__.TestCalculator) ... ok
test_subtract (__main__.TestCalculator) ... ok

--------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

What if something does not work as expected? Just for fun, let’s change the expected value of test_divide from 5 to 6 (5 is the correct value; we are changing to demonstrate a failure case. This is not a source code error — a test case development error — you can have bugs in your test cases as well — so double check your test scripts always!)

import unittest
from Calculator import Calculator
#Test cases to test Calulator methods
#You always create a child class derived from unittest.TestCase class
class TestCalculator(unittest.TestCase):
#setUp method overridden from the parent class TestCase
def setUp(self):
self.calculator = Calculator()
...
def test_divide(self):
self.assertEqual(self.calculator.divide(10,2), 6)
# Executing the tests in the above test case class
if __name__ == "__main__":
unittest.main()

When you run this test case, now you should get an output similar to the following:

test_add (__main__.TestCalculator) ... ok
test_divide (__main__.TestCalculator) ... FAIL
test_multiply (__main__.TestCalculator) ... ok
test_subtract (__main__.TestCalculator) ... ok

====================================================================
FAIL: test_divide (__main__.TestCalculator)
--------------------------------------------------------------------
Traceback (most recent call last):
File "TestCalculator.py", line 23, in test_divide
self.assertEqual(self.calculator.divide(10,2), 6)
AssertionError: 5.0 != 6

--------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=1)

It shows that 3 out of 4 test successfully completed, while one failed. In a real world scenario, assuming that your test case is correct, this will identify an incorrectly implemented functionality or a broken functionality.

--

--

Simplified

Anything related to data analysis, QA testing and requirements analysis