Course
What is Unit Testing?
Unit testing is a software testing method by which individual units of source code are put under various tests to determine whether they are fit for use. It determines and ascertains the quality of your code.
Generally, when the development process is complete, the developer codes criteria, or the results that are known to be potentially practical and useful, into the test script to verify a particular unit's correctness. During test case execution, various frameworks log tests that fail any criterion and report them in a summary.
The developers are expected to write automated test scripts, which ensures that each and every section or a unit meets its design and behaves as expected.
Python Unit Testing
Though writing manual tests for your code is definitely a tedious and time-consuming task, Python's built-in unit testing framework has made life a lot easier.
The unit test framework in Python is called unittest
which comes packaged with Python.
Unit testing makes your code future-proof since you anticipate the cases where your code could potentially fail or produce a bug. Though you cannot predict all of the cases, you still address most of them.
A unit could be bucketed into various categories:
- An entire module,
- An individual function,
- A complete interface like a class or a method.
The best ways to write unit tests for your code is to first start with the smallest testable unit your code could possibly have, then move on to other units and see how that smallest unit interacts with other units, this way you could build up a comprehensive unit test for your applications.
Python's unit testing framework was inspired by java's JUnit
and has similar features as major unit testing frameworks in other languages. Python's unit testing framework offers various features, including:
- Test automation
- Sharing of setup and shutdown code for tests
- Aggregating tests into collections
- Independence of the tests from the reporting framework
Now let's take an example and understand why you need to have unit testing of your code.
Getting Started with Python unittest
Below, we've outlined the steps you need to use Python's unittest
framework
Creating a cube function
Let's write a code to calculate the volume of a cube in Python
def cuboid_volume(l):
return (l*l*l)
length = [2,1.1, -2.5, 2j, 'two']
for i in range(len(length)):
print ("The volume of cuboid:",cuboid_volume(length[i]))
The volume of cuboid: 8
The volume of cuboid: 1.3310000000000004
The volume of cuboid: -15.625
The volume of cuboid: (-0-8j)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-7-67e1c37a435f> in <module>
1 for i in range(len(length)):
----> 2 print ("The volume of cuboid:",cuboid_volume(length[i]))
<ipython-input-2-f50464fd88da> in cuboid_volume(l)
1 def cuboid_volume(l):
----> 2 return (l*l*l)
TypeError: can't multiply sequence by non-int of type 'str'
Result analysis
The above output should give you some intuition about the importance of having a unit test in place for your code. Now there are three things that are certainly incorrect in the above code:
- First, the volume of cuboid being negative,
- Second, the volume of the cuboid is a complex number,
- Finally, the code resulting in a
TypeError
since you cannot multiply a string, which is a non-int.
The third problem thankfully resulted in an error while the first & second still succeeded even though the volume of the cuboid cannot be negative and a complex number.
Unit tests are usually written as a separate code in a different file, and there could be different naming conventions that you could follow. You could either write the name of the unit test file as the name of the code/unit + test
separated by an underscore or test + name of the code/unit
separated by an underscore.
For example, let's say the above code file name is cuboid_volume.py
, then your unit test code name could be cuboid_volume_test.py
Without any further ado, let's write the unit test for the above code.
Using assertAlmostEqual
First, let's create a python file with the name volume_cuboid.py
, which will have the code for calculating the volume and second with the name test_volume_cuboid.py
, which will have the unit testing code.
import unittest
The TestCuboid
class inherits the unittest
module, and in this class, you would define various methods that you would want your unit test should check with your function cuboid_volume
.
The first function you will define is test_volume
, which will check whether the output your cuboid_volume
gives is equal to what you expect. To achieve this, you will make use of the assertAlmostEqual
method.
Test 1
class TestCuboid(unittest.TestCase):
def test_volume(self):
self.assertAlmostEqual(cuboid_volume(2),8)
self.assertAlmostEqual(cuboid_volume(1),1)
self.assertAlmostEqual(cuboid_volume(0),0)
self.assertAlmostEqual(cuboid_volume(5.5),166.375)
Let's run the above script. You would run the unittest
module as a script by specifying -m
while running it.
!python -m unittest test_volume_cuboid.py
Output:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Great! So you got your first unit test code working.
The test ran successfully and returned, OK
, which means the cuboid_volume function works as you would expect it too.
Test 2
Let's see what happens when one of the assertAlmostEqual
methods fails.
Notice that the last assert statement has been modified.
!python -m unittest test_volume_cuboid.py
Output:
F
======================================================================
FAIL: test_volume (test_volume_cuboid.TestCuboid)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\hda3kor\Documents\Unit_Testing_Python\test_volume_cuboid.py", line 15, in test_volume
self.assertAlmostEqual(cuboid_volume(5.5),0)
AssertionError: 166.375 != 0 within 7 places (166.375 difference)
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Well, from the above output, you can observe that the last assert statement resulted in an AssertionError
, hence a unit test failure. Python's unit test module shows you the reason for the failure, along with the number of failures your code has.
Using assertRaises
Now let's explore another assert method, i.e., assertRaises
, which will help you in finding out whether your function cuboid_volume
handles the input values correctly.
Let's say you want to test whether your function cuboid_volume
handles the class or type of input, for example, if you pass a string as an input, will it handle that input either as an exception or with an if condition since the length of the cuboid can never be a string.
!python -m unittest test_volume_cuboid.py
Output:
F.
======================================================================
FAIL: test_input_value (test_volume_cuboid.TestCuboid)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\hda3kor\Documents\Unit_Testing_Python\test_volume_cuboid.py", line 17, in test_input_value
self.assertRaises(TypeError, cuboid_volume, True)
AssertionError: TypeError not raised by cuboid_volume
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
Great! So from the above output, it is evident that your code volume_cuboid.py
doesn't take care of the input being passed to it properly.
Modifying the cube function
Let's add a condition in the volume_cuboid.py
to check whether the input or length of the cuboid is a boolean or a string and raise an error.
!python -m unittest test_volume_cuboid.py
Output:
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
Running the Python Testing Script in Jupyter lab
Let's now run the testing script within the jupyter lab. To achieve this, you would first define the testing script in the jupyter lab, as shown below.
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 25 20:16:58 2020
@author: Aditya
"""
from volume_cuboid import *
import unittest
class TestCuboid(unittest.TestCase):
def test_volume(self):
self.assertAlmostEqual(cuboid_volume(2),8)
self.assertAlmostEqual(cuboid_volume(1),1)
self.assertAlmostEqual(cuboid_volume(0),1)
def test_input_value(self):
self.assertRaises(TypeError, cuboid_volume, True)
Then, you will use the unittest.main()
method to run the testing script, you can pass several arguments to the below method, out of which one is verbosity level
.
Let's experiment with different verbosity levels and see how it changes the output description.
Verbosity 0
unittest.main(argv=[''],verbosity=0, exit=False)
Output:
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
<unittest.main.TestProgram at 0x1de02774348>
Verbosity 1
unittest.main(argv=[''],verbosity=1, exit=False)
Output:
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
<unittest.main.TestProgram at 0x1de027a1cc8>
Verbosity 2
unittest.main(argv=[''],verbosity=2, exit=False)
Output:
test_input_value (__main__.TestCuboid) ... ok
test_volume (__main__.TestCuboid) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
<unittest.main.TestProgram at 0x1de027a8308>
Visualizing the AssertionError
Lastly, let's modify the final assertAlmostEqual
in the test_volume
method and analyze how the verbosity level 2 will show the method failure.
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 25 20:16:58 2020
@author: Aditya
"""
from volume_cuboid import *
import unittest
class TestCuboid(unittest.TestCase):
def test_volume(self):
self.assertAlmostEqual(cuboid_volume(2),8)
self.assertAlmostEqual(cuboid_volume(1),1)
self.assertAlmostEqual(cuboid_volume(0),1)
def test_input_value(self):
self.assertRaises(TypeError, cuboid_volume, True)
unittest.main(argv=[''],verbosity=2, exit=False)
Output:
test_input_value (__main__.TestCuboid) ... ok
test_volume (__main__.TestCuboid) ... FAIL
======================================================================
FAIL: test_volume (__main__.TestCuboid)
----------------------------------------------------------------------
Traceback (most recent call last):
File "<ipython-input-28-46ad33b909ee>", line 14, in test_volume
self.assertAlmostEqual(cuboid_volume(0),1)
AssertionError: 0 != 1 within 7 places (1 difference)
----------------------------------------------------------------------
Ran 2 tests in 0.003s
FAILED (failures=1)
<unittest.main.TestProgram at 0x1de027b5448>
Summary
From the above outputs, the following conclusions can be made:
- Verbosity level 0 just shows how many tests were run, i.e., 2 tests along with the time it took to run them,
- Verbosity level 1 add two dots
..
which signifies two tests were executed, - Verbosity level 2 shows the detailed method names which were run along with their state
ok
orFAIL
.
Available unittest Assert Methods
There are a lot of assert methods in the unit test module of Python, which could be leveraged for your testing purposes.
Commonly used assert methods
Source : unittest documentation
Production checks
By using the following methods, you can check production of exceptions, warnings, and log messages.
Source : unittest documentation
Task-specific methods
Source : unittest documentation
To know in detail about the assert
methods, check out Python's official documentation.
You could also learn about them with the help of the Pydoc
module, which is similar to a help
function in Python. A demonstration of how you could use the Pydoc module to read about the assertCountEqual
method's documentation is shown below.
!python -m pydoc unittest.TestCase.assertCountEqual
Output:
Help on function assertCountEqual in unittest.TestCase:
unittest.TestCase.assertCountEqual = assertCountEqual(self, first, second, msg=None)
An unordered sequence comparison asserting that the same elements,
regardless of order. If the same element occurs more than once,
it verifies that the elements occur the same number of times.
self.assertEqual(Counter(list(first)),
Counter(list(second)))
Example:
- [0, 1, 1] and [1, 0, 1] compare equal.
- [0, 0, 1] and [0, 1] compare unequal.
Conclusion
This tutorial was a basic introduction to unit testing in python and its importance as a developer. One good exercise for you would be to write a unit test module for any of your past projects. This will give you a good hands-on experience with writing unit tests. Also, try exploring the remaining Assert methods. You can read more about unit testing with our Pytest tutorial.
If you are just getting started in Python and would like to learn more, take our Introduction to Data Science in Python course.
Python Courses
Course
Introduction to Python
Course
Intermediate Python
tutorial
How to Use Pytest for Unit Testing
tutorial
Test-Driven Development in Python: A Beginner's Guide
tutorial
Type Checking in Python Tutorial
Olivia Smith
7 min
tutorial
Definitive Guide: Threading in Python Tutorial
tutorial