问题描述
我想为我的测试函数构建一个装饰器,它有多种用途.其中之一是帮助向生成的 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 固定装置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!