Python有许多测试框架,自带的有unittest。nose和nose2是unittest的衍生。这篇文章要介绍的pytest,则是一款非常流行的第三方框架。其特点是采用python原生的assert语句来做断言;可以与unittest和nose很好协调工作;支持非常多的扩展等等。pytest的主要版本需要Python 3.5+(或者PyPy3)。

Installation and Getting Started¶

pytest默认会从test_*.py文件中发现测试用例,并且会运行所有的测试用例。

可以用来检测异常是否抛出:

# content of test_sysexit.py
import pytest


def f():
    raise SystemExit(1)


def test_mytest():
    with pytest.raises(SystemExit):
        f()

可以从类方法中定义测试用例:

# content of test_class.py
class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")

TestClass采用了Test前缀以便pytest从中发现测试用例。

pytest -q test_sampe.py可以只选择运行某个文件中的测试用例。

pytest --fixtures可以显示内建的fixture。

Usage and Invocations¶

pytest执行之后通过程序退出码来给出执行状态的一些提示,这些退出码可以在from pytest import ExitCode中查看。

一些命令行的说明:

  • pytest -x 遇到第一个失败即停止
  • pytest --maxfail=2 遇到第二个失败才停止‘
  • pytest test_mod.py 执行test_mod.py中的用例
  • pytest testing/ 执行testing目录下的用例
  • pytest -k "MyClass and not method" 根据关键词过滤出要执行的用例
  • pytest -m slow,运行所有的@pytest.mark.slow修饰过的用例
  • 以nodeid来选择用例:
    • pytest test_mod.py::test_func
    • pytest test_mod.py::TestClass::test_method
  • 从package运行测试:pytest --pyargs pkg.testing

可以通过命令行来控制栈回溯信息的打印粒度:

  • pytest –showlocals
  • pytest -l
  • pytest –tb=auto|long|short|line|native|no
  • pytest –full-trace比–tb=long打印出的信息还要多,并且会在Ctrl+C时也打印出信息

通过-r选项可以在结尾打印一个报告(默认时fE),该选项接受以下定制:

  • f, failed
  • E, error
  • s, skipped
  • x, xfailed
  • X, xpassed
  • p, passed
  • P, passed without output
  • a, all except pP
  • A, all
  • N, 不打印报告

pytest --pdb可以用来在失败的时候启动调试器,异常信息会被保存在sys.last_value、sys.last_type、sys.last_traceback。如果一开始就要执行调试器,那么可以pytest --trace。也可以在测试用例中指定 import pdb;pdb.set_trace(),这样pytest不会不做那个用例的输出,并把控制交给pdb。

Python3.7引入了breakpoint()函数,pytest对breakpoint()提供以下支持:

  • 当breakpoint()被调用时,如果PYTHONBREAKPOINT 设置为默认值,pytest将会使用自定义的PDB trace UI,而不是系统默认的Pdb
  • 当所有测试结束的时候,会恢复使用系统默认的Pdb
  • 当指定了--pdb选项,自定义的PDB trace UI不仅会在breakpoint()中使用,也会在失败/未处理的异常中使用
  • --pdbcls可以用来指定一个自定义的调试器类型

pytest --durations=10可以显示10个跑得最慢的测试用例。默认情况下,如果没有指定-vv的话,pytest不会显示运行时间少于0.01s的测试用例。

Python的faulthandler模块会被用来在segfault或者timeout的时候回吐tracebacks。这个行为可以在命令行指定-p no:faulthandler来禁止。在Windows之外的平台,可以指定faulthandler_timeout=X来如果在X秒之内没有结束运行就产生一个回吐。

上面的功能时从pytest-faulthandler 中合并进来的。

生成JUnit风格的XML报告,可以在Jenkins之类的服务接受处理:

pytest --junitxml=path

可以在配置文件里面指定junit_suite_name来指定默认运行的套装:

[pytest]
junit_suite_name = my_suite
junit_duration_report = call

如果想记录额外的信息,可以使用record_property测具:

def test_function(record_property):
    record_property("example_key", 1)
    assert True

或者在marker里面添加

# content of conftest.py


def pytest_collection_modifyitems(session, config, items):
    for item in items:
        for marker in item.iter_markers(name="test_id"):
            test_id = marker.args[0]
            item.user_properties.append(("test_id", test_id))

# content of test_function.py
import pytest


@pytest.mark.test_id(1501)
def test_function():
    assert True

如果想创建普通文本格式的报告,可以使用:pytest --resultlog=path,可是这个选项将被移除,可以考虑使用pytest-reportlog 替代。

通过指定–pastebin,可以将测试报告发送给http://bpaste.net,例子:pytest --pastebin=failed

通过指定-p选项,可以在pytest运行伊始指定某个插件:

  • pytest -p mypluginmodule
  • pytest -p pytest_cov
  • pytest -p myproject.plugins

如果要禁止某个插件,需要采取这种形式:pytest -p no:doctest

如果想在代码中调用pytest,可以执行pytest.main(),效果就像直接从命令行执行pytest一样。传参数的话可以采用:pytest.main(["-x", "mytestdir"])的方式。更复杂的使用自定义插件的例子:

# content of myinvoke.py
import pytest


class MyPlugin:
    def pytest_sessionfinish(self):
        print("*** test run reporting finishing")


pytest.main(["-qq"], plugins=[MyPlugin()])

最好不要在同一个process多次调用pytest.main(),根据python的缓存机制,在同一进程调用pytest.main()可能执行的是第一次运行缓存残留的代码。

(本篇完)