天天看點

Pytest-用法和調用

通過 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使用的意思是“除通過之外的所有”。

以下是可以使用的可用字元的完整清單:

  • f

     - failed
  • E

     - error
  • s

     - skipped
  • x

     - xfailed
  • X

     - xpassed
  • p

     - passed
  • P

     - passed with output

用于(取消)選擇組的特殊字元:

  • a

     - all except 

    pP

  • A

     - all
  • N

     - none, this can be used to display nothing (since 

    fE

     is the default)

可以使用多個字元,是以例如僅檢視失敗和跳過的測試,您可以執行:

$ 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() (例如,為了重新運作測試)。