Building parameterized fixtures

It's common to have a large number of similar examples for a test case. In previous sections, we talked about having users or analysts create spreadsheets with examples of inputs and outputs. This can be helpful for permitting direct input to the software development process. We need our testing tools to work with the CSV example files as directly as possible.

With pytest, we can apply parameters to a fixture. The pytest runner will use each object in the parameter collection to run the test function repeatedly. To build a parameterized fixture, we can use code like the following example:

import csv

with (Path.cwd() / "Chapter_17" / "ch17_data.csv").open() as source:
rdr = csv.DictReader(source)
rtd_cases = list(rdr)

@fixture(params=rtd_cases)
def rtd_example(request):
yield request.param

We've opened the CSV file in a context manager. The file is used to build a reader that transforms each row of data into a dictionary. The keys are the column titles and the values are the strings from each cell in a given row. The final rtd_cases variable will be a list of dictionaries; a type hint of List[Dict[str, str]] would capture the structure.

The rtd_example fixture was built with the params= argument. Each item in the params collection will be provided as a context to a function under test. This means the test will be run several times, and each time it will use a different value from the params sequence. To use this fixture, we'll have a test case like the following example:

from pytest import approx

def
test_rtd(rtd_example):
args = dict(
rate=float_or_none(rtd_example['rate_in']),
time=float_or_none(rtd_example['time_in']),
distance=float_or_none(rtd_example['distance_in']),
)
result = dict(
rate=float_or_none(rtd_example['rate_out']),
time=float_or_none(rtd_example['time_out']),
distance=float_or_none(rtd_example['distance_out']),
)

rtd = RateTimeDistance(**args)

assert rtd.distance == approx(rtd.rate * rtd.time)
assert rtd.rate == approx(result["rate"], abs=1E-2)
assert rtd.time == approx(result["time"])
assert rtd.distance == approx(result["distance"])

This case depends on the rtd_example fixture. Since the fixture had a list of values for the params, this case will be called multiple times; each time, the value of rtd_example will be a different row from the sequence of values. This makes it convenient to write a common test for a variety of input values. 

This test also uses the pytest.approx object. This object is used to wrap floating-point values so the __eq__() method is an approximately equal algorithm instead of a simple exact equality test. This is a very convenient way to ignore the tiny floating-point discrepancies that arise from the truncation of the binary representation of a number.

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

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