Mock objects in action

That was a lot of theory interspersed with unrealistic examples. Let's take a look at what we've learned and apply it to the tests from the previous chapters for a more realistic view of how these tools can help us.

Better PID tests

The PID tests suffered mostly from having to do a lot of extra work to patch and unpatch time.time, and had some difficulty breaking the dependence on the constructor.

Patching time.time

Using patch, we can remove a lot of the repetitiveness of dealing with time.time; this means that it's less likely that we'll make a mistake somewhere, and saves us from spending time on something that's kind of boring and annoying. All of the tests can benefit from similar changes:

>>> from unittest.mock import Mock, patch
>>> with patch('time.time', Mock(side_effect = [1.0, 2.0, 3.0, 4.0, 5.0])):
...    import pid
...    controller = pid.PID(P = 0.5, I = 0.5, D = 0.5, setpoint = 0,
...                         initial = 12)
...    assert controller.gains == (0.5, 0.5, 0.5)
...    assert controller.setpoint == [0.0]
...    assert controller.previous_time == 1.0
...    assert controller.previous_error == -12.0
...    assert controller.integrated_error == 0.0

Apart from using patch to handle time.time, this test has been changed. We can now use assert to check whether things are correct instead of having doctest compare the values directly. There's hardly any difference between the two approaches, except that we can place the assert statements inside the context managed by patch.

Decoupling from the constructor

Using mock objects, we can finally separate the tests for the PID methods from the constructor, so that mistakes in the constructor cannot affect the outcome:

>>> with patch('time.time', Mock(side_effect = [2.0, 3.0, 4.0, 5.0])):
...    pid = imp.reload(pid)
...    mock = Mock()
...    mock.gains = (0.5, 0.5, 0.5)
...    mock.setpoint = [0.0]
...    mock.previous_time = 1.0
...    mock.previous_error = -12.0
...    mock.integrated_error = 0.0
...    assert pid.PID.calculate_response(mock, 6) == -3.0
...    assert pid.PID.calculate_response(mock, 3) == -4.5
...    assert pid.PID.calculate_response(mock, -1.5) == -0.75
...    assert pid.PID.calculate_response(mock, -2.25) == -1.125

What we've done here is set up a mock object with the proper attributes, and pass it into calculate_response as the self-parameter. We could do this because we didn't create a PID instance at all. Instead, we looked up the method's function inside the class and called it directly, allowing us to pass whatever we wanted as the self-parameter instead of having Python's automatic mechanisms handle it.

Never invoking the constructor means that we're immune to any errors it might contain, and guarantees that the object state is exactly what we expect here in our calculate_response test.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.26.217