Function factories for random data with py.test
Py.test has an excellent facility called fixtures, which are a pythonic way of doing dependency injection.
Quick example:
from random import randint
import pytest
@pytest.fixture
def number():
return randint(10)
@pytest.fixture
def structure(number):
return {'name': 'something',
'number': number}
def test_something(structure, number):
assert structure['number'] == number
The random number will only be generated once, and during the test, it will always be that number wherever it is injected. So here the structure
fixture sees the same number
as test_something
. But if we run it again, a new number will be generated.
This is great if you want to generate a single piece of random data, and check it later. But often you actually need several pieces of random data, and you don't want to create fixtures like random_number1
, random_number2
etc.
A nice strategy that I've found works is instead of having the fixture return the data directly, instead the fixture returns a function that has access to other fixtures, and that keeps track of what it's generated.
This looks something like the following example:
@pytest.fixture
def number():
return randint(10)
@pytest.fixture
def structure(number):
def _structure(name):
'''This function gets injected'''
retval = {
'name': name,
'number': randint(number, 20)
}
# register the result in the function
_structure.results[name] = retval
return retval
# before injecting, we create the registry
# on the function
_structure.results = {}
return _structure
@pytest.fixture
def big_structure(structure):
return {
'foo': structure('bar')
}
The key points are:
- Have the fixture return a function, instead of the data
- The function gets access to any other fixtures you want (here we use the
number
fixture as a floor for a new random number. - Add a registry attribute to the function, to keep track of results
def test_something(structure, number, big_structure):
result = structure('some_name')
assert result['number'] >= number
assert structure.results['some_name'] == result
assert structure.results['bar'] == big_structure['foo']
Why does the _structure
function register its own results? Because you may need to use the structure
fixture in other fixtures. Here big_structure
uses structure
and if we want to know what was generated, we can just look in the registry.
Additionally, since the function is created per test the registry is automatically cleared out for every test.