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()可能执行的是第一次运行缓存残留的代码。
(本篇完)