One of the things I've learned working at Google have been the importance of testing code such that it executes reliably and consistently. Most of my familiarity with tests comes from testing individual modules that I write (read as unit tests). I've commonly faced the problem where I have code that makes a network or disk I/O request or relies on some input from an external module I have no control over. Enter the concept of mocking.
Aside: Martin Fowler has a great discussion on the differences between Mocks, Stubs, and Fakes that you can read here.
Mocking, for the purposes of this discussion, is overriding behavior of a module to consistent behavior such that the module being tested is properly isolated. In the examples I'll be using here, we'll be referring to the unittest.mock framework available in Python 3.x. If you're using Python 2.7, the Voidspace mock library provides a very similar interface. I'd suggest keeping that open in another tab so you can refer to it as we go along.
Let's begin, suppose you have the following Python class.
class Barista(object): def MakeMocha(EspressoMachine e): if e.InService(): shot := e.MakeShot() return self._mix(shot, _fridge.GetMilk(.25)) else: raise Error("Espresso Machine is broken.") ...
Now suppose that making the shot takes a long time and you just want to make sure that the Barista class can make the drink. A (non-exhaustive) unit test would look something like:
class BaristaTest(unittest.TestCase): ... def TestMakeMocha(): expectedDrink = ... e = EspressoMachine(...) assertEquals(barista.MakeMocha(e), expectedDrink) ...
However the problem is that your test for MakeMocha assumes that EspressoMachine is implemented correctly, furthermore you're also relying on a potential production implementation that could call out to other resources and thus "de-isolate" your test. This is where mocks are useful, instead of having to write an interface and implement your own FakeEspressoMachine, you can use mocks to isolate this test to the behavior of the Barista and eliminate any variables that could come from the EspressoMachine.
With the mock library, we can use a decorator to override certain behavior before we ask the Barista to make the drink and resume normal behavior after he's done. Let's walk through a code example.
from unittest.mock import patch class BaristaTest(unittest.TestCase): ... def TestMakeMocha(): expectedDrink = ... e = EspressoMachine(...) with patch.object(EspressoMachine, 'MakeShot', returnValue=EspressoShot()): assertEquals(barista.MakeMocha(e), expectedDrink) ...
In this example we make use of the patch.object decorator. This takes a class and method name and will override it with a class that will allow us to specify a custom return value. You could also use this to specify custom side effects (like throwing errors). This is great, now we can test the Barista's Mocha making skills without having to have a working EspressoMachine.
But we could take this one step further: How do we know if the Barista is actually using the EspressoMachine at all? Luckily, unittest.mock provides us with some extra diagnostic goodies when we patch an object. Here's another example:
from unittest.mock import patch class BaristaTest(unittest.TestCase): ... def TestMakeMocha(): expectedDrink = ... e = EspressoMachine(...) with patch.object(EspressoMachine, 'MakeShot', returnValue=EspressoShot()): assertEquals(barista.MakeMocha(e), expectedDrink) assert e.MakeShot.called ...
Now we're able to test if the Barista even calls the EspressoMachine at all. Doing this allows you to not only black-box test the implementation of a method, but allows you to make sure the correct side effects occur and correct functions are called.