The testing of algorithms and software designs is one of the key features of programming. The right data for testing will always inform applications of any bugs or fixes that need incorporation before it is released into the market. This is where Python unittest comes in.
The Unittest unit testing framework was originally inspired by JUnit and is similar to major unit testing frameworks in other languages. Unittest supports test automation, the sharing of setup and shutdown code for tests, aggregation of tests into collections, and independence of the tests from the reporting framework. Let’s look at an example to better understand the application of unittest.
Imagine that a software company asks you to design a function that calculates the volume of a sphere given a radius. You may remember from math class that the volume of a sphere is (4/3 * pi * r^{3}). This seems pretty easy, so let’s write the code for this problem:
def sphere_volume(radius):pi = 3.141592653589793return 4/3 *pi*(radius**3)r = [1,2.5,3.4,5.6,7,9.0]for x in r:print("Volume of sphere with radius = ", x, "is = ", sphere_volume(x))
This is quite good. However, what if I pass a string or boolean value as a radius? Or what would happen if a negative value is passed as one of the values of radius? Let’s incorporate the inputs from the code above:
def sphere_volume(radius):pi = 3.141592653589793return 4/3 *pi*(radius**3)r = [1,2.5,3.4,5.6,7,9.0, -2, -5, True, False, "string"]for x in r:print("Volume of sphere with radius = ", x, "is = ", sphere_volume(x))
Now we have a testing problem. This function also gives an output on the following values instead of throwing an error.
-2
– Radius cannot be a negative value.-5
– Radius cannot be a negative value.True
– Radius cannot be a boolean value.False
– Radius cannot be a boolean value.Even though the program does not know that radius is not supposed to be negative, it gave an error when the string value was passed as a radius. However, this case could fail in certain situations, which is why we would need to define a unittest class to validate the radius passed. Let’s write a code for the test file:
def sphere_volume(radius):pi = 3.141592653589793return 4/3 *pi*(radius**3)r = [1,2.5,3.4,5.6,7,9.0, True,"string"]for x in r:if type(x) not in [int, float]:raise TypeError("Invalid type")if x<0:raise ValueError("Radius Negative")print("Volume of sphere with radius = ",x, "is",sphere_volume(x))
In order to run the following code, make sure that both of the files are in the same folder and then type the following command in the terminal:
python -m unittest test_volume.py
Where
test_volume.py
refers to the name of our test file.
When we run this file, it will give an error for boolean and negative. This error tells us that our test has failed, which means we are on the right track. So, we will remove the invalid values – now, our test gives the correct output and Tests run Ok
.
import unittest from volume import sphere_volume pi = 3.141592653589793 class TestVolumeSpehere(unittest.TestCase): def test_values(self): # parameters of assertRaises # 1: Type of error # 2: Volume caculated by the function # 3: The values which should raise the error. self.assertRaises(ValueError, sphere_volume, -2) self.assertRaises(ValueError, sphere_volume, -5) def test_values(self): # parameters of assertRaises # 1: Type of error # 2: Data type Volume caculated by the function # 3: The dataType which should raise an error self.assertRaises(TypeError, sphere_volume, "string") self.assertRaises(TypeError, sphere_volume, bool)
Let’s understand what we have done:
We defined a unittest class to test our function and learned that: it is important to import unnittest
library and we need to define a test class to perform unittest test cases. In our function, we mostly encountered two major problems:
The function was giving output on negative values. In this situation, we call the assertRaises
method of our class module, which returns a ValueError
if the radius is negative. For testing purposes, we have given it examples -2
and -5
. Now, for a negative input, we know that this function is going to raise an error. How do we solve it? Well, in volume.py
file, we define an if condition to only calculate the volume if the radius is positive. If not, it will raise a value error. Now, our code is aligned with our first test case.
The function was given output on boolean values. In this situation, we call the assertRaises
method of our class module that returns TypeError
if the radius is boolean, string, etc. For testing purposes, we have given it the examples of bool
and "string"
as an input. Now we know that for invalid input, this function is going to raise an error. So how do we solve it?
Well, in volume.py
file, we define an if condition to only calculate the volume if the radius data type is either a float value or int value. If not, this raises a Type error. Now, our code is also aligned with our second test case.