上一節我們介紹了pytest的安裝和簡單使用,這一節我們再加些料
pytest不僅容易編寫小的測試例,也支持程序和庫相關的複雜函數測試
通過python -m pytest來調用pytest
python -m pytest [...]
這和直接使用pytest命令功能相同,唯一的區別是會將當前目錄加入到sys.path中.
可能的退出碼
調用pytest可能產生6種不同的退出碼
Exit code 0: | 所有的測試例都被收集且通過 |
---|---|
Exit code 1: | 所有的測試例都被收集但有部分失敗 |
Exit code 2: | 測試例執行被用戶中斷 |
Exit code 3: | 發生了內部錯誤 |
Exit code 4: | pytest命令行使用錯誤 |
Exit code 5: | 沒用收集到測試例 |
pytest的退出碼API
from pytest import ExitCode
如果你需要在某些場景下定製ExiCode,可以使用pytest-custom_exit_code插件
pytest幫助命令
pytest --version # shows where pytest was imported from
pytest --fixtures # show available builtin function arguments
pytest -h | --help # show help on command line and config file options
在測試例失敗指定個數後停止pytest
pytest -x # stop after first failure
pytest --maxfail=2 # stop after two failures
選擇指定的測試例運行
pytest支持多種方式選擇要運行的測試例
運行指定的模塊
pytest test_mod.py
運行指定的目錄
pytest testing/
指定關鍵字運行
pytest -k "MyClass and not method"
上面的命令將會運行名字滿足上面字符串表達式的用例,字符串表達式可以包含文件名,類名,函數名。上面的例子會運行TestMyClass.test_something,但不會運行
TestMyClass.test_method_simple
.
指定node ids運行
每個測試例都有一個唯一的nodeid,由模塊名::類名::函數名組成。
運行指定模塊中的測試例:
pytest test_mod.py::test_func
運行指定模塊中指定類中的指定函數
pytest test_mod.py::test_func
指定marker表達式運行
pytest -m slow
上面的命令將會運行所有被@pytest.mark.slow
裝飾的測試例
運行指定包中的測試例
pytest --pyargs pkg.testing
修改python打印traceback的方式
pytest --showlocals # show local variables in tracebacks
pytest -l # show local variables (shortcut)
pytest --tb=auto # (default) 'long' tracebacks for the first and last
# entry, but 'short' style for the other entries
pytest --tb=long # exhaustive, informative traceback formatting
pytest --tb=short # shorter traceback format
pytest --tb=line # only one line per failure
pytest --tb=native # Python standard library formatting
pytest --tb=no # no traceback at all
"--full-trace”會在發生錯誤時打印很長的回溯棧,同時也能保證在
KeyboardInterrupt (Ctrl+C)時打印棧.
在測試長時間運行不中止時我們用Ctrl+C中止後十分有用,便於我們發現問題所在。
通常情況下,pytests會捕獲Ctrl+C因此不會打印棧信息。使用這個選項能夠確保顯示trace信息。
詳細的總結報告
"-r"選項用於在測試結束時顯示一個簡短的測試總結信息。這便於我們在大的測試套件中獲取失敗,跳過用例的清晰信息。
默認情況下,pytest使用"fE"來顯示失敗和錯誤.
舉例:
# content of 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 -ra
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 6 items
test_example.py .FEsxX [100%]
================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________
@pytest.fixture
def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 0
E assert 0
test_example.py:14: AssertionError
========================= short test summary info ==========================
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:22: 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.12s ===
-r選項後面可以接受一系列字符,'a'表示除了所有通過以外的測試例
下面是所有可以使用的字符:
f
- failedE
- errors
- skippedx
- xfailedX
- xpassedp
- passedP
- passed with output
用於選擇和反選擇的特殊字符:
a
- all exceptpP
A
- allN
- none, this can be used to display nothing (sincefE
is the default)
可以指定多個字符,下面的例子中只顯示失敗和跳過的測試例:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 6 items
test_example.py .FEsxX [100%]
================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________
@pytest.fixture
def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 0
E assert 0
test_example.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_example.py::test_fail - assert 0
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:22: skipping this test
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
使用p展示通過的測試例,P添加額外的"PASSES"來展示:
$ pytest -rpP
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 6 items
test_example.py .FEsxX [100%]
================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________
@pytest.fixture
def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 0
E assert 0
test_example.py:14: 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.12s ===
失敗時使用PDB(Python Debugger)
pytest --pdb
控制使用PDB的條件
pytest -x --pdb # drop to PDB on first failure, then end test session
pytest --pdb --maxfail=3 # drop to PDB for first three failures
注意當失敗時,異常信息會存儲到sys.last_value
, sys.last_type
和sys.last_traceback
. 在交互模式下,這允許我們在用debug工具在事後進行分析。你也可以手動訪問異常信息:
>>> import sys
>>> sys.last_traceback.tb_lineno
42
>>> sys.last_value
AssertionError('assert result == "ok"',)
在測試開始時進入PDB
pytest --trace
上面的命令將會在每個測試例開始時進入PDB.
使用斷點
要在代碼裏設置斷點,使用原生的Python方法:
def test_two(self):
x = "hello"
import pdb
pdb.set_trace()
assert hasattr(x, "check")
使用內置的breakpoint函數
python3.7提供了內置的breakpoint函數,pytest支持的breakpoint函數有以下行爲:
- When
breakpoint()
is called andPYTHONBREAKPOINT
is set to the default value, pytest will use the custom internal PDB trace UI instead of the system defaultPdb
. - When tests are complete, the system will default back to the system
Pdb
trace UI. - With
--pdb
passed to pytest, the custom internal Pdb trace UI is used with bothbreakpoint()
and failed tests/unhandled exceptions. --pdbcls
can be used to specify a custom debugger class.
分析測試執行時間
得到執行最慢的10個測試
pytest --durations=10
默認情況下,pytest不顯示執行時間特別短的測試(<0.01s),除非使用-vv選項。
===================================================================================================== slowest 2 test durations ======================================================================================================
1.81s call test_class.py::TestClass::test_two
0.01s setup test_tmpdir.py::test_needsfiles
缺陷處理
從5.0版本開始支持
faulthandler標準庫用於在產生段錯誤或超時時dump Python tracebacks.
這個模塊在pytest中自動加載,除非使用 -p no:faulthandler
.
faulthandler_timeout=X配置可以用於
dump所有線程的traceback,如果一個測試例運行超過X秒(在Windows上不支持).
提前加載插件
pytest -p mypluginmodule
禁用插件
pytest -p no:doctest
在Python代碼中使用pytest
pytest.main()
使用參數
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()])
$ python myinvoke.py
.FEsxX. [100%]*** test run reporting finishing
================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________
@pytest.fixture
def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 0
E assert 0
test_example.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_example.py::test_fail - assert 0
ERROR test_example.py::test_error - assert 0