如何執行 doctest

預設情況下,所有符合 test*.txt 模式的檔案都將通過 python 標準 doctest 模組執行。您可以通過發出

pytest --doctest-glob="*.rst"

在命令列中來變更模式。--doctest-glob 可以在命令列中多次給定。

如果您然後有一個像這樣的文字檔案

# content of test_example.txt

hello this is a doctest
>>> x = 3
>>> x
3

那麼您可以直接調用 pytest

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_example.txt .                                                   [100%]

============================ 1 passed in 0.12s =============================

預設情況下,pytest 將收集 test*.txt 檔案以尋找 doctest 指令,但是您可以使用 --doctest-glob 選項(允許多次)傳遞其他 glob。

除了文字檔案之外,您還可以從類別和函數的 docstring 中直接執行 doctest,包括從測試模組中

# content of mymodule.py
def something():
    """a doctest in a docstring
    >>> something()
    42
    """
    return 42
$ pytest --doctest-modules
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items

mymodule.py .                                                        [ 50%]
test_example.txt .                                                   [100%]

============================ 2 passed in 0.12s =============================

您可以通過將這些變更放入像這樣的 pytest.ini 檔案中,使這些變更在您的專案中永久生效

# content of pytest.ini
[pytest]
addopts = --doctest-modules

編碼

預設編碼為 UTF-8,但是您可以使用 doctest_encoding ini 選項指定將用於這些 doctest 檔案的編碼

# content of pytest.ini
[pytest]
doctest_encoding = latin1

使用 'doctest' 選項

Python 的標準 doctest 模組提供了一些 選項 來配置 doctest 測試的嚴格程度。在 pytest 中,您可以使用組態檔啟用這些標誌。

例如,要使 pytest 忽略尾隨空格並忽略冗長的異常堆疊追蹤,您可以直接編寫

[pytest]
doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL

或者,可以通過 doctest 本身中的內聯註解啟用選項

>>> something_that_raises()  # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
ValueError: ...

pytest 還引入了新的選項

  • ALLOW_UNICODE:啟用後,u 前綴將從預期的 doctest 輸出中的 unicode 字串中剝離。這允許 doctest 在 Python 2 和 Python 3 中不變地運行。

  • ALLOW_BYTES:類似地,b 前綴將從預期的 doctest 輸出中的位元組字串中剝離。

  • NUMBER:啟用後,浮點數只需要與您在預期的 doctest 輸出中寫入的精度相符。這些數字使用 pytest.approx() 進行比較,相對容差等於精度。例如,以下輸出在比較 3.14pytest.approx(math.pi, rel=10**-2) 時,只需要匹配到小數點後 2 位

    >>> math.pi
    3.14
    

    如果您寫了 3.1416,那麼實際輸出將需要匹配到大約小數點後 4 位;依此類推。

    這避免了由於有限的浮點精度而導致的誤報,例如這樣

    Expected:
        0.233
    Got:
        0.23300000000000001
    

    NUMBER 也支援浮點數列表 – 事實上,它匹配輸出中任何地方出現的浮點數,甚至在字串內部!這表示在全球範圍內在您的組態檔案的 doctest_optionflags 中啟用它可能不合適。

    在版本 5.1 中新增。

失敗時繼續

預設情況下,pytest 將僅報告給定 doctest 的第一個失敗。如果您即使在發生失敗時也想繼續測試,請執行

pytest --doctest-modules --doctest-continue-on-failure

輸出格式

您可以通過使用標準 doctest 模組格式中的一個選項(請參閱 doctest.REPORT_UDIFF, doctest.REPORT_CDIFF, doctest.REPORT_NDIFF, doctest.REPORT_ONLY_FIRST_FAILURE)來變更 doctest 失敗時的差異輸出格式

pytest --doctest-modules --doctest-report none
pytest --doctest-modules --doctest-report udiff
pytest --doctest-modules --doctest-report cdiff
pytest --doctest-modules --doctest-report ndiff
pytest --doctest-modules --doctest-report only_first_failure

pytest 特有功能

提供了一些功能,以使編寫 doctest 更容易或更好地與您現有的測試套件整合。但是請記住,通過使用這些功能,您將使您的 doctest 與標準 doctests 模組不相容。

使用 fixtures

可以使用 getfixture helper 來使用 fixtures

# content of example.rst
>>> tmp = getfixture('tmp_path')
>>> ...
>>>

請注意,fixture 需要定義在 pytest 可見的位置,例如,conftest.py 檔案或外掛程式;除非通過 python_files 明確配置,否則通常不會掃描包含 docstring 的常規 python 檔案以尋找 fixtures。

此外,執行文字 doctest 檔案時,支援 usefixtures 標記和標記為 autouse 的 fixtures。

‘doctest_namespace’ fixture

doctest_namespace fixture 可用於將項目注入到您的 doctest 運行的命名空間中。它旨在在您自己的 fixtures 中使用,以便為使用它們的測試提供上下文。

doctest_namespace 是一個標準 dict 物件,您可以在其中放置您想要出現在 doctest 命名空間中的物件

# content of conftest.py
import pytest
import numpy


@pytest.fixture(autouse=True)
def add_np(doctest_namespace):
    doctest_namespace["np"] = numpy

然後可以直接在您的 doctest 中使用它們

# content of numpy.py
def arange():
    """
    >>> a = np.arange(10)
    >>> len(a)
    10
    """

請注意,與常規 conftest.py 一樣,fixtures 是在 conftest 所在的目錄樹中發現的。這表示如果您將 doctest 與原始碼放在一起,則相關的 conftest.py 需要位於相同的目錄樹中。fixtures 不會在同級目錄樹中被發現!

跳過測試

出於人們可能想要跳過常規測試的相同原因,也可以跳過 doctest 內部的測試。

要跳過 doctest 內部的單個檢查,您可以使用標準 doctest.SKIP 指令

def test_random(y):
    """
    >>> random.random()  # doctest: +SKIP
    0.156231223

    >>> 1 + 1
    2
    """

這將跳過第一個檢查,但不跳過第二個。

pytest 還允許在 doctest 內部使用標準 pytest 函數 pytest.skip()pytest.xfail(),這可能很有用,因為您可以根據外部條件跳過/xfail 測試

>>> import sys, pytest
>>> if sys.platform.startswith('win'):
...     pytest.skip('this doctest does not work on Windows')
...
>>> import fcntl
>>> ...

但是,不鼓勵使用這些函數,因為它會降低 docstring 的可讀性。

注意

pytest.skip()pytest.xfail() 的行為取決於 doctest 是在 Python 檔案(在 docstring 中)還是包含與文字混合的 doctest 的文字檔案中

  • Python 模組(docstring):這些函數僅在該特定 docstring 中起作用,讓同一個模組中的其他 docstring 正常執行。

  • 文字檔案:這些函數將跳過/xfail 整個檔案其餘部分的檢查。

替代方案

雖然內建的 pytest 支援為使用 doctest 提供了一組良好的功能,但是如果您廣泛使用它們,您可能會對那些添加了更多功能並包含 pytest 整合的外部套件感興趣

  • pytest-doctestplus:提供進階的 doctest 支援,並啟用 reStructuredText (“.rst”) 檔案的測試。

  • Sybil:提供了一種通過從文件來源解析範例並在您的常規測試運行中評估已解析的範例來測試文件中的範例的方法。