pytest中文文档
官方文档
pytest documentation:http://www.pytest.org/en/latest/
第一章 安装和启动
pytest作为一个测试框架,可以非常简单的建立易用性好,扩展性强的测试集。这些测试因为避免了
大量的样板代码,所以可读性非常高。
你可以花费一点时间通过一个unittest或者略复杂的函数测试来验证你的应用程序或者库
1.1 安装pytest
1.在你的python环境下运行下面的命令即可安装pytest
pip install ‐U pytest
2.检查你安装的pytest的版本信息是否正确:
pip show pytest
或
pytest --version
1.2 创建你的第一个测试
创建一个只有4行代码的简单函数:
# test_sample.py 的内容 def func(x): return x + 1 def test_answer(): assert func(3) == 5
现在,你可以在test_sample.py的同级目录下直接执行pytest,结果如下:
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest ================================================= test session starts ================================================= platform win32 -- Python 3.7.1, pytest-5.3.2, py-1.8.1, pluggy-0.13.1 rootdir: D:\DiluWorkspace\Code\MyTest collected 1 item test_sample.py F [100%] ====================================================== FAILURES ======================================================= _____________________________________________________ test_answer _____________________________________________________ def test_answer(): > assert func(3) == 5 E assert 4 == 5 E + where 4 = func(3) test_sample.py:7: AssertionError ================================================== 1 failed in 0.02s ================================================== (pytest_env) D:\DiluWorkspace\Code\MyTest>
这个测试的结果是失败的,因为func(3)的返回值不是5
1.3 运行多个测试
pytest会运行当前目录及子目录下所有以 test_*.py 和 *_test.py 命名的文件。文件匹配方式遵循
Standard test discovery rules
1.4 判断是否发生了指定的异常
使用raises可以判断代码是否抛出了异常:
# test_sysexit.py 的内容 import pytest def f(): raise SystemExit(1) def test_my_test(): with pytest.raises(SystemExit): f()
使用"quiet"模式来执行这个测试:
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q test_sysexit.py . [100%] 1 passed in 0.02s
1.5 将多个测试用例放在一个class中
当你需要开发多个测试用例的时候,你可能需要将他们放在同一个class中,pytest可以很简单的创建
包含多个测试用例的class:
# test_class.py的内容 class TestClass(object): def test_one(self): x = 'this' assert 'h' in x def test_two(self): x = 'hello' assert hasattr(x, 'check')
pytest根据Conventions for Python test discovery查找所有的测试用例,所以可以找到所有以
**test_**开头的测试函数。我们可以通过文件名来直接运行整个模块:
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q test_sysexit.py . [100%] 1 passed in 0.02s (pytest_env) D:\DiluWorkspace\Code\MyTest> (pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q test_class.py .F [100%] ====================================================== FAILURES ======================================================= _________________________________________________ TestClass.test_two __________________________________________________ self = <test_class.TestClass object at 0x000002012A089550> def test_two(self): x = 'hello' > assert hasattr(x, 'check') E AssertionError: assert False E + where False = hasattr('hello', 'check') test_class.py:9: AssertionError 1 failed, 1 passed in 0.03s
第一个测试用例passed,第二个测试用例failed。你可以很直观的观察到测试用例中进行判断的中间
值,这可以帮助理解测试用例失败的原因。
1.6 为测试创建唯一的临时文件夹
pytest 提供 Builtin fixtures/function arguments来创建任意的资源,比如一个具有唯一的临时文件
夹:
# test_tmpdir.py的内容 def test_needs_files(tmpdir): print(tmpdir) assert 0
如果函数的签名中(函数签名包括函数的参数和返回值,以及参数的封送顺序等等)包含参数tmpdir,
pytest就会在执行测试用例之前查找并调用特定的fixture创建所需资源。在本例中,pytest会创建一
个unique-per-test-invocation临时文件夹:
pytest ‐‐fixtures
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q test_tmpdir.py F [100%] ====================================================== FAILURES ======================================================= __________________________________________________ test_needs_files ___________________________________________________ tmpdir = local('C:\\Users\\lcgst\\AppData\\Local\\Temp\\pytest-of-lcgst\\pytest-0\\test_needs_files0') def test_needs_files(tmpdir): print(tmpdir) > assert 0 E assert 0 test_tmpdir.py:4: AssertionError ------------------------------------------------ Captured stdout call ------------------------------------------------- C:\Users\lcgst\AppData\Local\Temp\pytest-of-lcgst\pytest-0\test_needs_files0 1 failed in 0.03s
关于tmpdir的更多信息请参考Temporary directories and files 通过下面的命令可以查看所有内置
的pytest fixture:
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest --fixtures ================================================= test session starts ================================================= platform win32 -- Python 3.7.1, pytest-5.3.2, py-1.8.1, pluggy-0.13.1 rootdir: D:\DiluWorkspace\Code\MyTest collected 5 items cache Return a cache object that can persist state between testing sessions. cache.get(key, default) cache.set(key, value) Keys must be a ``/`` separated value, where the first part is usually the name of your plugin or application to avoid clashes with other cache users. Values can be any object handled by the json stdlib module. capsys Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsys.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. capsysbinary Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``bytes`` objects. capfd Enable text capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. capfdbinary Enable bytes capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``byte`` objects. doctest_namespace [session scope] Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. pytestconfig [session scope] Session-scoped fixture that returns the :class:`_pytest.config.Config` object. Example:: def test_foo(pytestconfig): if pytestconfig.getoption("verbose") > 0: ... record_property Add an extra properties the calling test. User properties become part of the test report and are available to the configured reporters, like JUnit XML. The fixture is callable with ``(name, value)``, with value being automatically xml-encoded. Example:: def test_function(record_property): record_property("example_key", 1) record_xml_attribute Add extra xml attributes to the tag for the calling test. The fixture is callable with ``(name, value)``, with value being automatically xml-encoded record_testsuite_property [session scope] Records a new ``<property>`` tag as child of the root ``<testsuite>``. This is suitable to writing global information regarding the entire test suite, and is compatible with ``xunit2`` JUnit family. This is a ``session``-scoped fixture which is called with ``(name, value)``. Example: .. code-block:: python def test_foo(record_testsuite_property): record_testsuite_property("ARCH", "PPC") record_testsuite_property("STORAGE_TYPE", "CEPH") ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. caplog Access and control log capturing. Captured logs are available through the following properties/methods:: * caplog.messages -> list of format-interpolated log messages * caplog.text -> string containing formatted log output * caplog.records -> list of logging.LogRecord instances * caplog.record_tuples -> list of (logger_name, level, message) tuples * caplog.clear() -> clear captured records and formatted log output string monkeypatch The returned ``monkeypatch`` fixture provides these helper methods to modify objects, dictionaries or os.environ:: monkeypatch.setattr(obj, name, value, raising=True) monkeypatch.delattr(obj, name, raising=True) monkeypatch.setitem(mapping, name, value) monkeypatch.delitem(obj, name, raising=True) monkeypatch.setenv(name, value, prepend=False) monkeypatch.delenv(name, raising=True) monkeypatch.syspath_prepend(path) monkeypatch.chdir(path) All modifications will be undone after the requesting test function or fixture has finished. The ``raising`` parameter determines if a KeyError or AttributeError will be raised if the set/deletion operation has no target. recwarn Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. See http://docs.python.org/library/warnings.html for information on warning categories. tmpdir_factory [session scope] Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session. tmp_path_factory [session scope] Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session. tmpdir Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. The returned object is a `py.path.local`_ path object. .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html tmp_path Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. The returned object is a :class:`pathlib.Path` object. .. note:: in python < 3.6 this is a pathlib2.Path ================================================ no tests ran in 0.07s ================================================
第二章 用法
2.1 通过python -m pytest调用pytest
这是在2.0版本中新引入的功能。 你可以通过python的解释器,利用命令行来调用测试:
python ‐m pytest [...]
这种调用方式几乎等同于直接调用pytest […],但需要注意的是这种通过python来调用的方式同时会
将当前目录添加到sys.path
2.2 退出码
pytest有以下6种退出码:
Exit code 0: 找到所有测试用例并测试通过 Exit code 1: 找到测试用例并运行但是部分测试用例运行失败 Exit code 2: 用户中断了测试 Exit code 3: 执行过程中发生了内部错误 Exit code 4: pytest命令行使用错误 Exit code 5: 没有找到任何测试用例
2.3 版本信息,参数名,环境变量的帮助
pytest ‐‐version #显示pytest的import的路径 pytest ‐‐fixtures #显示内置的函数参数 pytest ‐h | ‐‐help #帮助信息
2.4 第一(N)次测试失败后停止
使用下面的参数可以让测试在第1(N)次测试失败后停止:
pytest ‐x # 第一次测试失败后停止测试 pytest ‐‐maxfail=2 # 第2次测试失败后停止测试
2.5 指定/选择测试用例
Pytest在命令行中支持多种方式来运行和选择测试用例:
对模块中进行测试:
pytest test_mod.py
对文件夹中进行测试:
pytest testing/
通过关键字表达式来进行测试:
pytest ‐k "MyClass and not method"
这种方式会执行文件名,类名以及函数名与给定的字符串表达式相匹配的测试用例。 上面的用例会执行TestMyClass.test_something但是不会执行TestMyClass.test_method_simple。and not 在这里是表达式,未经过测试
通过节点id来进行测试
每个被选中的测试用例都会被分配一个唯一的nodeid,它由模块文件名和以下说明符组成:参数化的类名、函数名和参数,用::分隔。
可以通过下面的方式运行模块中的指定的测试用例:
pytest test_mod.py::test_func
也可以通过下面这种方式:
pytest test_mod.py::TestClass::test_method
通过标记符来进行测试
pytest ‐m slow
这种方式会运行所有通过装饰器 @pytest.mark.slow进行装饰的测试用例。
关于标记符请参考marks
通过包来运行
pytest ‐‐pyargs pkg.testing
这种方式会导入pkg.testing,并且基于该包所在位置来查找并运行测试用例。
2.6 修改python的traceback的打印
pytest ‐‐showlocals #在tracebacks中显示本地变量 pytest ‐l #同上 pytest ‐‐tb=auto #(默认显示方式)该方式会显示tracebacks的第一条和最后一条的详细信息 #其余的信息会简略显示 pytest ‐‐tb=long #详细显示所有的tracebacks pytest ‐‐tb=short #简略显示tracebacks pytest ‐‐tb=line #每个错误只显示一行 pytest ‐‐tb=native #以python标准库模式输出 pytest ‐‐tb=no #不输出tracebacks
另外还有比**–tb=long输出更详细的参数–full-trace**。在该参数下,KeyboardInterrupt(Ctrl+C)在中断traceback的输出的时候同时会打印栈信息。 这在测试耗时过长的时候非常有用,使用该组合可以帮助我们找到测试用例挂死在何处。
如果使用默认方式,测试被中断的时候不会有任何栈信息输出(因为KeyboardInterrupt被pytest捕获了),使用该参数可以保证traceback能够显示出来。
例子:
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q --showlocals test_class.py .F [100%] ====================================================== FAILURES ======================================================= _________________________________________________ TestClass.test_two __________________________________________________ self = <test_class.TestClass object at 0x0000015D742CC908> def test_two(self): x = 'hello' > assert hasattr(x, 'check') E AssertionError: assert False E + where False = hasattr('hello', 'check') self = <test_class.TestClass object at 0x0000015D742CC908> x = 'hello' test_class.py:9: AssertionError 1 failed, 1 passed in 0.03s (pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q -l test_class.py .F [100%] ====================================================== FAILURES ======================================================= _________________________________________________ TestClass.test_two __________________________________________________ self = <test_class.TestClass object at 0x00000117BB4B9940> def test_two(self): x = 'hello' > assert hasattr(x, 'check') E AssertionError: assert False E + where False = hasattr('hello', 'check') self = <test_class.TestClass object at 0x00000117BB4B9940> x = 'hello' test_class.py:9: AssertionError 1 failed, 1 passed in 0.03s (pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q --tb=auto test_class.py .F [100%] ====================================================== FAILURES ======================================================= _________________________________________________ TestClass.test_two __________________________________________________ self = <test_class.TestClass object at 0x000001F2A992A7B8> def test_two(self): x = 'hello' > assert hasattr(x, 'check') E AssertionError: assert False E + where False = hasattr('hello', 'check') test_class.py:9: AssertionError 1 failed, 1 passed in 0.03s (pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q --tb=auto test_class.py .F [100%] ====================================================== FAILURES ======================================================= _________________________________________________ TestClass.test_two __________________________________________________ self = <test_class.TestClass object at 0x000001BC93029898> def test_two(self): x = 'hello' > assert hasattr(x, 'check') E AssertionError: assert False E + where False = hasattr('hello', 'check') test_class.py:9: AssertionError 1 failed, 1 passed in 0.03s (pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q --tb=long test_class.py .F [100%] ====================================================== FAILURES ======================================================= _________________________________________________ TestClass.test_two __________________________________________________ self = <test_class.TestClass object at 0x0000027AEE435978> def test_two(self): x = 'hello' > assert hasattr(x, 'check') E AssertionError: assert False E + where False = hasattr('hello', 'check') test_class.py:9: AssertionError 1 failed, 1 passed in 0.02s (pytest_env) D:\DiluWorkspace\Code\MyTest> (pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q --tb=short test_class.py .F [100%] ====================================================== FAILURES ======================================================= _________________________________________________ TestClass.test_two __________________________________________________ test_class.py:9: in test_two assert hasattr(x, 'check') E AssertionError: assert False E + where False = hasattr('hello', 'check') 1 failed, 1 passed in 0.03s (pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q --tb=line test_class.py .F [100%] ====================================================== FAILURES ======================================================= D:\DiluWorkspace\Code\MyTest\test_class.py:9: AssertionError: assert False 1 failed, 1 passed in 0.02s (pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q --tb=native test_class.py .F [100%] ====================================================== FAILURES ======================================================= _________________________________________________ TestClass.test_two __________________________________________________ Traceback (most recent call last): File "D:\DiluWorkspace\Code\MyTest\test_class.py", line 9, in test_two assert hasattr(x, 'check') AssertionError: assert False + where False = hasattr('hello', 'check') 1 failed, 1 passed in 0.01s (pytest_env) D:\DiluWorkspace\Code\MyTest> (pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q --tb=no test_class.py .F [100%] 1 failed, 1 passed in 0.02s
2.7 详尽的测试报告
这是2.9的新功能。
参数-r可以用来在测试结束后展示一份“测试概要信息”,这使得在大型测试套中获取一份清楚
的测试结果(失败,跳过等测试信息)十分简单。
示例
# test_example.py的内容 import pytest @pytest.fixture def error_fixture(): assert 0 def test_ok(): print("ok") def test_fail(): assert 0 def test_error(error_fixture): pass def test_skip(): pytest.skip("skipping this test") def test_xfail(): pytest.xfail("xfailing this test") @pytest.mark.xfail(reason="always xfail") def test_xpass(): pass
执行
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q test_example.py -ra .FEsxX [100%] ======================================================= ERRORS ======================================================== ____________________________________________ ERROR at setup of test_error _____________________________________________ @pytest.fixture def error_fixture(): > assert 0 E assert 0 test_example.py:7: AssertionError ====================================================== FAILURES ======================================================= ______________________________________________________ test_fail ______________________________________________________ def test_fail(): > assert 0 E assert 0 test_example.py:15: AssertionError =============================================== short test summary info =============================================== SKIPPED [1] D:\DiluWorkspace\Code\MyTest\test_example.py:23: skipping this test XFAIL test_example.py::test_xfail reason: xfailing this test XPASS test_example.py::test_xpass always xfail ERROR test_example.py::test_error - assert 0 FAILED test_example.py::test_fail - assert 0 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.05s
-r后可以追加一些参数,上面示例中的a表示"除了passes的所有信息"。
可以追加的参数列表如下:
f - failed E - error s - skipped x - xfailed X - xpassed p - passed P - passed with output a - all except pP
可以同时追加多个参数,如果你想只看"failed"和"skipped"的测试结果,你可以执行:
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q test_example.py -rfs .FEsxX [100%] ======================================================= ERRORS ======================================================== ____________________________________________ ERROR at setup of test_error _____________________________________________ @pytest.fixture def error_fixture(): > assert 0 E assert 0 test_example.py:7: AssertionError ====================================================== FAILURES ======================================================= ______________________________________________________ test_fail ______________________________________________________ def test_fail(): > assert 0 E assert 0 test_example.py:15: AssertionError =============================================== short test summary info =============================================== FAILED test_example.py::test_fail - assert 0 SKIPPED [1] D:\DiluWorkspace\Code\MyTest\test_example.py:23: skipping this test 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.04s
p可以用来显示pass的测试用例,P会在测试报告中增加一段"PASSES"的信息来显示通过的测试用
例
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest -q test_example.py -rpP .FEsxX [100%] ======================================================= ERRORS ======================================================== ____________________________________________ ERROR at setup of test_error _____________________________________________ @pytest.fixture def error_fixture(): > assert 0 E assert 0 test_example.py:7: AssertionError ====================================================== FAILURES ======================================================= ______________________________________________________ test_fail ______________________________________________________ def test_fail(): > assert 0 E assert 0 test_example.py:15: AssertionError ======================================================= PASSES ======================================================== _______________________________________________________ test_ok _______________________________________________________ ------------------------------------------------ Captured stdout call ------------------------------------------------- ok =============================================== short test summary info =============================================== PASSED test_example.py::test_ok 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.05s
2.8 测试失败时自动调用PDB
pytest允许通过命令行使能在测试失败时自动调用python的内置调试工具PDB:
pytest ‐‐pdb
这会在每次测试失败(或者发生KeyboardInterrupt)的时候都去调用PDB。通常我们可能只需要在第
一次测试失败的时候来调用pdb:
pytest ‐x ‐‐pdb #首次失败的时候调用pdb,然后结束测试进程 pytest ‐‐pdb ‐‐maxfail=3 #前三次失败的时候调用pdb
注意所有的异常信息都会保存在sys.last_value, sys.last_type和sys.last_traceback中。 在交互使用
中,这允许使用任何调试工具进入后期调试。我们也可以手动的访问这些异常信息,如下:
(Pdb) import sys (Pdb) sys.last_traceback.tb_lineno 1448 (Pdb) sys.last_value AssertionError('assert 0') (Pdb) sys.last_type <class 'AssertionError'>
2.9 测试启动时调用PDB
pytest允许在测试启动时立即调用pdb:
pytest ‐‐trace
这会在每个测试开始时立即调用python的pdb
2.10 设置断点
在代码中使用python的原生接口python import pdb; pdb.set_trace()来设置断点,在pytest中会自动禁用该测试的输出捕获:
其他测试的输出捕获不会受影响
先前的测试中已经被捕获的输出会被正常处理
同一测试中的后续输出不会被捕获,而被转发给sys.stdout。注意即使退出交互式pdb继续运行测试,这一设置依然生效
2.11 使用内置的断点函数
python3.7 引入了内置的断点函数 breakpoint(),pytest支持该函数:
当breakpoint()被调用,并且PYTHONBREAKPOINT是默认值时,pytest将会使用内部自定义的PDB UI, 而不是系统默认的PDB。测试完成后,返回系统默认的PDB UI 当使用–pdb参数时,breakpoint()和测试异常时都会使用内部自定义的PDB UI
–pdbcls 可以用于指定自定义的调试类
2.12 分析测试时间
显示最慢的10个测试步骤:
pytest ‐‐durations=10
默认情况下,如果测试时间很短(<0.01s),这里不会显示执行时常,如果需要显示,在命令行中追加-vv参数
2.13 创建 JUnitXML 格式的文件
使用下面的方式可以创建Jekins或者其他的集成测试环境可读的结果文件:
pytest ‐‐junitxml=path
path是生成的XML文件
3.1引入的新特性:
可以在pytest的配置文件中设置junit_suite_name属性来配置测试结果XML中的root名称
[pytest] junit_suite_name = my_suite
4.0引入的新特性:
Junit XML规范中"time"属性应该是总的测试执行的时间,包含setup和teardown。这也是pytest的默认行为。如果想要只显示测试的时间(call duration),配置junit_duration_report:
[pytest] junit_duration_report = call
2.13.1 record_property
略
2.13.2 record_xml_attribute
3.4引入
使用record_xml_attribute可以在测试用例中增加额外的xml属性。该固件也可以用来复写已经存在的属性值
def test_function(record_xml_attribute): record_xml_attribute("assertions", "REQ‐1234") record_xml_attribute("classname", "custom_classname") print("hello world") assert True
与record_property不同,该固件不会新增子节点,而是在testcase的tag里面增加或者覆盖已有的属性:
<testcase classname="custom_classname" file="test_function.py" line="0" name="test_function" time="0.003" assertions="REQ‐1234"> <system‐out> hello world </system‐out> </testcase>
注意: record_xml_attribute尚处于实验阶段,将来可能会有所变化。
2.13.3 LogXML:add_global_property
略
2.14 创建resultlog格式文件
很少用,很快就要被移除了
2.15 将测试结果发送给在线的pastebin
Pastebin是一个用户存储纯文本的web应用。用户可以通过互联网分享文本片段,一般都是源代码。
略
2.16 禁用插件
如果想要在运行时禁用特定的插件,使用 -p以及no:前缀符。
如: 禁止加载doctest插件
pytest ‐p no:doctest
2.17 在python代码中运行pytest
2.0引入 你可以在python代码中直接调用pytest:
pytest.main()
这和在命令行中使用pytest是一样的, 这种方式不会抛出SystemExit异常,而会返回exitcode, 通过如下方式可以传入调用参数:
pytest.main(['‐x', 'mytestdir'])
你可以在pytest.main中指定额外的插件:
# myinvoke.py的内容 import pytest class MyPlugin(object): def pytest_sessionfinish(self): print("*** test run reporting finishing") pytest.main(["‐qq"], plugins=[MyPlugin()])
调用pytest.main()会导入你的测试用例及其所引用的所有的模块。因为python存在模块导入的缓存机制,如果多次调用pytest.main(),后续的调用也不会再刷新这些导入的资源。因此,不建议再同一进程中多次调用pytest.main() (比如重新运行测试).
第三章 在现有测试套中使用pytest
pytest可以与大多数现有的测试套一起使用,但他的测试行为与其他的测试工具(如nose或者python的默认的unittest)有差异. 在使用此部分之前,您需要安装pytest
3.1 与现有的测试套一起运行pytest
比如说你想要修改某处的已有的代码库,在将代码拉到你的开发环境后并且设置好python的环境后,你需要在你的工程的根目录下运行
cd <repository> pip install ‐e . #解决环境依赖可以通过"python setup.py develop"或"conda develop"
这将在site-packages中设置一个symlink到你的代码,允许你在运行测试的时候编辑代码,就好像你已经安装了这个package一样。设置项目为开发模式,可以避免每次运行测试时都需要重新install。当然,也可以考虑使用tox库。
第四章 在测试用例中编写和上报断言
4.1 使用断言语句
pytest允许你在测试用例中使用标准的python断言,如下:
# test_assert1 中的内容 def f(): return 3 def test_function(): assert f() == 4
本例中的函数期望返回一个固定的值。如果该断言失败了,你会看到该函数的返回值:
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest test_assert1.py ================================================= test session starts ================================================= platform win32 -- Python 3.7.1, pytest-5.3.2, py-1.8.1, pluggy-0.13.1 rootdir: D:\DiluWorkspace\Code\MyTest collected 1 item test_assert1.py F [100%] ====================================================== FAILURES ======================================================= ____________________________________________________ test_function ____________________________________________________ def test_function(): > assert f() == 4 E assert 3 == 4 E + where 3 = f() test_assert1.py:7: AssertionError ================================================== 1 failed in 0.03s ==================================================
pytest支持显示常见的子表达式的值,包括调用,属性,比较以及二元和一元运算符。(参看Demoof Python failure reports with purest 这允许你使用你习惯的python的在不丢失内省信息的情况下构造代码。如果你为断言指定了输出信息,那么不会输出任何内省信息,而是在traceback中直接输出指定的信息:
assert a % 2 ==0, "value was odd, should be even"
更多断言内省信息请参考Advanced assertion introspection
例子:
# test_assert1 中的内容 def f(): return 3 def test_function(): assert f() == 4, "被测函数的值不等于4"
执行结果:
(pytest_env) D:\DiluWorkspace\Code\MyTest>pytest test_assert1.py ================================================= test session starts ================================================= platform win32 -- Python 3.7.1, pytest-5.3.2, py-1.8.1, pluggy-0.13.1 rootdir: D:\DiluWorkspace\Code\MyTest collected 1 item test_assert1.py F [100%] ====================================================== FAILURES ======================================================= ____________________________________________________ test_function ____________________________________________________ def test_function(): > assert f() == 4, "被测函数的值不等于4" E AssertionError: 被测函数的值不等于4 E assert 3 == 4 E + where 3 = f() test_assert1.py:7: AssertionError ================================================== 1 failed in 0.03s ==================================================
4.2 异常的断言
你可以在上下文中使用pytest.raises来对异常产生断言:
import pytest def test_zero_division(): with pytest.raises(ZeroDivisionError): 1 / 0
如果你还需要访问异常的确切信息,你需要使用下面的方式:
def test_recursion_depth(): with pytest.raises(RuntimeError) as excinfo: def f(): f() f() assert 'Maximum recursion' in str(excinfo.value)
excinfo是ExceptionInfo的一个实例,里面包含了异常的详细信息,主要属性是.type, .value以及.traceback. 你可以通过match参数来使用正则表达式来匹配一个异常是否发生
import pytest def myfunc(): raise ValueError("Exception 123 raised") def test_match(): with pytest.raises(ValueError, match=r'.* 123 .*'): myfunc()
参数match与re.search行为是一致的,所以上面的match='123’能够正确的匹配到myfunc抛出的异常。
pytest.raises的另一种使用方法是传递一个函数给它,该函数使用给定的参数,并且最终判断该函数是否产生了期望的断言
pytest.raises(ExpectedException, func, *args, **kwargs)
测试报告会提供你一些帮助信息,比如是没有异常还是抛出了错误的异常注意也可以在pytest.mark.xfail中使用raises来检查测试用例是否是因为抛出了异常而失败:
@pytest.mark.xfail(raises=IndexError) def test_f(): f()
使用pytest.raises更适合测试自己的代码故意引发的异常。而使用@pytest.mark.xfail和检查函数更适合那些记录的未修复的bug(也就是那些应该发生的异常)或者依赖项中的异常。?(这两个场景在测试过程中是不一样的)
持续更新...