Unit Testing in Python
Testing your code is just as important as writing it.
If the user can do it, the user will do it.
Our programs should be as robust as possible. In other words, a program must work correctly for all possible scenarios. This is why unit testing is so important.
Unit testing is the process of verifying and validating code ๐. A unit test specifies the program input and expected result. You write unit test cases for all possible scenarios. For every input, the program must produce the correct result.
Of course it is not practical to test every possible input. But we can generalize our test cases over a range of possible values. Then we can have confidence that our programs work correctly.
Imagine that your task is to write a Python program to count the number of occurrences of a number, or key, in an array and return the count. Here is a program that defines a count()
function to do that:
# countkey.py
def count(k,l):
# count occurrences of k in l
num = 0
for i in l:
if i == k:
num += 1
return num # return the count
Now consider this unit test, where the input values are the number to search for and the array to search, followed by the expected output:
- Input:
1
,[1,2,3,1]
- Expected Output:
2
Since 1
appears in the array twice, the expected return value is 2
.
Python provides a unit testing framework called unittest
that automates unit test cases. Given the countkey.py
program that we are testing, here is another program called tester.py
that automates the unit test:
# tester.py
import countkey # the program to test
import unittest # Python test framework
class TesterClass(unittest.TestCase):
# test cases
def test1(self):
self.assertEqual(countkey.count(1,[1,2,3,1]), 2)
if __name__ == '__main__':
unittest.main()
In the above program, there is one test case which is the test1()
method. This method asserts that the count()
function returns the value 2
, when given the input parameters: 1
, [1,2,3,1]
. When you execute the tester.py
program, the output is:
$ python tester.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
$
The test case passed. But there are a lot more scenarios to test if we want to ensure that the program is correct and robust. Identifying these scenarios is vital to the success of your programs. Here are some other unit tests:
- Count occurrences of a positive number
- Count occurrences of a negative number
- Count occurrences when the key is at the beginning of the array
- Count occurrences when the key is in the middle of the array
- Count occurrences when the key is at the end of the array
- Count occurrences when the key occurs multiple times
- Count occurrences when the key does not occur at all
- Count occurrences when the array is empty
- Count occurrences when searching for the key
None
, and it is in the array - Count occurrences when searching for the key
None
, and it is not in the array - Count occurrences when searching in
None
instead of an array
These are not all possible unit tests, but they do check all types of scenarios such as different numbers, boundaries, empty arrays, etc.
Here is the complete tester.py
program that automates all of the above unit tests:
# tester.py
import countkey # the program to test
import unittest # Python test framework
class TesterClass(unittest.TestCase):
# test cases
def test1(self):
# find positive key
self.assertEqual(countkey.count(6,[6,7,8]), 1)
def test2(self):
# find negative key
self.assertEqual(countkey.count(-7,[6,-7,8]), 1)
def test3(self):
# key is found at the beginning
self.assertEqual(countkey.count(6,[6,7,8]), 1)
def test4(self):
# key is found in the middle
self.assertEqual(countkey.count(7,[6,7,8]), 1)
def test5(self):
# key is found at the end
self.assertEqual(countkey.count(8,[6,7,8]), 1)
def test6(self):
# multiple keys found
self.assertEqual(countkey.count(1,[1,6,1,7,8,1]), 3)
def test7(self):
# key is not found
self.assertEqual(countkey.count(9,[1,2,3,1]), 0)
def test8(self):
# empty array
self.assertEqual(countkey.count(3,[]), 0)
def test9(self):
# Find None when it is there
self.assertEqual(countkey.count(None,[1,2,None,3]), 1)
def test10(self):
# Find None when it is not there
self.assertEqual(countkey.count(None,[1,2,3]), 0)
def test11(self):
# Find in None?
self.assertEqual(countkey.count(3,None), 0)
if __name__ == '__main__':
unittest.main()
When you execute tester.py
, the output is:
$ python tester.py
..E........
======================================================================
ERROR: test11 (__main__.TesterClass)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tester.py", line 51, in test11
self.assertEqual(countkey.count(3,None), 0)
File "/Users/user/Desktop/countkey.py", line 5, in count
for i in l:
TypeError: 'NoneType' object is not iterable
----------------------------------------------------------------------
Ran 11 tests in 0.001s
FAILED (errors=1)
$
The output indicates that test11()
failed. That is because the program does not properly handle the case when the count()
function tries to search None
instead of an array. We can modify countkey.py
to make it more robust:
# countkey.py
def count(k,l):
# count occurrences of k in l
# NEW
if l == None:
return 0
num = 0
for i in l:
if i == k:
num += 1
return num # return the count
Here is the output now:
$ python tester.py
...........
----------------------------------------------------------------------
Ran 11 tests in 0.000s
OK
$
Since all the unit test cases are successful, we can be confident that the program is correct. Another advantage of automating unit test cases is that we can easily re-test the countkey.py
whenever someone modifies it. All you have to do is execute the tester.py
program. That is why it is important to write good, comprehensive unit test cases for your programs. And remember to update your tester programs as needed.
Unit testing is an important part of program development. Once you identify the full set of unit test cases, automate them with a testing framework like Python unittest
. This will improve your program and ensure that the program is correct, now and in the future.
Thanks for reading. ๐
Follow me on Twitter @realEdwinTorres
for more programming tips and help.