py.test:将参数传递给fixture函数

mag*_*gie 92 python fixtures pytest

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

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

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

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
Run Code Online (Sandbox Code Playgroud)

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

通常我可以使用某种设置函数(xUnit-style)为每个测试方法执行此操作.但我希望获得某种重用.有没有人知道这是否可以使用灯具?

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

@pytest.fixture(scope="module", params=["merlinux.eu", "mail.python.org"])
Run Code Online (Sandbox Code Playgroud)

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

imi*_*ric 128

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

在你的情况下,你会有:

@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
Run Code Online (Sandbox Code Playgroud)

  • **这应该是公认的答案.**[官方文档](http://docs.pytest.org/en/latest/example/parametrize.html#deferring-the-setup-of-parametrized-resources) "间接"关键字参数无疑是稀疏且不友好的,这可能是这种基本技术含糊不清的原因.我已经多次搜索py.test网站了解这个特性 - 只是空洞,老了,糊涂了.苦味是一个被称为持续整合的地方._感谢Odin for Stackoverflow._ (37认同)
  • 请注意,此方法会更改测试的名称以包含参数,这可能是需要的,也可能不是。`test_tc1` 变为 `test_tc1[tester0]`。 (4认同)
  • 我尝试使用此解决方案但是在传递多个参数或使用除请求之外的变量名称时出现问题.我最终使用了@Iguananaut的解决方案. (2认同)
  • 所以 `indirect=True` 将参数传递给所有被调用的装置,对吗?因为 [文档](https://docs.pytest.org/en/latest/example/parametrize.html#apply-indirect-on-particular-arguments) 明确命名了用于间接参数化的装置,例如名为` x`:`间接=['x']` (2认同)
  • 好的,根据[官方文档](https://docs.pytest.org/en/latest/reference.html#pytest-mark-parametrize),“True”和“False”也适用于“indirect”关键字关于参数化。 (2认同)

Igu*_*aut 87

我有一个类似的问题 - 我有一个夹具调用test_package,后来我想在特定的测试中运行它时能够将一个可选的参数传递给该夹具.例如:

@pytest.fixture()
def test_package(request, version='1.0'):
    ...
    request.addfinalizer(fin)
    ...
    return package
Run Code Online (Sandbox Code Playgroud)

(对于这些目的而言,夹具的功能或返回的对象类型无关紧要package).

然后,需要以某种方式在测试函数中使用此夹具,以便我还可以指定该version夹具的参数以与该测试一起使用.目前这是不可能的,但可能会成为一个很好的功能.

与此同时,使我的夹具简单地返回一个完成夹具先前完成的所有工作的功能,这很容易,但允许我指定version参数:

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

    return make_test_package
Run Code Online (Sandbox Code Playgroud)

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

def test_install_package(test_package):
    package = test_package(version='1.1')
    ...
    assert ...
Run Code Online (Sandbox Code Playgroud)

等等.

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"
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 关于这个主题的一篇不错的短文:https://alysivji.github.io/pytest-fixures-with-function-arguments.html (2认同)

din*_*igo 21

您还可以使用闭包,这将为您提供更全面的命名和对参数的控制。它们比隐式参数化request中使用的参数更“显式” :

@pytest.fixture
def tester():
    # Create a closure on the Tester object
    def _tester(first_param, second_param):
        # use the above params to mock and instantiate things
        return MyTester(first_param, second_param)
    
    # Pass this closure to the test
    yield _tester 


@pytest.mark.parametrize(['param_one', 'param_two'], [(1,2), (1000,2000)])
def test_tc1(tester, param_one, param_two):
    # run the closure now with the desired params
    my_tester = tester(param_one, param_two)
    # assert code here
Run Code Online (Sandbox Code Playgroud)

我用它来构建可配置的装置。


Yuk*_*oda 19

我找不到任何文件,但是,它似乎在最新版本的 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
Run Code Online (Sandbox Code Playgroud)

  • 为了澄清,@Maspe36 表示“Nadège”链接的 PR 已恢复。因此,这个未记录的功能(我认为它仍然没有记录?)仍然存在。 (7认同)
  • 感谢您指出这一点——这似乎是最干净的解决方案。我认为这在早期版本中是不可能的,但很明显现在可以了。您知道[官方文档](https://docs.pytest.org/en/5.3.1/fixture.html#)中是否提到了此表单?我找不到类似的东西,但它显然有效。我已经更新了[我的答案](/sf/answers/1999947421/)以包含这个示例,谢谢。 (2认同)
  • 如果您查看 https://github.com/pytest-dev/pytest/issues/5712 和相关(合并)PR,我认为该功能是不可能的。 (2认同)

hpk*_*k42 11

您可以从fixture函数(以及Tester类)访问请求模块/类/函数,查看与fixture函数请求测试上下文的交互.因此,您可以在类或模块上声明一些参数,并且测试仪夹具可以拾取它.

  • 我知道我可以这样做:(来自文档)@ pytest.fixture(scope ="module",params = ["merlinux.eu","mail.python.org"])但我需要在测试模块.如何动态地将params添加到灯具? (3认同)
  • 重点不是必须*与夹具函数的请求测试上下文*交互,而是要有一个明确定义的方法将参数传递给夹具函数.Fixture函数不应该知道一种请求测试上下文,只是为了能够接收具有商定名称的参数.例如,一个希望能够写`@fixture高清my_fixture(请求)`然后`@pass_args(ARG1 = ...,ARG2 = ...)高清测试(my_fixture)`并获得`my_fixture这些ARGS ()`喜欢这个`arg1 = request.arg1,arg2 = request.arg2`.现在py.test中有可能出现这种情况吗? (2认同)

sma*_*rie 9

改进一点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
Run Code Online (Sandbox Code Playgroud)

请注意,pytest-cases还提供@fixture允许您直接在灯具上使用参数化标记,而不必使用@pytest.fixture(params=...)

from pytest_cases import fixture, parametrize

@fixture
@parametrize("var", [['var1', 'var2']], ids=str)
def tester(var):
    """Create tester object"""
    return MyTester(var)
Run Code Online (Sandbox Code Playgroud)

并且@parametrize_with_cases,可以让你从可能的类,甚至一个单独的模块进行分组“功能的情况下,” SOURCE您的参数。有关详细信息,请参阅文档。顺便说一下,我是作者;)


squ*_*rel 6

我做了一个有趣的装饰器,它允许编写这样的装置:

@fixture_taking_arguments
def dog(request, /, name, age=69):
    return f"{name} the dog aged {age}"
Run Code Online (Sandbox Code Playgroud)

在这里,左侧/有其他灯具,右侧有使用以下方法提供的参数:

@dog.arguments("Buddy", age=7)
def test_with_dog(dog):
    assert dog == "Buddy the dog aged 7"
Run Code Online (Sandbox Code Playgroud)

这与函数参数的工作方式相同。如果您不提供age参数,则使用默认参数 , 69。如果您不提供name或省略dog.arguments装饰器,您将获得常规的TypeError: dog() missing 1 required positional argument: 'name'. 如果你有另一个需要参数的装置name,它与这个没有冲突。

还支持异步装置。

此外,这为您提供了一个不错的设置计划:

$ pytest test_dogs_and_owners.py --setup-plan

SETUP    F dog['Buddy', age=7]
...
SETUP    F dog['Champion']
SETUP    F owner (fixtures used: dog)['John Travolta']
Run Code Online (Sandbox Code Playgroud)

一个完整的例子:

@dog.arguments("Buddy", age=7)
def test_with_dog(dog):
    assert dog == "Buddy the dog aged 7"
Run Code Online (Sandbox Code Playgroud)

装饰器的代码:

$ pytest test_dogs_and_owners.py --setup-plan

SETUP    F dog['Buddy', age=7]
...
SETUP    F dog['Champion']
SETUP    F owner (fixtures used: dog)['John Travolta']
Run Code Online (Sandbox Code Playgroud)

  • @GeorgeShuklin 好吧,我继续为此提出了一个问题,以及更多疯狂的想法 https://github.com/pytest-dev/pytest/issues/8109 (2认同)

Mat*_*let 5

另一种方法是使用请求对象来访问定义测试函数的模块或类中定义的变量。

@pytest.mark.parametrize()这样,如果您想为类/模块的所有测试函数传递相同的变量,则不必在测试类的每个函数上重用装饰器。

类变量的示例:

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


class TestIt:
    tester_args = ['var1', 'var2']

    def test_tc1(self, tester):
       tester.dothis()

    def test_tc2(self, tester):
       tester.dothat()
Run Code Online (Sandbox Code Playgroud)

这样testertest_tc1 和 test_tc2 的对象都将使用tester_args参数进行初始化。

您还可以使用:

  • request.function访问 test_tc1 函数,
  • request.instance访问 TestIt 类实例,
  • request.module访问模块 TestIt 的定义
  • 等等(参考request文档)


小智 5

另一种方法是使用自定义标记。它看起来比代码中的参数化更好,没有反映在测试名称中,并且也是可选的(如果不存在这样的标记,可以通过引发失败来定义为不可选)

例如:

@pytest.fixture
def loaded_dll(request):
    dll_file = None
    for mark in request.node.iter_markers("dll_file"):
        if mark.args:
            if dll_file is not None:
                pytest.fail("Only one dll_file can be mentioned in marks")
            dll_file = mark.args[0]
    if dll_file is None:
        pytest.fail("dll_file is a required mark")
    return some_dll_load(dll_file)

@pytest.mark.dll_file("this.dll")
def test_this_dll(loaded_dll):
    ...
Run Code Online (Sandbox Code Playgroud)

当我需要一个模拟 ssh 客户端的装置并想要测试不同的可能输出时,我在测试中使用了它,我可以使用标记来决定每个测试的输出。

请注意,如果是供个人使用,则不需要未通过测试的故障保存机制。