Reputation: 401
I'm writing some unit tests using Pytest and came across two ways to parameterize test inputs. One is using parametereized fixtures and the other is using the pytest.mark.parametrize
method.
The two examples I have are:
# method 1
def tokens():
yield from ["+", "*", "?"]
@pytest.mark.parametrize("token", tokens())
def test_stuff(token):
assert stuff
and
# method 2
@pytest.fixture(params=["+", "*", "?"])
def token(request):
return request.param
def test_stuff(token):
assert stuff
Both have different advantages and disadvantages from what I can tell:
Advantages
Disadvantages
Advantages
Disadvantages
I'm still new to PyTest so maybe there is a way around the disadvantages that I listed above for each method but given those I have been having a hard time trying to decide which one to use. I would guess that the intended way to do what I am trying to do is to use @pytest.mark.parametrize
but when passing only a single parameter having less boilerplate code by using a fixture seems like a big advantage. Can anyone tell me a reason not to do it this way or is this a perfectly valid use case?
Upvotes: 12
Views: 5595
Reputation: 534
As pk786 mentions in his comment, you should use a fixture "...if you have something to set up and teardown for the test or using (the) same dataset for multiple tests then use fixture".
For example, you may want to load several datasets that you test against in different test functions. Using a fixture allows you to only load these datasets once and share them across the test functions. You can use params
argument of @pytest.fixture
to load and cache each dataset. Then, the test functions that use those fixtures will run against each loaded dataset. In code, this might look something like:
import json
import pytest
test_files = ["test_file1.json", "test_file2.json"]
@pytest.fixture(params=test_files)
def example_data(request):
with open(request.param, "r") as f:
data = json.load(f)
return data
def test_function1(example_data):
# run test with example data.
# this test will be run once for each file in the list `test_files` above.
...
def test_function2(example_data):
# run a different test with example data.
# this test will be run once for each file in the list `test_files` above.
# this test takes advantage of automatic caching mechanisms of fixtures...
# ...so that data is not loaded again.
...
Alternatively, as pk786 states, "If you are using a set of data only once, then @pytest.mark.parametrize should be the approach".
This statement applies to the example that you provided since you are not performing any setup in the fixture that you need to share across test. In this case, even if you are using the "tokens" across multiple tests, I would consider decorating each function with @pytest.mark.parameterize
since I believe this approach more explicitly states your intent and will be easier to understand for anyone else reading your code. This would look like this:
...
def tokens():
yield from ["+", "*", "?"]
@pytest.mark.parametrize("token", tokens())
def test_stuff(token):
assert stuff
@pytest.mark.parametrize("token", tokens())
def test_other_stuff(token)
assert other_stuff
Upvotes: 14