Pytest: How to provide fixture parameters at test level?

Created on 14 Jun 2017  路  3Comments  路  Source: pytest-dev/pytest

Hello!

My use case for pytest is for functional testing. What I am dealing on a daily basis is deployed backend applications on real or virtual servers. System Under Test is probed via combination of SSH and REST API calls. I have come across a pattern that I frequently have to use (see simple example below) and was wondering if I am not reinventing the wheel or perhaps there is some idiomatic way to approach this.

I frequently have fixtures that need to be parametrized at test time. These fixtures usually are just "proxies" that instantiate some objects. So far nothing out of ordinary. But Pytest's approach to fixture parametrization is not suitable for me (or so it seems) since I require a mechanism of passing parameters to objects being created by fixtures.

What I have come up is marking test function with a special mark which is used to pass parameters to fixture.

Again, I am not sure if this is the idiomatic way, anyone care to comment or suggest a better approach? Will appreciate any comments!

@pytest.mark.sut_params(role='some_role')
def test_example1(sut):
    assert sut.role == 'some_role'

@pytest.mark.sut_params(name='sut1-uat')
def test_example2(sut):
    assert sut.name == 'sut1-uat'

def test_example3(sut):
    pass

@pytest.fixture
def sut(request):
    if not hasattr(request.function, 'sut_params'):
        raise ValueError('Need to specify sut parameters')
    sut = Sut(**request.function.sut_params.kwargs)
    yield sut
    sut.cleanup()

class Sut:
    """
    Represents system under test

    Provides high-level methods for interaction
    """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def cleanup(self):
        print('DO CLEANUP')
question

Most helpful comment

If your parameters are available at module import time (which seems to be the case by your example), you can use the indirect parameter of parametrize.

import pytest

@pytest.mark.parametrize('sut', [dict(role='some_role')], indirect=True)
def test_example1(sut):
    assert sut.role == 'some_role'

@pytest.mark.parametrize('sut', [dict(name='sut1-uat')], indirect=True)
def test_example2(sut):
    assert sut.name == 'sut1-uat'

def test_example3(sut):
    pass

@pytest.fixture
def sut(request):
    if not hasattr(request, 'param'):
        raise ValueError('Need to specify sut parameters')
    sut = Sut(**request.param)
    yield sut
    sut.cleanup()

class Sut:
    """
    Represents system under test

    Provides high-level methods for interaction
    """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def cleanup(self):
        print('DO CLEANUP')

Alternatively, I would just make the fixture return a factory which is simpler and more flexible for your use case IMHO:

import pytest

def test_example1(create_sut):
    sut = create_sut(role='some_role')
    assert sut.role == 'some_role'


def test_example2(create_sut):
    sut = create_sut(name='sut1-uat')
    assert sut.name == 'sut1-uat'

def test_example3(create_sut):
    pass

@pytest.fixture
def create_sut():
    instances = []
    def create_sut(**kwargs):
        s = Sut(**kwargs)
        instances.append(s)
        return s
    yield create_sut
    for s in instances:
        s.cleanup()

class Sut:
    """
    Represents system under test

    Provides high-level methods for interaction
    """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def cleanup(self):
        print('DO CLEANUP')

All 3 comments

If your parameters are available at module import time (which seems to be the case by your example), you can use the indirect parameter of parametrize.

import pytest

@pytest.mark.parametrize('sut', [dict(role='some_role')], indirect=True)
def test_example1(sut):
    assert sut.role == 'some_role'

@pytest.mark.parametrize('sut', [dict(name='sut1-uat')], indirect=True)
def test_example2(sut):
    assert sut.name == 'sut1-uat'

def test_example3(sut):
    pass

@pytest.fixture
def sut(request):
    if not hasattr(request, 'param'):
        raise ValueError('Need to specify sut parameters')
    sut = Sut(**request.param)
    yield sut
    sut.cleanup()

class Sut:
    """
    Represents system under test

    Provides high-level methods for interaction
    """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def cleanup(self):
        print('DO CLEANUP')

Alternatively, I would just make the fixture return a factory which is simpler and more flexible for your use case IMHO:

import pytest

def test_example1(create_sut):
    sut = create_sut(role='some_role')
    assert sut.role == 'some_role'


def test_example2(create_sut):
    sut = create_sut(name='sut1-uat')
    assert sut.name == 'sut1-uat'

def test_example3(create_sut):
    pass

@pytest.fixture
def create_sut():
    instances = []
    def create_sut(**kwargs):
        s = Sut(**kwargs)
        instances.append(s)
        return s
    yield create_sut
    for s in instances:
        s.cleanup()

class Sut:
    """
    Represents system under test

    Provides high-level methods for interaction
    """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def cleanup(self):
        print('DO CLEANUP')

@nicoddemus Great examples! Frankly I had hard time understanding indirect parametrize from documentation. I think now I get it. So thanks for that!

Coincidently my initial approach to the problem was very much similar to your second example with factory. I still have some dislike but that is mainly cosmetic and very subjective.

No problem, feel free to follow on the approach you think it is best. 馃憤

If you don't have more questions could you please close this then?

Was this page helpful?
0 / 5 - 0 ratings