在函数装饰器中使用 pytest 固定装置 [英] Use pytest fixture in a function decorator

查看:53
本文介绍了在函数装饰器中使用 pytest 固定装置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to build a decorator for my test functions which has several uses. One of them is helping to add properties to the generated junitxml.

我知道有一个 fixture 内置 pytest对于这个叫做 record_property 的东西,它就是这样做的.我如何在我的装饰器中使用这个装置?

I know there's a fixture built-in pytest for this called record_property that does exactly that. How can I use this fixture inside my decorator?

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.

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

I know I can pass the fixture directly into every test function and use it in the tests, but I have a lot of tests and it seems extremely redundant to do this.

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

Also, I know I can use conftest.py and create a custom marker and call it in the decorator, but I have a lot of conftest.py files and I don't manage all of them alone so I can't enforce it.

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

Lastly, trying to import the fixture directly in to my decorator module and then using it results in an error - so that's a no go also.

感谢您的帮助

推荐答案

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

It's a bit late but I came across the same problem in our code base. I could find a solution to it but it is rather hacky, so I wouldn't give a guarantee that it works with older versions or will prevail in the future.

因此我问是否有更好的解决方案.您可以在此处查看:如何在装饰器中使用 pytest 固定装置而不将其作为装饰函数的参数

Hence I asked if there is a better solution. You can check it out here: How to use pytest fixtures in a decorator without having it as argument on the decorated function

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

The idea is to basically register the test functions which are decorated and then trick pytest into thinking they would require the fixture in their argument list:

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",)

这篇关于在函数装饰器中使用 pytest 固定装置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