Skip to content

Unit Test Patterns

Prasad Talasila edited this page Mar 21, 2018 · 10 revisions

Unit Test Patterns

Side Effects

  • 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_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.

Mocking print statements

  • 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.

Mocking modules

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.


Exceptions


Questions

  1. Where should the mock functions/side effects functions be located? At global level within a test module or within a test class?
  2. What should be the naming convention for the variable names of the expected results?
  3. Should the mocks be asserted if they are being called with correct argumnets or correct number of times?