Python testing with unittest module | Testing mathematical operations

The unittest module is part of Python’s standard library and it is very popular when it comes to testing Python code. You need two files (preferably placed in the same directory), one file that contains the code that is tested and another file with the code that tests the code of the first file.

My first file is named math_operators.py and it contains basic functions I am going to test. The second file is named test_math_operators.py and it is going to contain the testing code.

Note:
I am referring to this documentation when writing the tests on this page: https://docs.python.org/3/library/unittest.html#unittest.TestCase.debug.

Testing Python’s mathematical operators with unittest module

In order to start testing I have created the following basic functions in my math_operators.py file.

def addition(a, b):
	return a + b

def subtraction(a, b):
	return a - b

def multiplication(a, b):
	return a * b

def division(a, b):
	if b == 0:
		raise ValueError("Can't divide by zero!")
	return a / b

The code is self explanatory with the exception of division(a, b) function which contains an if statement used for the case when b variable is 0.

Now the interesting part, the test_math_operators.py file.

import unittest
import math_operators

class TestMathOperators(unittest.TestCase):

	def test_addition(self):
		result = math_operators.addition(8, 4)
		self.assertEqual(result, 12)

if __name__ == '__main__':
	unittest.main()

First of all we have to import the unittest module. As I said, it is part of the main Python library and it can easily be imported as shown on line one.

On the second line we import the module that we want to test. Since it is in the same directory with our testing file it also can be imported easily.

Next, we have to create some test cases for the functions we want to test. In order to do that we have to create a test class that inherits from unittest.Testcase. This will give our class different testing capabilities within the TestMathOperators class.

The first test is written within the class we have just created. The test is actually a method and its name has to start with test_ otherwise it won’t be run. This is a unittest naming convention. The first method is named test_addition and it tests the addition function.

Note:
As any method in a class, a method takes self as the first argument.

Now, within our method, we can write our actual test. I have created a variable named result which actually is the result of the addition function from our math_operators.py module. It is the line result = math_operators.addition(8, 4).

Since we pass the values of 8 and 4, we expect the result to be 12. The next line of code does exactly that self.assertEqual(result, 12).

The last part has to do with running the test.

The if statement says that if we run this module directly then run the code within the conditional. That code is unittest.main(), so now we can run this file in the terminal running the command python3 test_math_operators.py.
Without this conditional we would have to run the command python3 -m unittest test_math_operators.py.

This is the terminal output:

(PythonTesting) saigon@ddnro:~/Documents/Environments/PythonTesting$ python3 test_math_operators.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
(PythonTesting) saigon@ddnro:~/Documents/Environments/PythonTesting$ 

Let’s try to make our test to fail. For that I will just replace the 12 with 13. Since 8 + 4 = 12 not 13, the test should fail. If we run it in the terminal the result is as shown below:

(PythonTesting) saigon@ddnro:~/Documents/Environments/PythonTesting$ python3 test_math_operators.py
F
======================================================================
FAIL: test_addition (__main__.TestMathOperators)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_math_operators.py", line 8, in test_addition
    self.assertEqual(result, 13)
AssertionError: 12 != 13

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
(PythonTesting) saigon@ddnro:~/Documents/Environments/PythonTesting$ 

Shortening the code

We can clearly see that the result repeats on both lines within the testing method test_addition. We can eliminate the first line (result = math_operators.addition(8, 4)) by replacing the result on the second line with the value of the result from the first line. The test_addition now looks like shown below:

def test_addition(self):
		self.assertEqual(math_operators.addition(8, 4), 12)

Running the test now will give you the same positive result.

Now, we have to test some edge cases. For example addition of -3 and 3 should equal 0, or addition of two negative numbers etc.

def test_addition(self):
		self.assertEqual(math_operators.addition(8, 4), 12)
		self.assertEqual(math_operators.addition(-3, 3), 0)
		self.assertEqual(math_operators.addition(-3, -3), -6)

If we run it in the terminal then the result will be the same “Run 1 test … OK”. You might expect three tests but it is really a single test, the test_addition, but with three assertions.

Note:
The goal of writing tests is not to write as many tests as possible but to write as good tests as possible.

Now we have to test the other three mathematical operations. We will do it the same way we tested the addition function.

    def test_addition(self):
		self.assertEqual(math_operators.addition(8, 4), 12)
		self.assertEqual(math_operators.addition(-3, 3), 0)
		self.assertEqual(math_operators.addition(-3, -3), -6)

	def test_subtraction(self):
		self.assertEqual(math_operators.subtraction(8, 4), 4)
		self.assertEqual(math_operators.subtraction(-3, 3), -6)
		self.assertEqual(math_operators.subtraction(-3, -3), 0)

	def test_multiplication(self):
		self.assertEqual(math_operators.multiplication(8, 4), 32)
		self.assertEqual(math_operators.multiplication(-3, 3), -9)
		self.assertEqual(math_operators.multiplication(-3, -3), 9)
		self.assertEqual(math_operators.multiplication(3, 0), 0)

	def test_division(self):
		self.assertEqual(math_operators.division(8, 4), 2)
		self.assertEqual(math_operators.division(-3, 3), -1)
		self.assertEqual(math_operators.division(-3, -3), 1)
		self.assertEqual(math_operators.division(3, 6), 0.5)

		with self.assertRaises(ValueError):
			math_operators.division(3, 0)

Everything should be clear at this stage, except the last two lines of code that checks the if statement within our division function. We have to check if a number is divided by 0 the function raises a ValueError.

This can be done in more than one way but one common way of testing this is by using the context manager. Basically, what the code says is that if 3 is divided by 0 then the ValueError error is raised.

Leave a Reply