通過 python -m pytest 調用 pytest
您可以從指令行通過 Python 解釋器調用測試:
python -m pytest [...]
這幾乎等同于直接調用指令行腳本 pytest [...],隻不過通過 python 調用還會将目前目錄添加到 sys.path。
可能的退出碼
運作 pytest 可能會産生六種不同的退出代碼:
- 退出代碼 0: 所有測試用例均已收集并成功通過
- 退出代碼 1: 收集并運作了測試用例,但有些測試失敗了
- 退出代碼 2 :測試執行被使用者中斷
- 退出代碼 3 :執行測試時發生内部錯誤
- 退出代碼 4: pytest 指令行使用錯誤
- 退出代碼 5: 未收集到測試用例
它們由 pytest.ExitCode 枚舉表示。作為公共 API 一部分的退出代碼可以使用以下方法直接導入和通路:
from pytest import ExitCode
Note:如果您想在某些情況下自定義退出代碼,特别是在未收集到測試用例時,請考慮使用 pytest-custom_exit_code 插件。
擷取有關版本、選項名稱、環境變量的幫助
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
完整的指令行标志可以在參考資料中找到。
第一次(或 N 次)失敗後停止
在前 (N) 次失敗後停止測試過程:
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"
這将運作包含與給定字元串表達式比對的名稱(不區分大小寫)的測試,其中可以包括使用檔案名、類名和函數名作為變量的 Python 運算符。上面的示例将運作 TestMyClass.test_something 而不執行 TestMyClass.test_method_simple。
使用Node ID 運作測試
每個被收集的測試用例都配置設定了一個唯一的 nodeid,它由子產品檔案名後跟類名、函數名和參數化參數等說明符組成,由 :: 字元分隔。
在子產品中運作指定測試:
pytest test_mod.py::test_func
在指令行中指定測試方法的另一個示例:
pytest test_mod.py::TestClass::test_method
通過标記表達式運作測試
pytest -m slow
這将運作所有用 @pytest.mark.slow 裝飾器裝飾的測試用例。
有關更多資訊,請參閱标記
從包運作測試
pytest --pyargs pkg.testing
這将導入 pkg.testing 并使用其檔案系統位置來查找和運作測試。
修改 Python 跟蹤資訊列印
修改跟蹤資訊列印的示例:
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 導緻在列印錯誤時會有很長的跟蹤(長于 --tb=long)。它還確定在 KeyboardInterrupt (Ctrl+C) 上列印堆棧回溯。如果測試花費的時間太長并且您使用 Ctrl+C 中斷它們以找出測試挂起的位置,這将非常有用。預設情況下不會顯示任何輸出(因為 KeyboardInterrupt 被 pytest 捕獲)。通過使用此選項,您可以確定顯示跟蹤資訊。
詳細總結報告
-r 标志可用于在測試會話結束時顯示“簡短的測試摘要資訊”,以便在大型測試套件中輕松獲得所有失敗、跳過、xfail 等的清晰圖表。
它預設為 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-6.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] 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使用的意思是“除通過之外的所有”。
以下是可以使用的可用字元的完整清單:
-
- failedf
-
- errorE
-
- skippeds
-
- xfailedx
-
- xpassedX
-
- passedp
-
- passed with outputP
用于(取消)選擇組的特殊字元:
-
- all excepta
pP
-
- allA
-
- none, this can be used to display nothing (sinceN
is the default)fE
可以使用多個字元,是以例如僅檢視失敗和跳過的測試,您可以執行:
$ pytest -rfs
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-6.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] 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-6.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 調試器)
Python 帶有一個名為 PDB 的内置 Python 調試器。 pytest 允許通過指令行選項進入 PDB 提示符:
pytest --pdb
這将在每次失敗(或鍵盤中斷)時調用 Python 調試器。通常,您可能隻想對第一個失敗的測試執行此操作以了解特定的失敗情況:
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 中。在互動式使用中,這允許使用任何調試工具進行事後調試。也可以手動通路異常資訊,例如:
>>> import sys
>>> sys.last_traceback.tb_lineno
42
>>> sys.last_value
AssertionError('assert result == "ok"',)
在測試開始時進入到 PDB(Python 調試器)
pytest 允許在每個測試開始時通過指令行選項立即進入 PDB 提示符:
pytest --trace
這将在每次測試開始時調用 Python 調試器
設定斷點
要在代碼中設定斷點,請在代碼中使用本機Python的 import pdb;pdb.set_trace() 調用,pytest 會自動禁用該測試的輸出捕獲:
- 其他測試中的輸出捕獲不受影響。
- 任何先前已經被捕獲的輸出将會被這樣處理。
- 結束調試器會話時(通過 continue 指令)恢複輸出捕獲。
使用内置的breakpoint函數
Python 3.7 引入了一個内置的 breakpoint() 函數。 Pytest 支援使用具有以下行為的 breakpoint():
- 當調用 breakpoint() 并且 PYTHONBREAKPOINT 設定為預設值時,pytest 将使用自定義内部 PDB 跟蹤 UI 而不是系統預設 Pdb。
- 測試完成後,系統将預設傳回到系統 Pdb 跟蹤 UI。
- 通過将 --pdb 傳遞給 pytest,自定義内部 Pdb 跟蹤 UI 與 breakpoint() 和失敗的測試/未處理的異常一起使用。
- 通過将 --pdb 傳遞給 pytest,自定義内部 Pdb 跟蹤 UI 與 breakpoint() 和失敗的測試/未處理的異常一起使用。
分析測試執行持續時間
在 6.0 版本中發生變化。
要擷取超過 1.0 秒的最慢的 10 個測試持續時間的清單:
pytest --durations=10 --durations-min=1.0
預設情況下,除非在指令行上傳遞 -vv,否則 pytest 不會測試執行時間顯示太小(<0.005s)的測試用例。
故障處理程式
5.0 版本中的新功能。
python 的faulthandler 标準子產品可用于在段錯誤或逾時後轉儲 Python 的跟蹤資訊。
該子產品會自動啟用 pytest 運作,除非在指令行上給出 -p no:faulthandler。
如果測試需要超過 X 秒才能完成,則 faulthandler_timeout=X 配置選項可用于轉儲所有線程的跟蹤資訊(在 Windows 上不可用)。
注意:
此功能已從外部 pytest-faulthandler 插件內建,有兩個小差別:
- 要禁用它,請使用 -p no:faulthandler 而不是 --no-faulthandler:前者可以與任何插件一起使用,是以它可以節省一個選項。
- --faulthandler-timeout 指令行選項已成為 faulthandler_timeout 配置選項。它仍然可以從指令行使用 -o faulthandler_timeout=X 進行配置。
關于不可引發的異常和未處理的線程異常的警告
6.2 版本中的新功能。
注意: 這些功能僅适用于 Python>=3.8。
未處理的異常是在無法傳播給調用者的情況下引發的異常。最常見的情況是在 __del__ 實作中引發異常。
未處理的線程異常是在 Thread 中引發但未處理的異常,導緻線程不正常地終止。
這兩種類型的異常通常都被認為是錯誤,但可能不會被注意到,因為它們不會導緻程式本身崩潰。 Pytest 檢測到這些情況并發出在測試運作摘要中可見的警告。
插件在 pytest 運作時自動啟用,除非在指令行上給出了 -p no:unraisableexception(對于無法引發的異常)和 -p no:threadexception(對于線程異常)選項。
可以使用 pytest.mark.filterwarnings 标記選擇性地消除警告。警告類别是 pytest.PytestUnraisableExceptionWarning 和 pytest.PytestUnhandledThreadExceptionWarning。
建立 JUnitXML 格式檔案
要建立可由 Jenkins 或其他持續內建伺服器讀取的結果檔案,請使用以下調用:
pytest --junitxml=path
在path上建立一個 XML 檔案。
要設定根測試套件 xml 項的名稱,您可以在配置檔案中配置 junit_suite_name 選項:
[pytest]
junit_suite_name = my_suite
4.0 版本中的新功能。
JUnit XML 規範似乎表明“時間”屬性應該報告總測試執行時間,包括setup和teardown (1, 2)。
這是預設的 pytest 行為。要僅報告呼叫持續時間,請像這樣配置 junit_duration_report 選項:
[pytest]
junit_duration_report = call
record_property
如果要記錄測試的其他資訊,可以使用 record_property 測試裝置:
def test_function(record_property):
record_property("example_key", 1)
assert True
這将向生成的testcase标簽添加一個額外的屬性 example_key="1":
<testcase classname="test_function" file="test_function.py" line="0" name="test_function" time="0.0009">
<properties>
<property name="example_key" value="1" />
</properties>
</testcase>
或者,您可以将此功能與自定義标記內建:
# 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
這會導緻:
<testcase classname="test_function" file="test_function.py" line="0" name="test_function" time="0.0009">
<properties>
<property name="test_id" value="1501" />
</properties>
</testcase>
Warning:請注意,使用此功能将破壞最新 JUnitXML 架構的架構驗證。當與某些 CI 伺服器一起使用時,這可能是一個問題。
record_xml_attribute
要将額外的 xml 屬性添加到 testcase 元素,您可以使用 record_xml_attribute 測試裝置。這也可用于覆寫現有值:
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 不同,這不會添加新的子元素。相反,這将在生成的測試用例标記中添加一個屬性 assertions="REQ-1234" 并使用 "classname=custom_classname" 覆寫預設類名:
<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>
Warning:
record_xml_attribute 是一個實驗性功能,在未來的版本中,它的界面可能會被更強大和更通用的東西所取代。但是,功能本身将保留。在使用 ci 工具解析 xml 報告時,使用這個 over record_xml_property 會有所幫助。但是,一些解析器對允許的元素和屬性非常嚴格。許多工具使用 xsd 模式(如下例所示)來驗證傳入的 xml。確定您使用的是解析器允許的屬性名稱。
下面是 Jenkins 用來驗證 XML 報告的 Scheme:
<xs:element name="testcase">
<xs:complexType>
<xs:sequence>
<xs:element ref="skipped" minOccurs="0" maxOccurs="1"/>
<xs:element ref="error" minOccurs="0" maxOccurs="unbounded"/>
<xs:element ref="failure" minOccurs="0" maxOccurs="unbounded"/>
<xs:element ref="system-out" minOccurs="0" maxOccurs="unbounded"/>
<xs:element ref="system-err" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="assertions" type="xs:string" use="optional"/>
<xs:attribute name="time" type="xs:string" use="optional"/>
<xs:attribute name="classname" type="xs:string" use="optional"/>
<xs:attribute name="status" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
警告 :請注意,使用此功能将破壞最新 JUnitXML 架構的架構驗證。當與某些 CI 伺服器一起使用時,這可能是一個問題。
record_testsuite_property
4.5 版本中的新功能。
如果您想在測試套件級别添加一個屬性節點,其中可能包含與所有測試相關的屬性,您可以使用 record_testsuite_property 會話範圍裝置:
record_testsuite_property 會話範圍裝置可用于添加與所有測試相關的屬性。
import pytest
@pytest.fixture(scope="session", autouse=True)
def log_global_env_facts(record_testsuite_property):
record_testsuite_property("ARCH", "PPC")
record_testsuite_property("STORAGE_TYPE", "CEPH")
class TestMe:
def test_foo(self):
assert True
該裝置是一個可調用的方法,它接收在生成的 xml 的測試套件級别添加的 <property> 标記的名稱和值:
<testsuite errors="0" failures="0" name="pytest" skipped="0" tests="1" time="0.006">
<properties>
<property name="ARCH" value="PPC"/>
<property name="STORAGE_TYPE" value="CEPH"/>
</properties>
<testcase classname="test_me.TestMe" file="test_me.py" line="16" name="test_foo" time="0.000243663787842"/>
</testsuite>
name 必須是一個字元串,value 将被轉換為一個字元串并正确地進行 xml 轉義。
生成的 XML 與最新的 xunit 标準相容,這與 record_property 和 record_xml_attribute 不同。
建立結果日志格式檔案
要建立純文字機器可讀結果檔案,您可以輸入:
pytest --resultlog=path
并檢視path位置檔案的内容。使用此類檔案,例如通過 PyPy-test 網頁顯示多個修訂版的測試結果。
Warning:
此選項很少使用,并計劃在 pytest 6.0 中删除。 如果您使用此選項,請考慮改用新的 pytest-reportlog 插件。 有關更多資訊,請參閱棄用文檔。
将測試報告發送到線上 pastebin 服務
為每個測試失敗建立一個 URL:
pytest --pastebin=failed
這将向遠端粘貼服務送出測試運作資訊,并為每個失敗提供一個 URL。如果您隻想發送一個特定的失敗,您可以像往常一樣選擇測試或添加例如 -x。
為整個測試會話日志建立 URL:
pytest --pastebin=all
目前隻實作了粘貼到 http://bpaste.net 服務。 在 5.2 版中更改。 如果建立 URL 因任何原因失敗,則會生成警告而不是使整個測試套件失敗。
提前加載插件
您可以使用 -p 選項在指令行中顯式加載插件(内部和外部):
pytest -p mypluginmodule
該選項接收一個name參數,它可以是:
- 完整的子產品點名,例如 myproject.plugins。這個帶點的名稱必須是可導入的。
- 插件的入口點名稱。這是在注冊插件時傳遞給 setuptools 的名稱。例如,要提前加載 pytest-cov 插件,您可以使用:
pytest -p pytest_cov
禁用插件
要在調用時禁用加載特定插件,請使用 -p 選項和字首 no:。
示例:要禁用加載插件 doctest,這個插件負責從文本檔案執行 doctest 測試,請像這樣調用 pytest:
pytest -p no:doctest
從 Python 代碼調用 pytest
您可以直接從 Python 代碼調用 pytest:
pytest.main()
這就像您從指令行調用“pytest”一樣。它不會引發 SystemExit 而是傳回退出代碼。您可以傳入選項和參數:
pytest.main(["-x", "mytestdir"])
您可以為 pytest.main 指定其他插件:
# content of myinvoke.py
import pytest
class MyPlugin:
def pytest_sessionfinish(self):
print("*** test run reporting finishing")
pytest.main(["-qq"], plugins=[MyPlugin()])
運作它會顯示添加了 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
Note:
調用 pytest.main() 将導緻導入您的測試及其導入的任何子產品。由于 python 導入系統的緩存機制,從同一程序對 pytest.main() 進行後續調用不會反映調用之間對這些檔案的更改。是以,不建議從同一程序多次調用 pytest.main() (例如,為了重新運作測試)。