As I mentioned previously, although I have a computer science degree. My career took a different path, and I have not used that degree other than for my own hobbies (as evidenced by this blog). In school they teach you to plan before you code and to use unit testing as you develop. However, I am impetuous, and I typically jump straight into coding. Given that I am not working on timelines, I have no problem if I need to completely rip up my code after I have started and re-write.
Recently, I was working on a project that involved recursion (see my last post on debugging). Unlike, my other projects, I decided to take a unit testing approach to development. I created the shell of the function with the input parameters and the expected output type. However, before I wrote a single line of code, I created a set of unit tests using Pythons unittest standard library.
Bubble Sort
To see how unit testing works, we need example code. So I created the code below that takes a list as an input and recursively uses the Bubble Sort algorithm to sort. Bubble Sort is very inefficient, but it is easy to implement recursively. Therefore, it is easy to demonstrate. I will call this file bubble.py.
import sys
sys.setrecursionlimit(10**6)
def sort_bubble(current_list):
n = len(current_list)
if n == 1:
return current_list
for index in range(n-1):
if current_list[index] > current_list[index+1]:
# Swap the elements
temp = current_list[index]
current_list[index] = current_list[index+1]
current_list[index+1] = temp
# At this point, the largest element is in n-1, now recursively call on subset
return sort_bubble(current_list[:n-1]) + current_list[n-1:]
The function has two parts. First, if the input list contains one element, simply return the list with the element. If the input list has more than one element, we will iterate through the list. We test each pair. If the first number is greater than the second, we flip the elements. This causes the largest element in the list to “bubble” to the end. Now that we know the final element is in the correct place, we recursively call the function on the list without the last element. When the recursive function returns, we attach the largest element and return.
While testing this code on lists with large numbers, I found out that Python has a default recursion limit of 1000. I wanted to sort larger numbers, so I needed to increase the limit to 1 million (10^6).
Unit Testing Script
Next, we will create a test script. Create a new file called bubble_test.py with the following code.
import unittest
import bubble
import time
class TestBubbleSort(unittest.TestCase):
def test_sorted(self):
testList = [1,2,3,4,5,6,7,8,9,10]
result = bubble.sort_bubble(testList)
self.assertEqual(result, testList)
def test_reverse_sorted(self):
testList = [10,9,8,7,6,5,4,3,2,1]
result = bubble.sort_bubble(testList)
self.assertEqual(result,[1,2,3,4,5,6,7,8,9,10])
def test_worst_speed(self):
testList = []
for index in range(4000,1,-1):
testList.append(index)
start = time.time()
result = bubble.sort_bubble(testList)
end = time.time()
self.assertTrue(end-start < 2.0)
if __name__ == '__main__':
unittest.main()
We import the unitest library, our bubble code, and time (which we will use in one of our test cases). To start, we create a class called TestBubbleSort that inherits from unittest.TestCase. We then create methods within the class. Each method will represent a test case. We start each method with “test_” followed by the name of the test.
The first test, test_sorted, is a list that is already sorted. We create the list and sort it using our function. Then we use self.assertEqual to check our result with the correctly sorted test list.
def test_sorted(self):
testList = [1,2,3,4,5,6,7,8,9,10]
result = bubble.sort_bubble(testList)
self.assertEqual(result, testList)
The second test, test_reverse_sorted, is a list that is in reverse order from 10 to 1.
def test_reverse_sorted(self):
testList = [10,9,8,7,6,5,4,3,2,1]
result = bubble.sort_bubble(testList)
self.assertEqual(result,[1,2,3,4,5,6,7,8,9,10])
Finally, the last test case shows an example of testing a unit case for something other than the result. In this case, we are testing performance. We want the test to complete in less than two seconds.
def test_worst_speed(self):
testList = []
for index in range(4000,1,-1):
testList.append(index)
start = time.time()
result = bubble.sort_bubble(testList)
end = time.time()
self.assertTrue(end-start < 2.0)
If you are interested in other ways to use unit testing for performance testing, I found this post.
Conclusion
This post provides a quick overview of the Python unittest library. This library is easy to use and very effective for unit testing code.