When our code interacts with other classes through methods and attributes, these are referred to as collaborators. Mocking out collaborators using mockito (http://code.google.com/p/mockito and http://code.google.com/p/mockito-python) provides a key tool for BDD. Mocks provide a way for providing canned behavior compared to stubs, which provide canned state. While mocks by themselves don't define BDD, their usage keenly overlaps the ideas of BDD.
To further demonstrate the behavioral nature of the tests, we will also use the spec
nose plugin found in the pinocchio project (http://darcs.idyll.org/~t/projects/pinocchio/doc).
For this recipe, we will be using the shopping cart application shown at the beginning of this chapter with some slight modifications.
recipe31_cart.py
and copy all the code from cart.py
created in the introduction of this chapter.__init__
to add an extra storer
attribute used for persistence.class ShoppingCart(object): def __init__(self, storer=None): self.items = [] self.storer = storer
store
method that uses the storer
to save the cart.def store(self): return self.storer.store_cart(self)
retrieve
method that updates the internal items
by using the storer
.def restore(self, id): self.items = self.storer.retrieve_cart(id).items return self
The specifics of the API of the storer
will be given further down in this recipe.
We need to activate our virtual environment and then install mockito
for this recipe.
Install pinocchio
and figleaf
using the same steps from the Writing a testable story with Voidspace Mock and nose recipe.
With the following steps, we will explore how to use mocking to write a testable story:
recipe31_cart.py
, create a DataAccess
class with empty methods for storing and retrieving shopping carts.class DataAccess(object): def store_cart(self, cart): pass def retrieve_cart(self, id): pass
recipe31.py
for writing test code.DataAccess
.import unittest from copy import deepcopy from recipe31_cart import * from mockito import * class CartThatWeWillSaveAndRestoreUsingMockito(unittest.TestCase): def test_fill_up_a_cart_then_save_it_and_restore_it(self): # Create an empty shopping cart cart = ShoppingCart(DataAccess()) # Add a couple of items cart.add("carton of milk", 2.50) cart.add("frozen pizza", 3.00) self.assertEquals(2, len(cart)) # Create a clone of the cart for mocking # purposes. original_cart = deepcopy(cart) # Save the cart at this point in time into a database # using a mock cart.storer = mock() when(cart.storer).store_cart(cart).thenReturn(1) when(cart.storer).retrieve_cart(1). thenReturn(original_cart) id = cart.store() self.assertEquals(1, id) # Add more items to cart cart.add("cookie dough", 1.75) cart.add("ginger ale", 3.25) self.assertEquals(4, len(cart)) # Restore the cart to the last point in time cart.restore(id) self.assertEquals(2, len(cart)) verify(cart.storer).store_cart(cart) verify(cart.storer).retrieve_cart(1)
nosetests
with the spec
plugin.This recipe is very similar to the earlier recipe Writing a testable story with voidspace mock and nose. For details about mocking and the benefits with regards to BDD, it is very useful to read that recipe.
Let's compare the syntax of Voidspace Mock with mockito to get a feel for the differences. Look at the following voidspace mock block of code.
cart.storer.store_cart = Mock() cart.storer.store_cart.return_value = 1 cart.storer.retrieve_cart = Mock() cart.storer.retrieve_cart.return_value = original_cart
It shows the function store_cart
being mocked.
cart.storer = mock() when(cart.storer).store_cart(cart).thenReturn(1) when(cart.storer).retrieve_cart(1). thenReturn(original_cart)
Mockito approaches this by mocking out the entire storer
object. Mockito originated as a Java mocking tool, which explains its Java-ish APIs like thenReturn
compared with voidspace mock's Pythonic style of return_value
.
Some find this influence from Java on Python's implementation of mockito distasteful. Frankly, I believe that is insufficient reason to discard a library. In the previous example, mockito records the desired behavior in a more succinct fashion, something that would definitely offset the Java-like API.
18.191.186.219