-
Notifications
You must be signed in to change notification settings - Fork 13
Unit Test Patterns
- Avoid defining fakes/side_effects wherever possible and rather set the side_effect to iterable if we only need to ensure that correct returns are being assigned to mocked functions.
mock_util.rec_list_splice.side_effect = util.load_from_disk(current_directory+ "/data/message_number_graph/rec_list_splice")
The /data/message_number_graph/rec_list_splice
file contains a list. Once assigned as side_effect to mock_util.rec_list_splice() function, the mock library iterates over the list and returns the next item from the list every time the mocked function is called. Another way would be to define our own side_effects.
def explicit_side_effect(args):
return "some_value"
Although defining our own side_effects give us much more command over fakes but for our requirements, the first method is elegant and should be followed.
- Explicitly add return statements to function if they need to be mocked.
def rec_list_splice(rec_list):
rec_list[1] = rec_list[1][rec_list[1].find(">") + 1:len(rec_list[1])][1:]
return rec_list
In rec_list_splice() function a explicit return is added, even though not required in the code, it helps while writing tests and is coherent with out way of using side_effects.
Whenever rec_list_splice() function is used, we need to take assign the returned value to an appropriate variable.
rec_list = util.rec_list_splice(rec_list)
With the use of the coding conventions put down by the previous two code examples, we are restricting the the mode of arguments to "pass by value", there by avoiding "pass by reference" which becomes difficult to test.
- Use StringIO to redirect print statements to a python object.
capturedOutput = StringIO.StringIO()
sys.stdout = capturedOutput
network.filter_edge_list(current_directory +'/data/filter_edge_list_input.txt', 10000, 100)
sys.stdout = sys.__stdout__
output = capturedOutput.getvalue()
f = open(current_directory +'/data/filter_edge_list_out.txt','r')
expected_output = f.read()
f.close()
capturedOutput.close()
self.assertEqual(output, expected_output)
- Remember to reset stdout to default
sys.__stdout__
- Remeber to close StringIO stream
capturedOutput.close()
- The stdout redirection is useful in muting unwanted print outputs while testing.
- Use the captured output to compare with the expected results.
- Avoid creating a new file and performing filecmp.
For mocking modules, creating separate mocks for each function is advised, since it gives us the flexibility of not mocking trivial functions. If we create a Mock object for the module itself, then we must provide expected values for each of the function inside a mock.
@mock.patch('lib.slack.config.MAX_EXPECTED_DIFF_NICKS', 5000)
@mock.patch('lib.slack.config.THRESHOLD_MESSAGE_NUMBER_GRAPH', 0)
@mock.patch('lib.slack.config.MINIMUM_NICK_LENGTH', 3)
@mock.patch('lib.slack.config.DEBUGGER', True)
@mock.patch('lib.slack.util.to_graph', autospec=True)
@mock.patch('lib.slack.util.create_connected_nick_list', autospec=True)
@mock.patch('lib.slack.util.check_if_msg_line', autospec=True)
@mock.patch('lib.slack.util.get_nick_representative', autospec=True)
@mock.patch('lib.slack.util.get_year_month_day', autospec=True)
@mock.patch('lib.slack.util.get_nick_sen_rec', autospec=True)
@mock.patch('lib.slack.util.correctLastCharCR', autospec=True)
@mock.patch('lib.slack.util.rec_list_splice', autospec=True)
In the above code, we could have just created a single mock for lib.slack.util, but doing so we are bound to provide returns for every function inside module. The above design seems more flexible and hence should be followed.
- Branch History
- Best Practices
- Testing in Python
- Logger Config
- Refactoring Suggestions