blitz1616
blitz1616

Reputation: 401

Pytest Using Parametized Fixture VS. pytest.mark.parametrize

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:

Method 1

Advantages

Disadvantages

method 2

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

Answers (1)

forty_two
forty_two

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

Related Questions