【问题标题】:Use pytest fixture in a function decorator在函数装饰器中使用 pytest 夹具
【发布时间】:2018-12-18 20:43:13
【问题描述】:

我想为我的测试函数构建一个装饰器,它有多种用途。其中之一是帮助向生成的junitxml 添加属性。

我知道有一个名为 record_property 的内置 pytest fixture 正是这样做的。如何在我的装饰器中使用这个夹具?

def my_decorator(arg1):
    def test_decorator(func):
        def func_wrapper():
            # hopefully somehow use record_property with arg1 here
            # do some other logic here
            return func()
        return func_wrapper
    return test_decorator

@my_decorator('some_argument')
def test_this():
    pass # do actual assertions etc.

我知道我可以将夹具直接传递给每个测试函数并在测试中使用它,但是我有很多测试,这样做似乎非常多余。

另外,我知道我可以使用 conftest.py 并创建一个自定义标记并在装饰器中调用它,但是我有很多 conftest.py 文件,我不能单独管理所有这些文件,所以我不能强制执行。

最后,尝试将夹具直接导入我的装饰器模块,然后使用它会导致错误 - 所以这也是不行的。

感谢您的帮助

【问题讨论】:

    标签: python-2.7 pytest


    【解决方案1】:

    有点晚了,但我在我们的代码库中遇到了同样的问题。我可以找到解决方案,但它相当老套,所以我不能保证它适用于旧版本或将来会流行。

    因此我问是否有更好的解决方案。你可以在这里查看:How to use pytest fixtures in a decorator without having it as argument on the decorated function

    这个想法基本上是注册被装饰的测试函数,然后欺骗 pytest 认为他们需要在他们的参数列表中的夹具:

    class RegisterTestData:
        # global testdata registry
        testdata_identifier_map = {} # Dict[str, List[str]]
    
        def __init__(self, testdata_identifier, direct_import = True):
            self.testdata_identifier = testdata_identifier
            self.direct_import = direct_import
            self._always_pass_my_import_fixture = False
    
        def __call__(self, func):
            if func.__name__ in RegisterTestData.testdata_identifier_map:
                RegisterTestData.testdata_identifier_map[func.__name__].append(self.testdata_identifier)
            else:
                RegisterTestData.testdata_identifier_map[func.__name__] = [self.testdata_identifier]
    
            # We need to know if we decorate the original function, or if it was already
            # decorated with another RegisterTestData decorator. This is necessary to 
            # determine if the direct_import fixture needs to be passed down or not
            if getattr(func, "_decorated_with_register_testdata", False):
                self._always_pass_my_import_fixture = True
            setattr(func, "_decorated_with_register_testdata", True)
    
            @functools.wraps(func)
            @pytest.mark.usefixtures("my_import_fixture") # register the fixture to the test in case it doesn't have it as argument
            def wrapper(*args: Any, my_import_fixture, **kwargs: Any):
                # Because of the signature of the wrapper, my_import_fixture is not part
                # of the kwargs which is passed to the decorated function. In case the
                # decorated function has my_import_fixture in the signature we need to pack
                # it back into the **kwargs. This is always and especially true for the
                # wrapper itself even if the decorated function does not have
                # my_import_fixture in its signature
                if self._always_pass_my_import_fixture or any(
                    "hana_import" in p.name for p in signature(func).parameters.values()
                ):
                    kwargs["hana_import"] = hana_import
                if self.direct_import:
                    my_import_fixture.import_all()
                return func(*args, **kwargs)
            return wrapper
    
    def pytest_collection_modifyitems(config: Config, items: List[Item]) -> None:
        for item in items:
            if item.name in RegisterTestData.testdata_identifier_map and "my_import_fixture" not in item._fixtureinfo.argnames:
                # Hack to trick pytest into thinking the my_import_fixture is part of the argument list of the original function
                # Only works because of @pytest.mark.usefixtures("my_import_fixture") in the decorator
                item._fixtureinfo.argnames = item._fixtureinfo.argnames + ("my_import_fixture",)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-09
      • 2014-09-23
      • 2019-11-06
      • 2022-01-08
      • 2016-07-09
      • 2016-08-17
      • 1970-01-01
      • 2019-02-03
      相关资源
      最近更新 更多