我用的是py。测试一些封装在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

有可能这样实现吗,或者有更优雅的方式吗?

通常,我可以使用某种设置函数(xunit风格)为每个测试方法执行此操作。但我想获得某种重用。有人知道这在固定装置上是否可行吗?

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

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

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


当前回答

您还可以使用闭包,这将为您提供更全面的参数命名和控制。它们比隐式参数化中使用的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

我使用它来构建可配置的fixture。

其他回答

您可以从fixture函数(因此也可以从您的Tester类)访问请求的模块/类/函数,参见从fixture函数与请求测试上下文交互。因此,您可以在类或模块上声明一些参数,并且测试装置可以拾取它。

更新:由于这是这个问题的公认答案,有时仍然会被点赞,我应该添加一个更新。尽管我最初的答案(下面)是在旧版本的pytest中做到这一点的唯一方法,因为其他人已经注意到pytest现在支持fixture的间接参数化。例如,你可以这样做(通过@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

然而,尽管这种形式的间接参数化是明确的,正如@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

我不知道这种形式的确切语义是什么,但似乎pytest.mark.parametrize认识到,尽管test_tc1方法不接受名为test_arg的参数,但它使用的测试fixture接受,因此它通过测试fixture传递参数化参数。


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

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

(对于这些目的,fixture做什么或返回的包是什么类型的对象并不重要)。

然后,希望以某种方式在测试函数中使用这个fixture,这样我也可以指定该fixture的版本参数,以便与该测试一起使用。这目前是不可能的,尽管可能是一个不错的功能。

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

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

    return make_test_package

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

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

等等。

OP尝试的解决方案朝着正确的方向前进,正如@hpk42的回答所表明的那样,我的测试者。__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"

然后使用这个来实现像这样的夹具:

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

如果需要,可以稍微重新构造MyTester类,以便在创建后更新它的.args属性,以调整单个测试的行为。

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

例如:

@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):
    ...

当我需要一个模拟ssh客户机的fixture并希望测试不同的可能输出时,我使用了这个标记进行测试,我可以使用标记确定每个测试的输出。

注意,如果用于个人使用,则不需要失败测试的failsave机制。

您还可以使用闭包,这将为您提供更全面的参数命名和控制。它们比隐式参数化中使用的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

我使用它来构建可配置的fixture。

这实际上在py中是原生支持的。通过间接参数化进行测试。

在你的情况下,你会:

@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