Using fixtures for test setup

The test setup and teardown features of pytest are often handled by @fixture functions. These functions form the fixture into which a unit is connected for testing. In the realm of hardware testing, it might also be called a test harness or test bench.

Fixtures can be used to do any kind of setup or teardown related to a test. Because of the way pytest invokes a test, it implicitly calls the fixture functions, simplifying our test code considerably. The fixtures can reference other fixtures, letting us create composite objects that can help to isolate the unit being tested.

Previously, we looked at two complex test case subclasses: TestDeckBuild and TestDeckDeal. These two test cases covered separate features of the Deck3 class definition. We can build similar test cases using a common fixture. Here's the fixture definition:

import unittest.mock
from types import SimpleNamespace
from pytest import fixture

@fixture
def deck_context():
mock_deck = [
getattr(unittest.mock.sentinel, str(x))
for x in range(52)
]
mock_card = unittest.mock.Mock(side_effect=mock_deck)
mock_rng = unittest.mock.Mock(
wraps=random.Random,
shuffle=unittest.mock.Mock(return_value=None)
)
return SimpleNamespace(**locals())

The deck_context() function, creates the following three mock objects:

  • The mock_deck object is a list of 52 individual mock.sentinel objects. Each sentinel object is customized by getting a unique attribute of the sentinel. The attribute name is a string built from the integer values in range(52). There will be objects with names such as mock.sentinel.0. This is not a valid syntax for a simple Python attribute reference in source code, but we only need to be sure the sentinel is unique.
  • The mock_card object is a mock with a side_effect. This will behave like a function. Each time it's invoked, it will return another value from the list provided to the side_effect parameter. This can be used to simulate a function that reads values from files or a network connection.
  • The mock_rng object is a wrapped version of the random.Random class. This will behave like a random object, except for two features. First, the shuffle() method doesn't do anything. And, second, the Mock wrapper will track individual calls to the methods of this object so we can determine whether it's being used properly by the unit being tested.

The return step packages all of the local variables into a single SimpleNamespace object. This object lets us use syntax such as deck_context.mock_card to refer to the Mock function. We can use this fixture in a test function. The following is an example:

def test_deck_build(deck_context):
d = Deck3(
size=1,
random=deck_context.mock_rng,
card_factory=deck_context.mock_card
)
deck_context.mock_rng.shuffle.assert_called_once_with(d)
assert 52 == len(deck_context.mock_card.mock_calls)
expected = [
unittest.mock.call(r, s) for r in range(1, 14) for s in iter(Suit)
]
assert expected == deck_context.mock_card.mock_calls

This test references the deck_context fixture. Nothing special is done in the code; pytest will implicitly evaluate the function, and the resulting SimpleNamespace object will be the value of the deck_context parameter. The mapping between the parameter and the fixture is very simple: all parameter names must be the names of fixture functions, and these functions are evaluated automatically.

The test builds a Deck3 instance using mock objects for the random parameter and the card_factory parameter. Once the Deck3 instance is built, we can examine the mock objects to see if they had the proper number of calls with the expected argument values.

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

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