Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

21 Jul 2020

pytest文档阅读笔记【三】:Plugins

Installing and Using plugins¶

可以通过pip来安装和卸载插件:

pip install pytest-Name
pip uninstall pytest-Name

一些插件例子:

  • pytest-django
  • pytest-twisted
  • pytest-cov
  • pytest-xdist:将测试分散到多个CPU上执行
  • pytest-instafail:即时报告错误信息
  • pytest-bdd:支持behaviour-driven testing风格
  • pytest-timeout:支持测试timeout
  • pytest-pip8:添加–pep8选项
  • pytest-flakes:使用pyflakes来检查源码
  • oejskit:在现行浏览器中运行javascript unittest

可以在测试模块或者conftest.py中使用pytest_plugins来声明所需要的插件:

pytest_plugins = ("myapp.testsupport.myplugin",)

在根目录的conftest.py下使用pytest_plugins的支持以及被废弃了

打印导入的插件列表:pytest --trace-config

在命令行禁用一个插件:pytest -p no:NAME。在环境变量PYTEST_ADDOPTS 指定-p no:NAME可以达到相同效果。

在pytest.ini中禁用插件:

[pytest]
addopts = -p no:NAME

Writing plugins

一个插件包含一至多个钩子函数。pytest实现了配置、收集、运行和报告的方方面面,并提供很多精细的钩子,可供不同的插件来关联。pytest会查找以下插件:

  • 自带插件,在pytest的_pytest目录
  • 外部插件,通过setuptools进入点来判断
  • conftest.py插件,在众多test目录中发掘

pytest启动时加载插件的顺序:

  • 自带插件
  • 外部插件
  • 命令行-p name指定的插件
  • 查找conftest.py并加载其中的pytest_plugins
    • 如果没有在命令行指定运行目录,那么当前目录会被作为运行目录来查找conftest.py
    • 如果指定了,那么会从指定目录的相对路径运行conftest.py以及test*/contest.py

conftest.py可以对在同一目录下的测试进行自定义。

不要import conftest。如果真的需要import,保证conftest.py在一个package内。

可以从既有的例子中找容易学的来开始写自己的插件:

对于setuptools打包的插件,可以通过提供pytest11进入点来让pytest发现这个插件,一个例子:

from setuptools import setup

setup(
    name="myproject",
    packages=["myproject"],
    # the following makes a plugin available to pytest
    entry_points={"pytest11": ["name_of_plugin = myproject.pluginmodule"]},
    # custom PyPI classifier for pytest plugins
    classifiers=["Framework :: Pytest"],
)

有了以上描述,pytest就知道从myproject.pluginmodule加载插件了。

pytest通过PEP 302提供的import钩子来改写AST中的assert语句。pytest只改写测试模块,或者插件中的模块。但是通过register_assert_rewrite可以主动要求pytest来改写其中的assert。

在conftest.py指定插件的例子:

pytest_plugins = ["name1", "name2"]
pytest_plugins = "myapp.testsupport.myplugin"

注意,这个过程可以是递归的,当myapp.testsupport.myplugin也定义了pytest_plugins,那么那些pytest_plugins也会被导入。

可以使用:plugin = config.pluginmanager.get_plugin("name_of_plugin")在一个插件中访问其他插件。

可以在插件中注册markers:

def pytest_configure(config):
    config.addinivalue_line("markers", "cool_marker: this one is for cool tests.")
    config.addinivalue_line(
        "markers", "mark_with(arg, arg2): this marker takes arguments."
    )

pytester这个插件可以用来测试其他插件。在命令行使用-p pyteste,或者在testing目录的conftest.py中添加:

# content of conftest.py

pytest_plugins = ["pytester"]

pytest会调用插件中的钩子函数,以 pytest_collection_modifyitems(session, config, items)为例:

def pytest_collection_modifyitems(config, items):
    # called after collection is completed
    # you can modify the ``items`` list
    ...

pytest会传config以及items参数,而不会传session参数。

pytest的钩子一般返回一个结果列表,有些钩子使用firstresult=True来告知pytest它只在第一轮运行中被调用,而不再重试中生效。

pytest还提供钩子封装,可以用来封装其他钩子,不过操作比较复杂,需要通过generator来操作:



@pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem):
    do_something_before_next_hook_executes()

    outcome = yield
    # outcome.excinfo may be None or a (cls, val, tb) tuple

    res = outcome.get_result()  # will raise if outcome was exception

    post_process_result(res)

    outcome.force_result(new_res)  # to override the return value to the plugin system

一个钩子可能会关联不同的函数,可以通过选项来指定其执行顺序:

# Plugin 1
@pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(items):
    # will execute as early as possible
    ...


# Plugin 2
@pytest.hookimpl(trylast=True)
def pytest_collection_modifyitems(items):
    # will execute as late as possible
    ...


# Plugin 3
@pytest.hookimpl(hookwrapper=True)
def pytest_collection_modifyitems(items):
    # will execute even before the tryfirst one above!
    outcome = yield
    # will execute after all non-hookwrappers executed

conftest.py中可以指定其他plugin实现的钩子:pytest_addhooks(pluginmanager: PytestPluginManager) → None

一个插件可以通过pytest_addoption暴露一个命令行选项,供另一个插件来提供值。

可以延后钩子的注册,以免钩子所属的插件未安装:

class DeferPlugin:
    """Simple plugin to defer pytest-xdist hook functions."""

    def pytest_testnodedown(self, node, error):
        """standard xdist hook function.
        """


def pytest_configure(config):
    if config.pluginmanager.hasplugin("xdist"):
        config.pluginmanager.register(DeferPlugin())

其他

pytest的插件合集:http://plugincompat.herokuapp.com/,目前有976个

(本篇完)

2020-07-25更新

如果要运行async测试用例,则会出现以下提示:

  You need to install a suitable plugin for your async framework, for example:
    - pytest-asyncio
    - pytest-trio
    - pytest-tornasync
    - pytest-twisted
    warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid)))

解决办法之一是把pytest-asyncio装上

参考

(更新完)

Categories

Tags

comments powered by Disqus