Python 将参数传递给夹具函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18011902/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 09:42:55  来源:igfitidea点击:

Pass a parameter to a fixture function

pythonfixturespytest

提问by maggie

I am using py.test to test some DLL code wrapped in a python class MyTester. For validating purpose I need to log some test data during the tests and do more processing afterwards. As I have many test_... files I want to reuse the tester object creation (instance of MyTester) for most of my tests.

我正在使用 py.test 来测试一些包含在 Python 类 MyTester 中的 DLL 代码。出于验证目的,我需要在测试期间记录一些测试数据,然后进行更多处理。由于我有很多 test_... 文件,我想在大多数测试中重用测试器对象创建(MyTester 的实例)。

As the tester object is the one which got the references to the DLL's variables and functions I need to pass a list of the DLL's variables to the tester object for each of the test files (variables to be logged are the same for a test_... file). The content of the list is shall be used to log the specified data.

由于测试对象是获得对 DLL 变量和函数的引用的对象,因此我需要将 DLL 变量列表传递给每个测试文件的测试对象(要记录的变量对于 test_.. 。 文件)。列表的内容应用于记录指定的数据。

My idea is to do it somehow like this:

我的想法是以某种方式这样做:

import pytest

class MyTester():
    def __init__(self, arg = ["var0", "var1"]):
        self.arg = arg
        # self.use_arg_to_init_logging_part()

    def dothis(self):
        print "this"

    def dothat(self):
        print "that"

# located in conftest.py (because other test will reuse it)

@pytest.fixture()
def tester(request):
    """ create tester object """
    # how to use the list below for arg?
    _tester = MyTester()
    return _tester

# located in test_...py

# @pytest.mark.usefixtures("tester") 
class TestIt():

    # def __init__(self):
    #     self.args_for_tester = ["var1", "var2"]
    #     # how to pass this list to the tester fixture?

    def test_tc1(self, tester):
       tester.dothis()
       assert 0 # for demo purpose

    def test_tc2(self, tester):
       tester.dothat()
       assert 0 # for demo purpose

Is it possible to achieve it like this or is there even a more elegant way?

有可能像这样实现它还是有更优雅的方式?

Usually I could do it for each test method with some kind of setup function (xUnit-style). But I want to gain some kind of reuse. Does anyone know if this is possible with fixtures at all?

通常我可以使用某种设置函数(xUnit 样式)为每个测试方法执行此操作。但我想获得某种重用。有谁知道这是否可以通过固定装置实现?

I know I can do something like this: (from the docs)

我知道我可以做这样的事情:(来自文档)

@pytest.fixture(scope="module", params=["merlinux.eu", "mail.python.org"])

But I need to the parametrization directly in the test module. Is it possible to access the params attribute of the fixture from the test module?

但是我需要直接在测试模块中进行参数化。 是否可以从测试模块访问夹具的 params 属性?

采纳答案by Iguananaut

Update:Since this the accepted answer to this question and still gets upvoted sometimes, I should add an update. Although my original answer (below) was the only way to do this in older versions of pytest as othershave notedpytest now supports indirect parametrization of fixtures. For example you can do something like this (via @imiric):

更新:由于这是对这个问题的公认答案,并且有时仍然会得到支持,我应该添加一个更新。尽管我的原始答案(如下)是在旧版本的 pytest 中执行此操作的唯一方法,因为其他人已经指出pytest 现在支持设备的间接参数化。例如,您可以执行以下操作(通过 @imiric):

# test_parameterized_fixture.py
import pytest

class MyTester:
    def __init__(self, x):
        self.x = x

    def dothis(self):
        assert self.x

@pytest.fixture
def tester(request):
    """Create tester object"""
    return MyTester(request.param)


class TestIt:
    @pytest.mark.parametrize('tester', [True, False], indirect=['tester'])
    def test_tc1(self, tester):
       tester.dothis()
       assert 1
$ pytest -v test_parameterized_fixture.py
================================================================================= test session starts =================================================================================
platform cygwin -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: .
collected 2 items

test_parameterized_fixture.py::TestIt::test_tc1[True] PASSED                                                                                                                    [ 50%]
test_parameterized_fixture.py::TestIt::test_tc1[False] FAILED

