深入理解 pytest-repeat 插件的工作原理

前言

在文章无需修改代码,深入探究 pytest 如何自动查找并加载三方插件中最后提到了,到底pytest_repeat插件的具体功能是如何实现的呢?相信具体了解了该插件,其他三方插件也可以很快了解它内部运行机制。

不使用pytest_repeat插件如何实现重复执行用例

最笨的办法,当然是运行多次,但这显然不是我们需要的。在装饰器复习这片文章中,我们复习了装饰器相关知识点,知道装饰器可以在不修改原始代码的情况下,动态的增加功能或修改函数行为。显然,这里我们就可以使用装饰器来实现重复功能。

python 复制代码
def repeat(nums: int = 2):
  
    def wrapper(func):
​
        @functools.wraps(func)
        def decorator(*args, **kwargs):
            for i in range(nums):
                func(*args, **kwargs)
​
        return decorator
​
    return wrapper

这段代码很好理解,定义了带有自定义参数的装饰器,表示装饰器内部函数执行的次数。这样在用例上使用@repeat()装饰器就可以达到用例重复运行的目的。但是统计结果仍然为1条用例。使用过pytest_repeat的同学知道它的统计结果是多条用例?那是如何做的呢,通过源码一探究竟。

pytest_repeat如何实现重复执行

源码直达

源码解读

ini 复制代码
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:用于指定重复测试用例的作用域,可以选择 functionclassmodulesession。默认值是 functionaction=store 表示将值存储在命令行参数中。

这两个选项都是通过 parser.addoption 方法添加到 pytest 的命令行解析器中的。

当运行 pytest 并指定 --count--repeat-scope 参数时,pytest-repeat 插件将获取这些参数并自动为测试用例生成多个重复执行的实例。

例如,如果运行以下命令:

css 复制代码
pytest --count=2 --repeat-scope=function

pytest-repeat 将会在执行 test_my_function 测试用例时,自动执行该测试用例两次。


action=storeargparse 模块中的一个参数,它指定了在命令行解析过程中如何处理选项的值。具体地说,action=store 表示将选项的值存储在命令行参数中。

当使用 parser.addoption 方法添加选项到命令行解析器时,通过指定 action=store,选项的值将被存储在解析结果中,可以通过相应的属性来获取这些值。

例如,当运行 pytest 命令时,指定的 --count--repeat-scope 选项的值会存储在命令行参数中。你可以使用 request.config.getoption 方法来获取这些存储的值,例如:

ini 复制代码
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 的值,并分别存储在 countrepeat_scope 变量中。

总结:action=storeargparse 模块中的一个参数,用于指定将选项的值存储在命令行参数中。在 pytest 中,通过使用 request.config.getoption 方法可以获取存储在命令行参数中的选项值。


scss 复制代码
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)' 标记可以用于指定一个测试函数需要重复运行的次数。


less 复制代码
@pytest.fixture
def __pytest_repeat_step_number(request):
    marker = request.node.get_closest_marker("repeat")
    count = marker and marker.args[0] or request.config.option.count
    if count > 1:
        try:
            return request.param
        except 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 参数作为默认值。


ini 复制代码
@pytest.hookimpl(trylast=True)
def pytest_generate_tests(metafunc):
    count = metafunc.config.option.count
    m = 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_scope
        metafunc.parametrize(
            '__pytest_repeat_step_number',
            range(count),
            indirect=True,
            ids=make_progress_id,
            scope=scope
        )

这个 pytest_generate_tests 钩子函数会在 pytest 收集到所有测试函数之后被调用,并且它被设置为 trylast=True,以确保在其他钩子函数执行完毕之后再执行。

  1. 首先,代码获取了 metafunc.config.option.count 的值,该值表示测试用例重复执行的次数。
  2. 然后,代码调用 metafunc.definition.get_closest_marker('repeat') 来获取测试用例是否有被标记为 repeat 的 marker。
  3. 如果有 repeat 的 marker 标记,则从 marker 中获取重复执行的次数,并将其赋值给 count 变量。
  4. 接下来,代码通过 metafunc.fixturenames.append("__pytest_repeat_step_number") 添加了一个名为 __pytest_repeat_step_number 的 fixture 名称到 metafunc 的 fixture 列表中。
  5. 之后,定义了一个辅助函数 make_progress_id,用于生成测试用例的进度标识符。
  6. 根据 metafunc.config.option.repeat_scope 的值,确定了重复执行的作用域。
  7. 最后,通过调用 metafunc.parametrize 来动态生成测试用例。它使用了 range(count) 来生成重复执行的步骤数量作为参数,并将 indirect=True 设置为在加载 fixture 时进行间接调用。同时,使用了之前定义的进度标识符生成函数和作用域来设置参数化的其他选项。

可以看到最终是通过参数化来实现的,这也就是为啥重复执行多次能当做多条用例。

最后

相信你看我之后依然有很多疑问,fixture是啥?mark是啥?参数request是啥?钩子函数是啥?parametrize参数化是啥?这些疑问可以先留着,这片内容我们主要讲了pytest_repeat具体实现逻辑,然后引出了这么多知识点,别着急,之后会一个个逐一消灭。

相关推荐
noravinsc10 分钟前
人大金仓数据库 与django结合
数据库·python·django
豌豆花下猫17 分钟前
Python 潮流周刊#102:微软裁员 Faster CPython 团队(摘要)
后端·python·ai
秋野酱25 分钟前
基于javaweb的SpringBoot驾校预约学习系统设计与实现(源码+文档+部署讲解)
spring boot·后端·学习
北辰浮光1 小时前
[springboot]SSM日期数据转换易见问题
java·spring boot·后端
yzx9910131 小时前
Gensim 是一个专为 Python 设计的开源库
开发语言·python·开源
木梓辛铭1 小时前
Spring Cache的详细使用
java·后端·spring
麻雀无能为力1 小时前
python自学笔记2 数据类型
开发语言·笔记·python
Ndmzi1 小时前
matlab与python问题解析
python·matlab
懒大王爱吃狼1 小时前
怎么使用python进行PostgreSQL 数据库连接?
数据库·python·postgresql
猫猫村晨总1 小时前
网络爬虫学习之httpx的使用
爬虫·python·httpx