pytest的内置插件盘点7:python

本文系《pytest源码剖析》系列内容

正在连载,欢迎关注

图片

7. 内置插件 python

插件路径:_pytest.python

实现的 hook

hooktryfirsttrylastoptionalhookhookwrapperwrapper
pytest_addoptionFalseFalseFalseFalseFalse
pytest_cmdline_mainFalseFalseFalseFalseFalse
pytest_collect_fileFalseFalseFalseFalseFalse
pytest_configureFalseFalseFalseFalseFalse
pytest_generate_testsFalseFalseFalseFalseFalse
pytest_pycollect_makeitemFalseFalseFalseFalseTrue
pytest_pycollect_makemoduleFalseFalseFalseFalseFalse
pytest_pyfunc_callFalseFalseFalseFalseTrue

调用的 hook

  • pytest_pycollect_makeitem

  • pytest_collect_file

  • pytest_pyfunc_call

  • pytest_make_parametrize_id

  • pytest_pycollect_makemodule

  • pytest_ignore_collect

插件功能

  1. 创建一系列 ini 配置,指定 python 用例的发现规则:

    • python_files: 文件名,默认值 test_*.py, *_test.py

    • python_classes:类名前缀,默认值 Test

    • python_functions,函数名前缀,默认值 test

  2. 创建 Item 子类,作为测试用例对象,_pytest.python.Function

  3. 根据规则,从各目录、文件、类中,收集测试用例,实例化 Item

  4. 实现 hookpytest_pyfunc_call,执行用例中的代码

代码片段

def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module":
    if module_path.name == "__init__.py":
        pkg: Package = Package.from_parent(parent, path=module_path)
        return pkg
    mod: Module = Module.from_parent(parent, path=module_path)
    return mod

class Module(nodes.File, PyCollector):
    def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
        self._inject_setup_module_fixture()
        self._inject_setup_function_fixture()
        self.session._fixturemanager.parsefactories(self)
        return super().collect
    
@hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
    testfunction = pyfuncitem.obj
    if is_async_function(testfunction):
        async_warn_and_skip(pyfuncitem.nodeid)
    funcargs = pyfuncitem.funcargs
    testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
    result = testfunction(**testargs)
    if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
        async_warn_and_skip(pyfuncitem.nodeid)
    elif result is not None:
        warnings.warn(
            PytestReturnNotNoneWarning(
                f"Expected None, but {pyfuncitem.nodeid} returned {result!r}, which will be an error in a "
                "future version of pytest.  Did you mean to use `assert` instead of `return`?"
            )
        )
    return True
  1. 收集用例时判断文件名,将__init__视为包,否则视为模块

  2. 将文件中 setUpModule 和 setup_module 风格的夹具,转为标准 fixture

  3. 将类、函数、参数化等所有类型的用例,转为 item 对象

  4. 生成器和协程,不被视为用例,也不执行

  5. 用例执行结果必须是 None,也就是不应该有返回值

简评

从实现和调用hook的数量可以看出,本插件在pytest中的重要性较大

...

该插件长度 1800 + 行,详细定义了在收集用例和执行用例时,遇到 Python 文件、包、模块、类、函数会如何进行处理

...

对于模块级夹具有 3 几种写法:

  1. setup / teardown

  2. setUpModule / tearDownModule

  3. setup_module / teardown_module

第一种是测试框架 nose 的写法,pytest 从 7.2.0 开始不再兼容 nose 框架,这种写法无了

第二种是测试框架 unittest 的写法,这是 python 的标准库,应该会一直兼容下去

第三种是测试框架 pytest 的写法,是仿 xunit 风格,使用非面向对象的方式来创建夹具

在实际的运行过程中中,所有的写法都会统一处理成 fixture,建议一步到位直接写 fixture

...

pytest 只会将函数(function)、方法(method),会被视为测试用例,

除了名字前缀的要求之外,还要求用例没有参数、没有返回值。

...

方法所在的类,也不能有__init__方法,

一方面,__ini__方法往往需要传递参数,这违背了上面说的要求

另一方面,实例化之后,实例对象会让方法与方法之间建立了关联

可是,测试用例之间不应该有关联

所以也就根本不需要__init__

...

该插件充斥大量的实现细节,篇幅原因就不展开了,

有兴趣的话你也可以亲自看看源码,会收益颇丰的

首发于公众号:测试开发研习社

原创不易,喜欢请星标+点赞+在看

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值