Python unit tests using mock

Problem: Introductions to Python unit checking are too basic

This post is for the hands on tester looking to practice their unit checking skills.


Why this post?

Unit checks are good. They play an important role in your regression suite. Online tutorials of Python unit checks invariably leave me wanting more. The examples covered are extremely basic. Further, these basic tutorials miss one key step in learning unit checking – the ability to read code written by somebody else. This problem ends with this post. I take an open source Python application, read through the code and put in a unit check for one method. At the end of this tutorial, you will have learnt about mocking objects and patch decorators. Bonus: You will have a real application to practice your unit checking.


Setup

This post will show you how to unit check a Python application using the Python unittest module and the Python mock module. Our love affair with Python and chess continues. We will use the Python Chess application as the software under test and write a unit check for one of the methods. To get setup:

1. Install mock
We will use the Python mock module. The mock module has one of my favorite features – patch decorators, that make it easy to mock classes. We are doing this on Python 2.7. For Python 3.x you could skip this step. To install mock, open a command prompt and run:

pip install -U mock

2. Download the Python Chess application
Download the Python Chess app. You may also need to download and install pygame for your corresponding Python version. Pygame lets you run the application.


Write the Python unit check

The application we have chosen is small enough for you to look at the different classes and what they do. For this post, we are choosing to write a unit check for the IsCheckmate method in ChessRules class.
1. Get to know your application
2. Notice the key call
3. Mock and patch the method GetListOfValidMoves
4. Design the chessboard position needed for the unit check
5. Create the expected argument calls
6. Assert that the list of calls and the arguments were correct
7. Put it all together
8. Run the Test

1. Get to know your application
Open up ChessRules.py in your favorite editor and read through the IsCheckmate method.

def IsCheckmate(self,board,color):
	#returns true if 'color' player is in checkmate
	#Call GetListOfValidMoves for each piece of current player
	#If there aren't any valid moves for any pieces, then return true
 
	if color == "black":
		myColor = 'b'
		enemyColor = 'w'
	else:
		myColor = 'w'
		enemyColor = 'b'
 
	myColorValidMoves = [];
	for row in range(8):
		for col in range(8):
			piece = board[row][col]
			if myColor in piece:
				myColorValidMoves.extend(self.GetListOfValidMoves(board,color,(row,col)))
 
	if len(myColorValidMoves) == 0:
		return True
	else:
		return False

2. Notice the key call
Most methods that you want to write unit checks for, will hand off some of their logic to other methods. Its important that these other methods (called methods) are mocked so that you can check the working of the method under test in isolation. When a unit check fails, its because of a specific problem with the method under test. In our example, notice that the key call in the IsCheckmate is the call to GetListOfValidMoves in the line:

myColorValidMoves.extend(self.GetListOfValidMoves(board,color,(row,col))).

We will check whether the GetListOfValidMoves method is getting called with the arguments that we expect it to be called with. The method IsCheckmate is working correctly as it is calling GetListOfValidMoves with the right arguments. Bugs in GetListOfValidMoves will be caught by unit checks written for GetListOfValidMoves. Note that for this blog post we have chosen to write one among a large number of possible unit checks for the IsCheckmate method. We chose this particular check because it illustrates multiple key concepts: mocking objects, patching methods, reading and understanding code.

3. Mock and patch the method GetListOfValidMoves
Since the method GetListOfValidMoves is the key call here, lets mock and patch the method. To do so, you can simply begin with these lines:

import unittest, mock
 
    @mock.patch('ChessRules.ChessRules.GetListOfValidMoves')        
    def test_Checkmate(self, mockGetListOfValidMoves):

You can patch a method by calling the @mock.patch decorator. You can then access this patched object via the argument mockGetListOfValidMoves.

4. Design the chessboard position needed for the unit check
For this example, lets choose a simple chessboard position – the starting position. To create this chessboard object, you will need the following lines:

