在文章《无需修改代码,深入探究 pytest 如何自动查找并加载三方插件》中最后提到了,到底pytest_repeat插件的具体功能是如何实现的呢?
相信具体了解了该插件,其他三方插件也可以很快了解它内部运行机制。
不使用pytest_repeat插件如何实现重复执行用例
不使用pytest_repeat插件如何实现重复执行用例
最笨的办法,当然是运行多次,但这显然不是我们需要的。
在装饰器复习这篇文章中,我们复习了装饰器相关知识点,知道装饰器可以在不修改原始代码的情况下,动态的增加功能或修改函数行为。
显然,这里我们就可以使用装饰器来实现重复功能。
def repeat(nums: int = 2):def wrapper(func):@functools.wraps(func)def decorator(*args, **kwargs):for i in range(nums):func(*args, **kwargs)return decoratorreturn wrapper
这段代码很好理解,定义了带有自定义参数的装饰器,表示装饰器内部函数执行的次数。
这样在用例上使用@repeat()装饰器就可以达到用例重复运行的目的。
但是统计结果仍然为1条用例。使用过pytest_repeat的同学知道它的统计结果是多条用例?那是如何做的呢,通过源码一探究竟。
pytest_repeat如何实现重复执行
源码直达:https://github.com/pytest-dev/pytest-repeat/blob/v0.9.1/pytest_repeat.py
源码解读
def pytest_addoption(parser):parser.addoption('--count',action='/pytest-dev/pytest-repeat/blob/v0.9.1/store',default=1,type=int,help='Number of times to repeat each test')parser.addoption('--repeat-scope',action='/pytest-dev/pytest-repeat/blob/v0.9.1/store',default='function',type=str,choices=('function', 'class', 'module', 'session'),help='Scope for repeating tests')
这段代码定义了两个命令行选项:
--count:用于指定每个测试用例要重复执行的次数。action=store表示将值存储在命令行参数中。--repeat-scope:用于指定重复测试用例的作用域,可以选择function、class、module或session。默认值是
function。action=store表示将值存储在命令行参数中。
这两个选项都是通过parser.addoption方法添加到pytest的命令行解析器中的。
当运行pytest并指定--count、--repeat-scope参数时,pytest-repeat插件将获取这些参数并自动为测试用例生成多个重复执行的实例。
例如,如果运行以下命令:
pytest --count=2 --repeat-scope=functionpytest-repeat将会在执行test_my_function测试用例时,自动执行该测试用例两次。
action=store是argparse模块中的一个参数,它指定了在命令行解析过程中如何处理选项的值。
具体地说,action=store表示将选项的值存储在命令行参数中。
当使用parser.addoption方法添加选项到命令行解析器时,通过指定action=store,选项的值将被存储在解析结果中,可以通过相应的属性来获取这些值。
例如,当运行pytest命令时,指定的--count和--repeat-scope选项的值会存储在命令行参数中。
你可以使用request.config.getoption方法来获取这些存储的值,例如:
def test_example(request):count = request.config.getoption('--count')# count = request.config.option.count 这样也能获取repeat_scope = request.config.getoption('--repeat-scope')# repeat_scope = request.config.option.repeat_scope# 使用获取到的值进行后续操作
在上面的示例代码中,使用request.config.getoption方法从命令行参数中获取了--count和--repeat-scope的值,并分别存储在count和repeat_scope变量中。
总结:action=store是argparse模块中的一个参数,用于指定将选项的值存储在命令行参数中。
在pytest中,通过使用request.config.getoption方法可以获取存储在命令行参数中的选项值。
def pytest_configure(config):config.addinivalue_line('markers','repeat(n): run the given test function `n` times.')
这个函数在pytest的配置阶段被调用,通过调用config.addinivalue_line()将自定义标记'repeat(n)'添加到pytest的标记列表中。
'repeat(n)'标记可以用于指定一个测试函数需要重复运行的次数。
@pytest.fixturedef __pytest_repeat_step_number(request):marker = request.node.get_closest_marker("repeat")count = marker and marker.args[0] or request.config.option.countif count > 1:try:return request.paramexcept AttributeError:if issubclass(request.cls, TestCase):warnings.warn("Repeating unittest class tests not supported")else:raise UnexpectedError("This call couldn't work with pytest-repeat. ""Please consider raising an issue with your usage.")
这个fixture函数用于获取当前的重复运行步骤编号。它首先检查测试函数是否被'repeat'标记装饰,并从标记中获取重复次数。
如果没有标记,则使用命令行参数中的--count参数作为默认值。
@pytest.hookimpl(trylast=True)def pytest_generate_tests(metafunc):count = metafunc.config.option.countm = metafunc.definition.get_closest_marker('repeat')if m is not None:count = int(m.args[0])if count > 1:metafunc.fixturenames.append("__pytest_repeat_step_number")def make_progress_id(i, n=count):return '{0}-{1}'.format(i + 1, n)scope = metafunc.config.option.repeat_scopemetafunc.parametrize('__pytest_repeat_step_number',range(count),indirect=True,ids=make_progress_id,scope=scope)
这个pytest_generate_tests钩子函数会在pytest收集到所有测试函数之后被调用,并且它被设置为trylast=True,以确保在其他钩子函数执行完毕之后再执行。
首先,代码获取了
metafunc.config.option.count的值,该值表示测试用例重复执行的次数。然后,代码调用
metafunc.definition.get_closest_marker('repeat')来获取测试用例是否有被标记为repeat的marker。如果有
repeat的marker标记,则从marker中获取重复执行的次数,并将其赋值给count变量。接下来,代码通过
metafunc.fixturenames.append("__pytest_repeat_step_number")添加了一个名为__pytest_repeat_step_number的fixture名称到metafunc的fixture列表中。之后,定义了一个辅助函数
make_progress_id,用于生成测试用例的进度标识符。根据
metafunc.config.option.repeat_scope的值,确定了重复执行的作用域。最后,通过调用
metafunc.parametrize来动态生成测试用例。它使用了range(count)来生成重复执行的步骤数量作为参数,并将indirect=True设置为在加载fixture时进行间接调用。同时,使用了之前定义的进度标识符生成函数和作用域来设置参数化的其他选项。
可以看到最终是通过参数化来实现的,这也就是为啥重复执行多次能当做多条用例。
最后
相信你看我之后依然有很多疑问,fixture是啥?mark是啥?参数request是啥?钩子函数是啥?parametrize参数化是啥?
这些疑问可以先留着,这片内容我们主要讲了pytest_repeat具体实现逻辑,然后引出了这么多知识点,别着急,之后会一个个逐一消灭。
感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取