本文系《pytest源码剖析》系列内容
正在连载,欢迎关注
7. 内置插件 python
插件路径:_pytest.python
实现的 hook
hook | tryfirst | trylast | optionalhook | hookwrapper | wrapper |
---|---|---|---|---|---|
pytest_addoption | False | False | False | False | False |
pytest_cmdline_main | False | False | False | False | False |
pytest_collect_file | False | False | False | False | False |
pytest_configure | False | False | False | False | False |
pytest_generate_tests | False | False | False | False | False |
pytest_pycollect_makeitem | False | False | False | False | True |
pytest_pycollect_makemodule | False | False | False | False | False |
pytest_pyfunc_call | False | False | False | False | True |
调用的 hook
-
pytest_pycollect_makeitem
-
pytest_collect_file
-
pytest_pyfunc_call
-
pytest_make_parametrize_id
-
pytest_pycollect_makemodule
-
pytest_ignore_collect
插件功能
-
创建一系列 ini 配置,指定 python 用例的发现规则:
-
python_files
: 文件名,默认值test_*.py, *_test.py
-
python_classes
:类名前缀,默认值Test
-
python_functions
,函数名前缀,默认值test
-
-
创建 Item 子类,作为测试用例对象,
_pytest.python.Function
-
根据规则,从各目录、文件、类中,收集测试用例,实例化 Item
-
实现 hook
pytest_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 pkgmod: Module = Module.from_parent(parent, path=module_path)return modclass 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.objif is_async_function(testfunction):async_warn_and_skip(pyfuncitem.nodeid)funcargs = pyfuncitem.funcargstestargs = {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
-
收集用例时判断文件名,将
__init__
视为包,否则视为模块 -
将文件中
setUpModule
和setup_module
风格的夹具,转为标准 fixture -
将类、函数、参数化等所有类型的用例,转为 item 对象
-
生成器和协程,不被视为用例,也不执行
-
用例执行结果必须是 None,也就是不应该有返回值
简评
从实现和调用hook的数量可以看出,本插件在pytest中的重要性较大
...
该插件长度 1800 + 行,详细定义了在收集用例和执行用例时,遇到 Python 文件、包、模块、类、函数会如何进行处理
...
对于模块级夹具有 3 几种写法:
-
setup
/teardown
-
setUpModule
/tearDownModule
-
setup_module
/teardown_module
第一种是测试框架 nose
的写法,pytest 从 7.2.0 开始不再兼容 nose 框架,这种写法无了
第二种是测试框架 unittest
的写法,这是 python 的标准库,应该会一直兼容下去
第三种是测试框架 pytest
的写法,是仿 xunit 风格,使用非面向对象的方式来创建夹具
在实际的运行过程中中,所有的写法都会统一处理成 fixture,建议一步到位直接写 fixture
...
pytest 只会将函数(function)、方法(method),会被视为测试用例,
除了名字前缀的要求之外,还要求用例没有参数、没有返回值。
...
方法所在的类,也不能有__init__
方法,
一方面,__ini__
方法往往需要传递参数,这违背了上面说的要求
另一方面,实例化之后,实例对象会让方法与方法之间建立了关联
可是,测试用例之间不应该有关联
所以也就根本不需要__init__
...
该插件充斥大量的实现细节,篇幅原因就不展开了,
有兴趣的话你也可以亲自看看源码,会收益颇丰的
首发于公众号:测试开发研习社
原创不易,喜欢请星标+点赞+在看