from ChessBoard import ChessBoard
        # Creating objects of Chessboard and ChessRules class and calling IsCheckmate function with each piece for initial position and "black" color
        cb = ChessBoard(0) #Create a chess board object with the initial position

NOTE: ChessBoard(0) initializes the chessboard to the initial position.

5. Create the expected argument calls
By reading through the IsCheckmate method, we expect GetListOfValidMoves to be called sixteen times with every black piece on the board for the chess position we are passing it.

# Create expected_arg_calls list which is supposed to be called with GetListOfValidMoves for initial position
# IsCheckmate calls GetListOfValidMoves with arguments: board, color (who is to play?) and co-ordinates of a square with a piece on it
expected_arg_calls = []
for row in range(0,2):
	for col in range(0,8):
		expected_arg_calls.append(mock.call(cb.GetState(), 'black', (row, col)))

6. Assert that the list of calls and the arguments were correct
We can assert that the mocked object got called with the right arguments by adding this line:

# Assert that the mockGetListOfValidMoves.call_args_list matches expected_arg_calls
self.assertEqual(mockGetListOfValidMoves.call_args_list, expected_arg_calls)

7. Put it all together
Here is the final script you need. Please place it in the same directory as where you have ChessRules.py and ChessBoard.py.

"""
Example written for Qxf2 Services' blog post on Python Unit Checking
Check if IsCheckmate method in ChessRules class calls GetListOfValidMoves  
Assert that it called with the expected arguments
"""
import unittest, mock
import ChessRules 
from ChessBoard import ChessBoard
 
class CheckIsCheckmate(unittest.TestCase):
    "Class to unit check the IsCheckmate method of ChessRules.py module"
    # creating a mock for GetListOfValidMoves
    @mock.patch('ChessRules.ChessRules.GetListOfValidMoves')        
    def test_Checkmate(self, mockGetListOfValidMoves):
        "Unit test for ChessRules method: IsCheckmate"
        # NOTE 1: We only check that there are valid moves for a given color
        # NOTE 2: We are NOT checking the setting color logic in this unit test. You need to write another unit test for this logic.
 
        # Creating objects of Chessboard and ChessRules class and calling IsCheckmate function with each piece for initial position and "black" color
        cb = ChessBoard(0) #Create a chess board object with the initial position
        chess_rules_obj = ChessRules.ChessRules()
        chess_rules_obj.IsCheckmate(cb.GetState(),"black")
 
        # Create expected_arg_calls list which is supposed to be called with GetListOfValidMoves for initial position
        # IsCheckmate calls GetListOfValidMoves with arguments: board, color (who is to play?) and co-ordinates of a square with a piece on it
        expected_arg_calls = []
        for row in range(0,2):
            for col in range(0,8):
                expected_arg_calls.append(mock.call(cb.GetState(), 'black', (row, col)))
 
        # Assert that the mockGetListOfValidMoves.call_args_list matches expected_arg_calls
        self.assertEqual(mockGetListOfValidMoves.call_args_list, expected_arg_calls)
 
        # DID YOU KNOW?
        # assert_any_call can be used to assert a method that was called at least once with some arguments        
        #mockGetListOfValidMoves.assert_any_call(cb.GetState(),"black",(1,6))
 
        # DID YOU KNOW?
        # call_count can be used to check the number of times your mocked method was called
        #self.assertEqual(mockGetListOfValidMoves.call_count,16)
 
 
if __name__=="__main__":
    unittest.main()

8. Run the Test
Yay! Run the test.

Python unit check test run

And that’s it. You just ran a unit check on your Python application. Bonus: You now have a moderately complex application to practice your unit checking.


References

I found these references very useful:
1. An excellent example of Python mock and patching
2. Another tutorial on getting started with mock objects
3. Nice Youtube video introducing Mock

One thought on “%1$s”

Leave a Reply

Your email address will not be published. Required fields are marked *