However, although this form of indirect parametrization is explicit, as @Yukihiko Shinoda points outit now supports a form of implicit indirect parametrization (though I couldn't find any obvious reference to this in the official docs):

但是,尽管这种间接参数化形式是明确的,但正如@Yukihiko Shinoda指出的那样,它现在支持一种隐式间接参数化形式(尽管我在官方文档中找不到任何明显的参考资料):

# test_parameterized_fixture2.py
import pytest

class MyTester:
    def __init__(self, x):
        self.x = x

    def dothis(self):
        assert self.x

@pytest.fixture
def tester(tester_arg):
    """Create tester object"""
    return MyTester(tester_arg)


class TestIt:
    @pytest.mark.parametrize('tester_arg', [True, False])
    def test_tc1(self, tester):
       tester.dothis()
       assert 1
$ pytest -v test_parameterized_fixture2.py
================================================================================= test session starts =================================================================================
platform cygwin -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: .
collected 2 items

test_parameterized_fixture2.py::TestIt::test_tc1[True] PASSED                                                                                                                   [ 50%]
test_parameterized_fixture2.py::TestIt::test_tc1[False] FAILED

I don't know exactly what are the semantics of this form, but it seems that pytest.mark.parametrizerecognizes that although the test_tc1method does not take an argument named tester_arg, the testerfixture that it's using does, so it passes the parametrized argument on through the testerfixture.

我不确切知道这种形式的语义是什么,但它似乎pytest.mark.parametrize认识到虽然该test_tc1方法不接受名为 的参数,但它使用tester_argtester夹具确实如此,因此它通过tester夹具传递参数化参数。



I had a similar problem--I have a fixture called test_package, and I later wanted to be able to pass an optional argument to that fixture when running it in specific tests. For example:

我有一个类似的问题——我有一个名为 的夹具test_package,后来我希望能够在特定测试中运行它时将可选参数传递给该夹具。例如:

@pytest.fixture()
def test_package(request, version='1.0'):
    ...
    request.addfinalizer(fin)
    ...
    return package

(It doesn't matter for these purposes what the fixture does or what type of object the returned package) is.

(对于这些目的,夹具的作用或返回的对象类型无关紧要package)。

It would then be desirable to somehow use this fixture in a test function in such a way that I can also specify the versionargument to that fixture to use with that test. This is currently not possible, though might make a nice feature.

然后希望以某种方式在测试函数中使用这个夹具,这样我也可以指定该version夹具的参数以用于该测试。这目前是不可能的,但可能会成为一个不错的功能。

In the meantime it was easy enough to make my fixture simply return a functionthat does all the work the fixture previously did, but allows me to specify the versionargument:

与此同时,很容易让我的夹具简单地返回一个函数,该函数完成夹具之前所做的所有工作,但允许我指定version参数:

@pytest.fixture()
def test_package(request):
    def make_test_package(version='1.0'):
        ...
        request.addfinalizer(fin)
        ...
        return test_package

    return make_test_package

Now I can use this in my test function like:

现在我可以在我的测试函数中使用它,例如:

def test_install_package(test_package):
    package = test_package(version='1.1')
    ...
    assert ...

and so on.

等等。

The OP's attempted solution was headed in the right direction, and as @hpk42's answersuggests, the MyTester.__init__could just store off a reference to the request like:

OP 尝试的解决方案朝着正确的方向发展,正如@hpk42 的回答所暗示的那样,它MyTester.__init__可以只存储对请求的引用,例如:

class MyTester(object):
    def __init__(self, request, arg=["var0", "var1"]):
        self.request = request
        self.arg = arg
        # self.use_arg_to_init_logging_part()

    def dothis(self):
        print "this"

    def dothat(self):
        print "that"

Then use this to implement the fixture like:

然后使用它来实现夹具,如:

@pytest.fixture()
def tester(request):
    """ create tester object """
    # how to use the list below for arg?
    _tester = MyTester(request)
    return _tester

If desired the MyTesterclass could be restructured a bit so that its .argsattribute can be updated after it has been created, to tweak the behavior for individual tests.

如果需要,MyTester可以对类进行一些重构,以便.args在创建后更新其属性,以调整各个测试的行为。

回答by hpk42

You can access the requesting module/class/function from fixture functions (and thus from your Tester class), see interacting with requesting test context from a fixture function. So you could declare some parameters on a class or module and the tester fixture can pick it up.

您可以从夹具函数(因此从您的测试器类)访问请求模块/类/函数,请参阅与来自夹具函数的请求测试上下文交互。因此,您可以在类或模块上声明一些参数,并且测试夹具可以选择它。

回答by imiric

This is actually supported natively in py.test via indirect parametrization.

这实际上是通过间接参数化在 py.test 中原生支持的。

In your case, you would have:

在您的情况下,您将拥有:

@pytest.fixture
def tester(request):
    """Create tester object"""
    return MyTester(request.param)


class TestIt:
    @pytest.mark.parametrize('tester', [['var1', 'var2']], indirect=True)
    def test_tc1(self, tester):
       tester.dothis()
       assert 1

回答by smarie

To improve a little bit imiric's answer: another elegant way to solve this problem is to create "parameter fixtures". I personally prefer it over the indirectfeature of pytest. This feature is available from pytest_cases, and the original idea was suggested by Sup3rGeo.

为了改善一点imiric 的答案:解决这个问题的另一种优雅方法是创建“参数装置”。我个人更喜欢它在indirect的功能pytest。此功能可从 获得pytest_cases,最初的想法是由Sup3rGeo提出的。

import pytest
from pytest_cases import param_fixture

# create a single parameter fixture
var = param_fixture("var", [['var1', 'var2']], ids=str)

@pytest.fixture
def tester(var):
    """Create tester object"""
    return MyTester(var)

class TestIt:
    def test_tc1(self, tester):
       tester.dothis()
       assert 1

Note that pytest-casesalso provides @pytest_fixture_plusthat allow you to use parametrization marks on your fixtures, and @cases_datathat allow you to source your parameters from functions in a separate module. See docfor details. I'm the author by the way ;)

请注意,pytest-cases它还提供@pytest_fixture_plus了允许您在灯具上使用参数化标记的功能,并@cases_data允许您从单独模块中的函数中获取参数。有关详细信息,请参阅文档。顺便说一下,我是作者;)

回答by Yukihiko Shinoda

I couldn't find any document, however, it seems to work in latest version of pytest.

我找不到任何文件,但是,它似乎在最新版本的 pytest 中工作。

@pytest.fixture
def tester(tester_arg):
    """Create tester object"""
    return MyTester(tester_arg)


class TestIt:
    @pytest.mark.parametrize('tester_arg', [['var1', 'var2']])
    def test_tc1(self, tester):
       tester.dothis()
       assert 1