我想为我的测试功能构建一个装饰器,它有多种用途。其中之一是帮助向生成的junitxml
添加属性。
我知道有一个名为record_property
的{{3}}内置pytest可以做到这一点。如何在装饰器中使用此灯具?
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
文件,因此我不能单独管理所有文件我无法执行。
最后,尝试将灯具直接导入到我的装饰器模块中,然后使用它会导致错误-因此也是不可行的。
感谢您的帮助
答案 0 :(得分:0)
有点晚了,但我在我们的代码库中遇到了同样的问题。我可以找到解决方案,但它相当笨拙,因此我不保证它适用于旧版本或将来会流行。
因此我问是否有更好的解决方案。您可以在这里查看: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",)