doctest --- 測試交互性的Python示例?

** 源代碼 ** Lib/doctest.py


doctest 模塊尋找像Python交互式代碼的文本,然后執行這些代碼來(lái)確保它們的確就像展示的那樣正確運行,有許多方法來(lái)使用doctest:

  • 通過(guò)驗證所有交互式示例仍然按照記錄的方式工作,以此來(lái)檢查模塊的文檔字符串是否是最新的。

  • 通過(guò)驗證來(lái)自一個(gè)測試文件或一個(gè)測試對象的交互式示例按預期工作,來(lái)進(jìn)行回歸測試。

  • 為一個(gè)包寫(xiě)指導性的文檔,用輸入輸出的例子來(lái)說(shuō)明。 取決于是強調例子還是說(shuō)明性的文字,這有一種 "文本測試 "或 "可執行文檔 "的風(fēng)格。

下面是一個(gè)小卻完整的示例模塊:

"""
This is the "example" module.

The example module supplies one function, factorial().  For example,

>>> factorial(5)
120
"""

def factorial(n):
    """Return the factorial of n, an exact integer >= 0.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(30)
    265252859812191058636308480000000
    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0

    Factorials of floats are OK, but the float must be an exact integer:
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
    >>> factorial(30.0)
    265252859812191058636308480000000

    It must also not be ridiculously large:
    >>> factorial(1e100)
    Traceback (most recent call last):
        ...
    OverflowError: n too large
    """

    import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result


if __name__ == "__main__":
    import doctest
    doctest.testmod()

如果你直接在命令行里運行 example.py , doctest 將發(fā)揮它的作用。

$ python example.py
$

沒(méi)有輸出! 這很正常,這意味著(zhù)所有的例子都成功了。 把 -v 傳給腳本,doctest 會(huì )打印出它所嘗試的詳細日志,并在最后打印出一個(gè)總結。

$ python example.py -v
Trying:
    factorial(5)
Expecting:
    120
ok
Trying:
    [factorial(n) for n in range(6)]
Expecting:
    [1, 1, 2, 6, 24, 120]
ok

以此類(lèi)推,最終以:

Trying:
    factorial(1e100)
Expecting:
    Traceback (most recent call last):
        ...
    OverflowError: n too large
ok
2 items passed all tests:
   1 tests in __main__
   8 tests in __main__.factorial
9 tests in 2 items.
9 passed and 0 failed.
Test passed.
$

這就是對于高效地使用 doctest 你所需要知道的一切!開(kāi)始上手吧。 下面的章節提供了完整的細節。 請注意,在標準的 Python 測試套件和庫中有許多 doctest 的例子。特別有用的例子可以在標準測試文件 Lib/test/test_doctest.py 中找到。

簡(jiǎn)單用法:檢查Docstrings中的示例?

開(kāi)始使用doctest的最簡(jiǎn)單方法(但不一定是你將繼續這樣做的方式)是結束每個(gè)模塊 M 使用:

if __name__ == "__main__":
    import doctest
    doctest.testmod()

doctest 會(huì )隨后檢查模塊 M 中的文檔字符串。

以腳本形式運行該模塊會(huì )使文檔中的例子得到執行和驗證:

python M.py

這不會(huì )顯示任何東西,除非一個(gè)例子失敗了,在這種情況下,失敗的例子和失敗的原因會(huì )被打印到stdout,最后一行的輸出是``***Test Failed*** N failures.``,其中*N*是失敗的例子的數量。

用``-v``來(lái)運行它來(lái)切換,而不是:

python M.py -v

并將所有嘗試過(guò)的例子的詳細報告打印到標準輸出,最后還有各種總結。

你可以通過(guò)向 testmod() 傳遞 verbose=True 來(lái)強制執行 verbose 模式,或者通過(guò)傳遞 verbose=False 來(lái)禁止它。 在這兩種情況下,sys.argv 都不會(huì )被 testmod() 檢查(所以傳遞 -v 或不傳遞都沒(méi)有影響)。

還有一個(gè)命令行快捷方式用于運行 testmod()。 你可以指示Python解釋器直接從標準庫中運行doctest模塊,并在命令行中傳遞模塊名稱(chēng):

python -m doctest -v example.py

這將導入 example.py 作為一個(gè)獨立的模塊,并對其運行 testmod()。 注意,如果該文件是一個(gè)包的一部分,并且從該包中導入了其他子模塊,這可能無(wú)法正確工作。

關(guān)于 testmod() 的更多信息,請參見(jiàn) 基本API 部分。

簡(jiǎn)單的用法:檢查文本文件中的例子?

doctest 的另一個(gè)簡(jiǎn)單應用是測試文本文件中的交互式例子。 這可以用 testfile() 函數來(lái)完成;:

import doctest
doctest.testfile("example.txt")

這個(gè)簡(jiǎn)短的腳本執行并驗證文件 example.txt 中包含的任何交互式 Python 示例。該文件的內容被當作一個(gè)巨大的文檔串來(lái)處理;該文件不需要包含一個(gè)Python程序!例如,也許 example.txt 包含以下內容:

The ``example`` module
======================

Using ``factorial``
-------------------

This is an example text file in reStructuredText format.  First import
``factorial`` from the ``example`` module:

    >>> from example import factorial

Now use it:

    >>> factorial(6)
    120

運行``doctest.testfile("example.txt")``,然后發(fā)現這個(gè)文檔中的錯誤:

File "./example.txt", line 14, in example.txt
Failed example:
    factorial(6)
Expected:
    120
Got:
    720

testmod() 一樣, testfile() 不會(huì )顯示任何東西,除非一個(gè)例子失敗。 如果一個(gè)例子失敗了,那么失敗的例子和失敗的原因將被打印到stdout,使用的格式與 testmod() 相同。

默認情況下,testfile() 在調用模塊的目錄中尋找文件。參見(jiàn)章節 基本API,了解可用于告訴它在其他位置尋找文件的可選參數的描述。

testmod() 一樣,testfile() 的詳細程度可以通過(guò)命令行 -v 切換或可選的關(guān)鍵字參數 verbose 來(lái)設置。

還有一個(gè)命令行快捷方式用于運行 testfile()。 你可以指示Python解釋器直接從標準庫中運行doctest模塊,并在命令行中傳遞文件名:

python -m doctest -v example.txt

因為文件名沒(méi)有以 .py 結尾,doctest 推斷它必須用 testfile() 運行,而不是 testmod()。

關(guān)于 testfile() 的更多信息,請參見(jiàn) 基本API 一節。

它是如何工作的?

這一節詳細研究了doctest的工作原理:它查看哪些文檔串,它如何找到交互式的用例,它使用什么執行環(huán)境,它如何處理異常,以及如何用選項標志來(lái)控制其行為。這是你寫(xiě)doctest例子所需要知道的信息;關(guān)于在這些例子上實(shí)際運行doctest的信息,請看下面的章節。

哪些文件串被檢查了??

模塊的文檔串以及所有函數、類(lèi)和方法的文檔串都將被搜索。 導入模塊的對象不被搜索。

此外,如果 M.__test__ 存在并且 "為真值",則它必須是一個(gè)字典,其中每個(gè)條目都將一個(gè)(字符串)名稱(chēng)映射到一個(gè)函數對象、類(lèi)對象或字符串。 從 M.__test__ 找到的函數和類(lèi)對象的文檔字符串會(huì )被搜索,而字符串會(huì )被當作文檔字符串來(lái)處理。 在輸出時(shí),每個(gè)鍵 KM.__test__ 中都顯示為其名稱(chēng)

<name of M>.__test__.K

任何發(fā)現的類(lèi)都會(huì )以類(lèi)似的方式進(jìn)行遞歸搜索,以測試其包含的方法和嵌套類(lèi)中的文檔串。

文檔串的例子是如何被識別的??

在大多數情況下,對交互式控制臺會(huì )話(huà)的復制和粘貼功能工作得很好,但是 doctest 并不試圖對任何特定的 Python shell 進(jìn)行精確的模擬。

>>>
>>> # comments are ignored
>>> x = 12
>>> x
12
>>> if x == 13:
...     print("yes")
... else:
...     print("no")
...     print("NO")
...     print("NO!!!")
...
no
NO
NO!!!
>>>

任何預期的輸出必須緊隨包含代碼的最后 '>>> ''... ' 行,預期的輸出(如果有的話(huà))延伸到下一 '>>> ' 行或全空白行。

fine輸出:

  • 預期輸出不能包含一個(gè)全白的行,因為這樣的行被認為是預期輸出的結束信號。 如果預期的輸出包含一個(gè)空行,在你的測試例子中,在每一個(gè)預期有空行的地方加上``<BLANKLINE>``。

  • 所有硬制表符都被擴展為空格,使用 8 列的制表符。由測試代碼生成的輸出中的制表符不會(huì )被修改。 因為樣本輸出中的任何硬制表符都會(huì )被擴展,這意味著(zhù)如果代碼輸出包括硬制表符,文檔測試通過(guò)的唯一方法是 NORMALIZE_WHITESPACE 選項或者 指令 是有效的。 另外,測試可以被重寫(xiě),以捕獲輸出并將其與預期值進(jìn)行比較,作為測試的一部分。這種對源碼中標簽的處理是通過(guò)試錯得出的,并被證明是最不容易出錯的處理方式。通過(guò)編寫(xiě)一個(gè)自定義的 DocTestParser 類(lèi),可以使用一個(gè)不同的算法來(lái)處理標簽。

  • 向stdout的輸出被捕獲,但不向stderr輸出(異?;厮萃ㄟ^(guò)不同的方式被捕獲)。

  • 如果你在交互式會(huì )話(huà)中通過(guò)反斜線(xiàn)續行,或出于任何其他原因使用反斜線(xiàn),你應該使用原始文件串,它將完全保留你輸入的反斜線(xiàn):

    >>>
    >>> def f(x):
    ...     r'''Backslashes in a raw docstring: m\n'''
    >>> print(f.__doc__)
    Backslashes in a raw docstring: m\n
    

    否則,反斜杠將被解釋為字符串的一部分。例如,上面的``n``會(huì )被解釋為一個(gè)換行符。 另外,你可以在doctest版本中把每個(gè)反斜杠加倍(而不使用原始字符串):

    >>>
    >>> def f(x):
    ...     '''Backslashes in a raw docstring: m\\n'''
    >>> print(f.__doc__)
    Backslashes in a raw docstring: m\n
    
  • 起始列并不重要:

    >>>
    >>> assert "Easy!"
          >>> import math
              >>> math.floor(1.9)
              1
    

    并從預期的輸出中剝離出與開(kāi)始該例子的初始 '>>> ' 行中出現的同樣多的前導空白字符。

什么是執行上下文??

默認情況下,每次 doctest 找到要測試的文檔串時(shí),它都會(huì )使用 M 的*淺層副本*,這樣運行測試就不會(huì )改變模塊的真正全局變量,而且 M 的一個(gè)測試也不會(huì )留下臨時(shí)變量,從而意外地讓另一個(gè)測試通過(guò)。這意味著(zhù)例子可以自由地使用 M 中的任何頂級定義的名字,以及正在運行的文檔串中早期定義的名字。用例不能看到其他文檔串中定義的名字。

你可以通過(guò)將``globs=your_dict``傳遞給 testmod()testfile() 來(lái)強制使用你自己的dict作為執行環(huán)境。

異常如何處理??

沒(méi)問(wèn)題,只要回溯是這個(gè)例子產(chǎn)生的唯一輸出:只要粘貼回溯即可。1 由于回溯所包含的細節可能會(huì )迅速變化(例如,確切的文件路徑和行號),這是doctest努力使其接受的內容具有靈活性的一種情況。

簡(jiǎn)單實(shí)例:

>>>
>>> [1, 2, 3].remove(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list

如果 ValueError 被觸發(fā),該測試就會(huì )成功,list.remove(x): x not in list 的細節如圖所示。

異常的預期輸出必須以回溯頭開(kāi)始,可以是以下兩行中的任何一行,縮進(jìn)程度與例子中的第一行相同:

Traceback (most recent call last):
Traceback (innermost last):

回溯頭的后面是一個(gè)可選的回溯堆棧,其內容被doctest忽略。 回溯堆棧通常是省略的,或者從交互式會(huì )話(huà)中逐字復制的。

回溯堆棧的后面是最有用的部分:包含異常類(lèi)型和細節的一行(幾行)。 這通常是回溯的最后一行,但如果異常有多行細節,則可以延伸到多行:

>>>
>>> raise ValueError('multi\n    line\ndetail')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: multi
    line
detail

最后三行(以 ValueError 開(kāi)頭)將與異常的類(lèi)型和細節進(jìn)行比較,其余的被忽略。

最佳實(shí)踐是省略回溯棧,除非它為這個(gè)例子增加了重要的文檔價(jià)值。 因此,最后一個(gè)例子可能更好,因為:

>>>
>>> raise ValueError('multi\n    line\ndetail')
Traceback (most recent call last):
    ...
ValueError: multi
    line
detail

請注意,回溯的處理方式非常特別。 特別是,在重寫(xiě)的例子中,... 的使用與 doctest 的 ELLIPSIS 選項無(wú)關(guān)。 該例子中的省略號可以不寫(xiě),也可以是三個(gè)(或三百個(gè))逗號或數字,或者是一個(gè)縮進(jìn)的 Monty Python 短劇的劇本。

有些細節你應該讀一遍,但不需要記?。?/p>

  • Doctest 不能猜測你的預期輸出是來(lái)自異?;厮葸€是來(lái)自普通打印。 因此,例如,一個(gè)期望 ValueError: 42 is prime 的用例將通過(guò)測試,無(wú)論 ValueError 是真的被觸發(fā),或者該用例只是打印了該回溯文本。 在實(shí)踐中,普通輸出很少以回溯標題行開(kāi)始,所以這不會(huì )產(chǎn)生真正的問(wèn)題。

  • 回溯堆棧的每一行(如果有的話(huà))必須比例子的第一行縮進(jìn), 或者 以一個(gè)非字母數字的字符開(kāi)始?;厮蓊^之后的第一行縮進(jìn)程度相同,并且以字母數字開(kāi)始,被認為是異常細節的開(kāi)始。當然,這對真正的回溯來(lái)說(shuō)是正確的事情。

  • IGNORE_EXCEPTION_DETAIL doctest 選項被指定時(shí),最左邊的冒號后面的所有內容以及異常名稱(chēng)中的任何模塊信息都被忽略。

  • 交互式 shell 省略了一些 SyntaxError 的回溯頭行。但 doctest 使用回溯頭行來(lái)區分異常和非異常。所以在罕見(jiàn)的情況下,如果你需要測試一個(gè)省略了回溯頭的 SyntaxError,你將需要手動(dòng)添加回溯頭行到你的測試用例中。

  • For some exceptions, Python displays the position of the error using ^ markers and tildes:

    >>>
    >>> 1 + None
      File "<stdin>", line 1
        1 + None
        ~~^~~~~~
    TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
    

    由于顯示錯誤位置的行在異常類(lèi)型和細節之前,它們不被doctest檢查。 例如,下面的測試會(huì )通過(guò),盡管它把``^``標記放在了錯誤的位置:

    >>>
    >>> 1 + None
      File "<stdin>", line 1
        1 + None
        ^~~~~~~~
    TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
    

選項標記?

一系列選項旗標控制著(zhù) doctest 的各方面行為。 旗標的符號名稱(chēng)以模塊常量的形式提供,可以一起 bitwise ORed 并傳遞給各種函數。 這些名稱(chēng)也可以在 doctest directives 中使用,并且可以通過(guò) -o 選項傳遞給 doctest 命令行接口。

3.4 新版功能: 命令行選項 -o 。

第一組選項定義了測試語(yǔ)義,控制doctest如何決定實(shí)際輸出是否與用例的預期輸出相匹配方面的問(wèn)題。

doctest.DONT_ACCEPT_TRUE_FOR_1?

默認情況下,如果一個(gè)預期的輸出塊只包含 1,那么實(shí)際的輸出塊只包含 1 或只包含 True 就被認為是匹配的,同樣,0False 也是如此。 當 DONT_ACCEPT_TRUE_FOR_1 被指定時(shí),兩種替換都不允許。 默認行為是為了適應 Python 將許多函數的返回類(lèi)型從整數改為布爾值;期望 "小整數" 輸出的測試在這些情況下仍然有效。 這個(gè)選項可能會(huì )消失,但不會(huì )在幾年內消失。

doctest.DONT_ACCEPT_BLANKLINE?

默認情況下,如果一個(gè)預期輸出塊包含一個(gè)只包含字符串 <BLANKLINE> 的行,那么該行將與實(shí)際輸出中的一個(gè)空行相匹配。 因為一個(gè)真正的空行是對預期輸出的限定,這是傳達預期空行的唯一方法。 當 DONT_ACCEPT_BLANKLINE 被指定時(shí),這種替換是不允許的。

doctest.NORMALIZE_WHITESPACE?

當指定時(shí),所有的空白序列(空白和換行)都被視為相等。預期輸出中的任何空白序列將與實(shí)際輸出中的任何空白序列匹配。默認情況下,空白必須完全匹配。 NORMALIZE_WHITESPACE 在預期輸出非常長(cháng)的一行,而你想把它包在源代碼的多行中時(shí)特別有用。

doctest.ELLIPSIS?

當指定時(shí),預期輸出中的省略號(...)可以匹配實(shí)際輸出中的任何子串。這包括跨行的子串和空子串,所以最好保持簡(jiǎn)單的用法。復雜的用法會(huì )導致與``.*``在正則表達式中容易出現的 "oops, it matched too much!"相同的意外情況。

doctest.IGNORE_EXCEPTION_DETAIL?

When specified, doctests expecting exceptions pass so long as an exception of the expected type is raised, even if the details (message and fully-qualified exception name) don't match.

For example, an example expecting ValueError: 42 will pass if the actual exception raised is ValueError: 3*14, but will fail if, say, a TypeError is raised instead. It will also ignore any fully-qualified name included before the exception class, which can vary between implementations and versions of Python and the code/libraries in use. Hence, all three of these variations will work with the flag specified:

>>>
>>> raise Exception('message')
Traceback (most recent call last):
Exception: message

>>> raise Exception('message')
Traceback (most recent call last):
builtins.Exception: message

>>> raise Exception('message')
Traceback (most recent call last):
__main__.Exception: message

Note that ELLIPSIS can also be used to ignore the details of the exception message, but such a test may still fail based on whether the module name is present or matches exactly.

在 3.2 版更改: IGNORE_EXCEPTION_DETAIL 現在也忽略了與包含被測異常的模塊有關(guān)的任何信息。

doctest.SKIP?

當指定時(shí),完全不運行這個(gè)用例。 這在doctest用例既是文檔又是測試案例的情況下很有用,一個(gè)例子應該包括在文檔中,但不應該被檢查。例如,這個(gè)例子的輸出可能是隨機的;或者這個(gè)例子可能依賴(lài)于測試驅動(dòng)程序所不能使用的資源。

SKIP標志也可用于臨時(shí) "注釋" 用例。

doctest.COMPARISON_FLAGS?

一個(gè)比特或運算將上述所有的比較標志放在一起。

第二組選項控制測試失敗的報告方式:

doctest.REPORT_UDIFF?

當指定時(shí),涉及多行預期和實(shí)際輸出的故障將使用統一的差異來(lái)顯示。

doctest.REPORT_CDIFF?

當指定時(shí),涉及多行預期和實(shí)際輸出的故障將使用上下文差異來(lái)顯示。

doctest.REPORT_NDIFF?

當指定時(shí),差異由``difflib.Differ``來(lái)計算,使用與流行的:file:ndiff.py`工具相同的算法。這是唯一一種標記行內和行間差異的方法。 例如,如果一行預期輸出包含數字``1`,而實(shí)際輸出包含字母``l``,那么就會(huì )插入一行,用圓點(diǎn)標記不匹配的列位置。

doctest.REPORT_ONLY_FIRST_FAILURE?

當指定時(shí),在每個(gè) doctest 中顯示第一個(gè)失敗的用例,但隱藏所有其余用例的輸出。 這將防止 doctest 報告由于先前的失敗而中斷的正確用例;但也可能隱藏獨立于第一個(gè)失敗的不正確用例。 當 REPORT_ONLY_FIRST_FAILURE 被指定時(shí),其余的用例仍然被運行,并且仍然計入報告的失敗總數;只是輸出被隱藏了。

doctest.FAIL_FAST?

當指定時(shí),在第一個(gè)失敗的用例后退出,不嘗試運行其余的用例。因此,報告的失敗次數最多為1。這個(gè)標志在調試時(shí)可能很有用,因為第一個(gè)失敗后的用例甚至不會(huì )產(chǎn)生調試輸出。

doctest命令行接受選項``-f``作為``-o FAIL_FAST``的簡(jiǎn)潔形式。

3.4 新版功能.

doctest.REPORTING_FLAGS?

一個(gè)比特或操作將上述所有的報告標志組合在一起。

還有一種方法可以注冊新的選項標志名稱(chēng),不過(guò)這并不有用,除非你打算通過(guò)子類(lèi)來(lái)擴展 doctest 內部。

doctest.register_optionflag(name)?

用給定的名稱(chēng)創(chuàng )建一個(gè)新的選項標志,并返回新標志的整數值。 register_optionflag() 可以在繼承 OutputCheckerDocTestRunner 時(shí)使用,以創(chuàng )建子類(lèi)支持的新選項。 register_optionflag() 應始終使用以下方式調用:

MY_FLAG = register_optionflag('MY_FLAG')

指令?

Doctest指令可以用來(lái)修改單個(gè)例子的 option flags 。 Doctest指令是在一個(gè)用例的源代碼后面的特殊Python注釋。

directive             ::=  "#" "doctest:" directive_options
directive_options     ::=  directive_option ("," directive_option)\*
directive_option      ::=  on_or_off directive_option_name
on_or_off             ::=  "+" \| "-"
directive_option_name ::=  "DONT_ACCEPT_BLANKLINE" \| "NORMALIZE_WHITESPACE" \| ...

+- 與指令選項名稱(chēng)之間不允許有空格。 指令選項名稱(chēng)可以是上面解釋的任何一個(gè)選項標志名稱(chēng)。

一個(gè)用例的 doctest 指令可以修改 doctest 對該用例的行為。 使用 + 來(lái)啟用指定的行為,或者使用 - 來(lái)禁用它。

For example, this test passes:

>>>
>>> print(list(range(20)))  # doctest: +NORMALIZE_WHITESPACE
[0,   1,  2,  3,  4,  5,  6,  7,  8,  9,
10,  11, 12, 13, 14, 15, 16, 17, 18, 19]

Without the directive it would fail, both because the actual output doesn't have two blanks before the single-digit list elements, and because the actual output is on a single line. This test also passes, and also requires a directive to do so:

>>>
>>> print(list(range(20)))  # doctest: +ELLIPSIS
[0, 1, ..., 18, 19]

Multiple directives can be used on a single physical line, separated by commas:

>>>
>>> print(list(range(20)))  # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
[0,    1, ...,   18,    19]

If multiple directive comments are used for a single example, then they are combined:

>>>
>>> print(list(range(20)))  # doctest: +ELLIPSIS
...                         # doctest: +NORMALIZE_WHITESPACE
[0,    1, ...,   18,    19]

As the previous example shows, you can add ... lines to your example containing only directives. This can be useful when an example is too long for a directive to comfortably fit on the same line:

>>>
>>> print(list(range(5)) + list(range(10, 20)) + list(range(30, 40)))
... # doctest: +ELLIPSIS
[0, ..., 4, 10, ..., 19, 30, ..., 39]

請注意,由于所有的選項都是默認禁用的,而指令只適用于它們出現的用例,所以啟用選項 (通過(guò)指令中的 +) 通常是唯一有意義的選擇。 然而,選項標志也可以被傳遞給運行測試的函數,建立不同的默認值。 在這種情況下,通過(guò)指令中的 - 來(lái)禁用一個(gè)選項可能是有用的。

警告?

doctest 是嚴格地要求在預期輸出中完全匹配。 如果哪怕只有一個(gè)字符不匹配,測試就會(huì )失敗。 這可能會(huì )讓你吃驚幾次,在你確切地了解到 Python 對輸出的保證和不保證之前。 例如,當打印一個(gè)集合時(shí),Python 不保證元素以任何特定的順序被打印出來(lái),所以像:

>>>
>>> foo()
{"Hermione", "Harry"}

是不可靠的!一個(gè)變通方法是用:

>>>
>>> foo() == {"Hermione", "Harry"}
True

來(lái)取代。另一個(gè)是使用

>>>
>>> d = sorted(foo())
>>> d
['Harry', 'Hermione']

還有其他的問(wèn)題,但你會(huì )明白的。

Another bad idea is to print things that embed an object address, like

>>>
>>> id(1.0)  # certain to fail some of the time  
7948648
>>> class C: pass
>>> C()  # the default repr() for instances embeds an address   
<C object at 0x00AC18F0>

The ELLIPSIS directive gives a nice approach for the last example:

>>>
>>> C()  # doctest: +ELLIPSIS
<C object at 0x...>

浮點(diǎn)數在不同的平臺上也會(huì )有小的輸出變化,因為Python在浮點(diǎn)數的格式化上依賴(lài)于平臺的C庫,而C庫在這個(gè)問(wèn)題上的質(zhì)量差異很大。:

>>>
>>> 1./7  # risky
0.14285714285714285
>>> print(1./7) # safer
0.142857142857
>>> print(round(1./7, 6)) # much safer
0.142857

形式``I/2.**J``的數字在所有的平臺上都是安全的,我經(jīng)常設計一些測試的用例來(lái)產(chǎn)生該形式的數:

>>>
>>> 3./4  # utterly safe
0.75

簡(jiǎn)單的分數也更容易讓人理解,這也使得文件更加完善。

基本API?

函數 testmod()testfile() 為 doctest 提供了一個(gè)簡(jiǎn)單的接口,應該足以滿(mǎn)足大多數基本用途。關(guān)于這兩個(gè)函數的不太正式的介紹,請參見(jiàn) 簡(jiǎn)單用法:檢查Docstrings中的示例簡(jiǎn)單的用法:檢查文本文件中的例子 部分。

doctest.testfile(filename, module_relative=True, name=None, package=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, parser=DocTestParser(), encoding=None)?

除了*filename*,所有的參數都是可選的,而且應該以關(guān)鍵字的形式指定。

測試名為 filename 的文件中的用例。 返回``(failure_count, test_count)``。

可選參數 module_relative 指定了文件名的解釋方式。

  • 如果 module_relativeTrue (默認),那么 filename 指定一個(gè)獨立于操作系統的模塊相對路徑。 默認情況下,這個(gè)路徑是相對于調用模塊的目錄的;但是如果指定了 package 參數,那么它就是相對于該包的。 為了保證操作系統的獨立性, filename 應該使用字符來(lái)分隔路徑段,并且不能是一個(gè)絕對路徑 (即不能以 / 開(kāi)始)。

  • 如果 module_relativeFalse,那么 filename 指定了一個(gè)操作系統特定的路徑。路徑可以是絕對的,也可以是相對的;相對路徑是相對于當前工作目錄而言的。

可選參數 name 給出了測試的名稱(chēng);默認情況下,或者如果是``None``,那么使用``os.path.basename(filename)``。

可選參數 package 是一個(gè) Python 包或一個(gè) Python 包的名字,其目錄應被用作模塊相關(guān)文件名的基礎目錄。 如果沒(méi)有指定包,那么調用模塊的目錄將作為模塊相關(guān)文件名的基礎目錄。如果 module_relative 是``False``,那么指定 package 是錯誤的。

可選參數 globs 給出了一個(gè)在執行示例時(shí)用作全局變量的dict。 這個(gè)dict的一個(gè)新的淺層副本將為doctest創(chuàng )建,因此它的用例將從一個(gè)干凈的地方開(kāi)始。默認情況下,或者如果為``None``,使用一個(gè)新的空dict。

可選參數 extraglobs 給出了一個(gè)合并到用于執行用例全局變量中的dict。 這就像 dict.update() 一樣:如果 globsextraglobs 有一個(gè)共同的鍵,那么 extraglobs 中的相關(guān)值會(huì )出現在合并的dict中。 默認情況下,或者為``None`` ,則不使用額外的全局變量。這是一個(gè)高級功能,允許對 doctest 進(jìn)行參數化。例如,可以為一個(gè)基類(lèi)寫(xiě)一個(gè)測試,使用該類(lèi)的通用名稱(chēng),然后通過(guò)傳遞一個(gè) extraglobs dict,將通用名稱(chēng)映射到要測試的子類(lèi),從而重復用于測試任何數量的子類(lèi)。

可選的參數 verbose 如果為真值會(huì )打印很多東西,如果為假值則只打印失敗信息;默認情況下,或者為 None,只有當 '-v'sys.argv 中時(shí)才為真值。

可選參數 report 為T(mén)rue時(shí),在結尾處打印一個(gè)總結,否則在結尾處什么都不打印。 在verbose模式下,總結是詳細的,否則總結是非常簡(jiǎn)短的(事實(shí)上,如果所有的測試都通過(guò)了,總結就是空的)。

可選參數 optionflags (默認值為0)是選項標志的 bitwise OR 。參見(jiàn)章節 選項標記 。

可選參數 raise_on_error 默認為False。 如果是True,在一個(gè)用例中第一次出現失敗或意外的異常時(shí),會(huì )觸發(fā)一個(gè)異常。這允許對失敗進(jìn)行事后調試。默認行為是繼續運行例子。

可選參數 parser 指定一個(gè) DocTestParser (或子類(lèi)),它應該被用來(lái)從文件中提取測試。它默認為一個(gè)普通的解析器(即``DocTestParser()``)。

可選參數 encoding 指定了一個(gè)編碼,應該用來(lái)將文件轉換為unicode。

doctest.testmod(m=None, name=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False)?

所有的參數都是可選的,除了 m 之外,都應該以關(guān)鍵字的形式指定。

測試從模塊 m (或模塊 __main__ ,如果 m 沒(méi)有被提供或為``None``)可達到的函數和類(lèi)的文檔串中的用例,從``m.__doc__``開(kāi)始。

也測試從dict m.__test__ 可達到的用例,如果它存在并且不是 None 。 m.__test__ 將名字(字符串)映射到函數、類(lèi)和字符串;函數和類(lèi)的文檔串被搜索到的用例;字符串被直接搜索到,就像它們是文檔串一樣。

只搜索附屬于模塊 m 中的對象的文檔串。

返回 (failure_count, test_count) 。

可選參數 name 給出了模塊的名稱(chēng);默認情況下,或者如果為 None ,則為 m.__name__ 。

可選參數 exclude_empty 默認為假值。 如果為真值,沒(méi)有找到任何 doctest 的對象將被排除在考慮范圍之外。 默認情況下是向后兼容,所以仍然使用 doctest.master.summarize()testmod() 的代碼會(huì )繼續得到?jīng)]有測試的對象的輸出。 較新的 DocTestFinder 構造器的 exclude_empty 參數默認為真值。

可選參數 extraglobs 、 verbose 、 report 、 optionflags 、 raise_on_errorglobs 與上述函數 testfile() 的參數相同,只是 globs 默認為 m.__dict__ 。

doctest.run_docstring_examples(f, globs, verbose=False, name='NoName', compileflags=None, optionflags=0)?

與對象 f 相關(guān)的測試用例;例如, f 可以是一個(gè)字符串、一個(gè)模塊、一個(gè)函數或一個(gè)類(lèi)對象。

dict 參數 globs 的淺層拷貝被用于執行環(huán)境。

可選參數 name 在失敗信息中使用,默認為 "NoName" 。

如果可選參數 verbose 為真,即使沒(méi)有失敗也會(huì )產(chǎn)生輸出。 默認情況下,只有在用例失敗的情況下才會(huì )產(chǎn)生輸出。

可選參數 compileflags 給出了Python編譯器在運行例子時(shí)應該使用的標志集。默認情況下,或者如果為 None ,標志是根據 globs 中發(fā)現的未來(lái)特征集推導出來(lái)的。

可選參數 optionflags 的作用與上述 testfile() 函數中的相同。

Unittest API?

doctest 提供了兩個(gè)函數,可以用來(lái)從模塊和包含測試的文本文件中創(chuàng )建 unittest 測試套件。 要與 unittest 測試發(fā)現集成,請在你的測試模塊中包含一個(gè) load_tests() 函數::

import unittest
import doctest
import my_module_with_doctests

def load_tests(loader, tests, ignore):
    tests.addTests(doctest.DocTestSuite(my_module_with_doctests))
    return tests

有兩個(gè)主要函數用于從文本文件和帶doctest的模塊中創(chuàng )建 unittest.TestSuite 實(shí)例。

doctest.DocFileSuite(*paths, module_relative=True, package=None, setUp=None, tearDown=None, globs=None, optionflags=0, parser=DocTestParser(), encoding=None)?

將一個(gè)或多個(gè)文本文件中的doctest測試轉換為一個(gè) unittest.TestSuite 。

返回的 unittest.TestSuite 將由 unittest 框架運行,并運行每個(gè)文件中的交互式示例。如果任何文件中的用例失敗了,那么合成的單元測試就會(huì )失敗,并觸發(fā)一個(gè) failureException 異常,顯示包含該測試的文件名和一個(gè)(有時(shí)是近似的)行號。

傳遞一個(gè)或多個(gè)要檢查的文本文件的路徑(作為字符串)。

選項可以作為關(guān)鍵字參數提供:

可選參數 module_relative 指定了 paths 中的文件名應該如何解釋。

  • 如果*module_relative*是 True (默認值),那么 paths 中的每個(gè)文件名都指定了一個(gè)獨立于操作系統的模塊相對路徑。 默認情況下,這個(gè)路徑是相對于調用模塊的目錄的;但是如果指定了 package 參數,那么它就是相對于該包的。 為了保證操作系統的獨立性,每個(gè)文件名都應該使用字符來(lái)分隔路徑段,并且不能是絕對路徑(即不能以 / 開(kāi)始)。

  • 如果 module_relativeFalse ,那么 paths 中的每個(gè)文件名都指定了一個(gè)操作系統特定的路徑。 路徑可以是絕對的,也可以是相對的;相對路徑是關(guān)于當前工作目錄的解析。

可選參數 package 是一個(gè)Python包或一個(gè)Python包的名字,其目錄應該被用作 paths 中模塊相關(guān)文件名的基本目錄。 如果沒(méi)有指定包,那么調用模塊的目錄將作為模塊相關(guān)文件名的基礎目錄。 如果 module_relativeFalse ,那么指定 package 是錯誤的。

可選的參數 setUp 為測試套件指定了一個(gè)設置函數。在運行每個(gè)文件中的測試之前,它被調用。 setUp 函數將被傳遞給一個(gè) DocTest 對象。 setUp函數可以通過(guò)測試的 globs 屬性訪(fǎng)問(wèn)測試的全局變量。

可選的參數 tearDown 指定了測試套件的卸載函數。 在運行每個(gè)文件中的測試后,它會(huì )被調用。 tearDown 函數將被傳遞一個(gè) DocTest 對象。 setUp函數可以通過(guò)測試的 globs 屬性訪(fǎng)問(wèn)測試的全局變量。

可選參數 globs 是一個(gè)包含測試的初始全局變量的字典。 這個(gè)字典的一個(gè)新副本為每個(gè)測試創(chuàng )建。 默認情況下, globs 是一個(gè)新的空字典。

可選參數 optionflags 為測試指定默認的doctest選項,通過(guò)將各個(gè)選項的標志連接在一起創(chuàng )建。 參見(jiàn)章節 選項標記 。參見(jiàn)下面的函數 set_unittest_reportflags() ,以了解設置報告選項的更好方法。

可選參數 parser 指定一個(gè) DocTestParser (或子類(lèi)),它應該被用來(lái)從文件中提取測試。它默認為一個(gè)普通的解析器(即``DocTestParser()``)。

可選參數 encoding 指定了一個(gè)編碼,應該用來(lái)將文件轉換為unicode。

該全局 __file__ 被添加到提供給用 DocFileSuite() 從文本文件加載的doctest的全局變量中。

doctest.DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, setUp=None, tearDown=None, checker=None)?

將一個(gè)模塊的doctest測試轉換為 unittest.TestSuite 。

返回的 unittest.TestSuite 將由 unittest 框架運行,并運行模塊中的每個(gè) doctest。 如果任何一個(gè)測試失敗,那么合成的單元測試就會(huì )失敗,并觸發(fā)一個(gè) failureException 異常,顯示包含該測試的文件名和一個(gè)(有時(shí)是近似的)行號。

可選參數 module 提供了要測試的模塊。 它可以是一個(gè)模塊對象或一個(gè)(可能是帶點(diǎn)的)模塊名稱(chēng)。 如果沒(méi)有指定,則使用調用此函數的模塊。

可選參數 globs 是一個(gè)包含測試的初始全局變量的字典。 這個(gè)字典的一個(gè)新副本為每個(gè)測試創(chuàng )建。 默認情況下, globs 是一個(gè)新的空字典。

可選參數 extraglobs 指定了一組額外的全局變量,這些變量被合并到 globs 中。 默認情況下,不使用額外的全局變量。

可選參數 test_finderDocTestFinder 對象(或一個(gè)可替換的對象),用于從模塊中提取測試。

可選參數 setUp 、 tearDownoptionflags 與上述函數 DocFileSuite() 相同。

這個(gè)函數使用與 testmod() 相同的搜索技術(shù)。

在 3.5 版更改: 如果 module 不包含任何文件串,則 DocTestSuite() 返回一個(gè)空的 unittest.TestSuite,而不是觸發(fā) ValueError。

DocTestSuite()doctest.DocTestCase 實(shí)例中創(chuàng )建一個(gè) unittest.TestSuite ,而 DocTestCaseunittest.TestCase 的子類(lèi)。 DocTestCase 在這里沒(méi)有記錄(這是一個(gè)內部細節),但研究其代碼可以回答關(guān)于 unittest 集成的實(shí)際細節問(wèn)題。

同樣, DocFileSuite()doctest.DocFileCase 實(shí)例中創(chuàng )建一個(gè) unittest.TestSuite ,并且 DocFileCaseDocTestCase 的一個(gè)子類(lèi)。

所以這兩種創(chuàng )建 unittest.TestSuite 的方式都是運行 DocTestCase 的實(shí)例。這一點(diǎn)很重要,因為有一個(gè)微妙的原因:當你自己運行 doctest 函數時(shí),你可以直接控制使用中的 doctest 選項,通過(guò)傳遞選項標志給 doctest 函數。 然而,如果你正在編寫(xiě)一個(gè) unittest 框架, unittest 最終會(huì )控制測試的運行時(shí)間和方式。 框架作者通常希望控制 doctest 的報告選項(也許,例如,由命令行選項指定),但沒(méi)有辦法通過(guò) unittestdoctest 測試運行者傳遞選項。

出于這個(gè)原因, doctest 也支持一個(gè)概念,即 doctest 報告特定于 unittest 支持的標志,通過(guò)這個(gè)函數:

doctest.set_unittest_reportflags(flags)?

設置要使用的 doctest 報告標志。

參數 flags 是選項標志的 bitwise OR 。 參見(jiàn)章節 選項標記 。 只有 "報告標志" 可以被使用。

這是一個(gè)模塊的全局設置,并影響所有未來(lái)由模塊 unittest 運行的測試: DocTestCaserunTest() 方法會(huì )查看 DocTestCase 實(shí)例構建時(shí)為測試用例指定的選項標志。 如果沒(méi)有指定報告標志(這是典型的和預期的情況), doctestunittest 報告標志被 bitwise ORed 到選項標志中,這樣增加的選項標志被傳遞給 DocTestRunner 實(shí)例來(lái)運行doctest。 如果在構建 DocTestCase 實(shí)例時(shí)指定了任何報告標志,那么 doctestunittest 報告標志會(huì )被忽略。

unittest 報告標志的值在調用該函數之前是有效的,由該函數返回。

高級 API?

基本 API 是一個(gè)簡(jiǎn)單的封裝,旨在使 doctest 易于使用。它相當靈活,應該能滿(mǎn)足大多數用戶(hù)的需求;但是,如果你需要對測試進(jìn)行更精細的控制,或者希望擴展 doctest 的功能,那么你應該使用高級 API 。

高級API圍繞著(zhù)兩個(gè)容器類(lèi),用于存儲從 doctest 案例中提取的交互式用例:

  • Example: 一個(gè)單一的 Python statement ,與它的預期輸出配對。

  • DocTest: 一組 Examples 的集合,通常從一個(gè)文檔字符串或文本文件中提取。

定義了額外的處理類(lèi)來(lái)尋找、解析和運行,并檢查 doctest 的用例。

  • DocTestFinder : 查找給定模塊中的所有文檔串,并使用 DocTestParser 從每個(gè)包含交互式用例的文檔串中創(chuàng )建一個(gè) DocTest 。

  • DocTestParser : 從一個(gè)字符串(如一個(gè)對象的文檔串)創(chuàng )建一個(gè) DocTest 對象。

  • DocTestRunner : 執行 DocTest 中的用例,并使用 OutputChecker 來(lái)驗證其輸出。

  • OutputChecker : 將一個(gè)測試用例的實(shí)際輸出與預期輸出進(jìn)行比較,并決定它們是否匹配。

這些處理類(lèi)之間的關(guān)系總結在下圖中:

                            list of:
+------+                   +---------+
|module| --DocTestFinder-> | DocTest | --DocTestRunner-> results
+------+    |        ^     +---------+     |       ^    (printed)
            |        |     | Example |     |       |
            v        |     |   ...   |     v       |
           DocTestParser   | Example |   OutputChecker
                           +---------+

DocTest 對象?

class doctest.DocTest(examples, globs, name, filename, lineno, docstring)?

應該在單一命名空間中運行的doctest用例的集合。構造函數參數被用來(lái)初始化相同名稱(chēng)的屬性。

DocTest 定義了以下屬性。 它們由構造函數初始化,不應該被直接修改。

examples?

一個(gè) Example 對象的列表,它編碼了應該由該測試運行的單個(gè)交互式 Python 用例。

globs?

例子應該運行的命名空間(又稱(chēng) globals )。這是一個(gè)將名字映射到數值的字典。例子對名字空間的任何改變(比如綁定新的變量)將在測試運行后反映在 globs 中。

name?

識別 DocTest 的字符串名稱(chēng)。 通常情況下,這是從測試中提取的對象或文件的名稱(chēng)。

filename?

這個(gè) DocTest 被提取的文件名;或者為``None``,如果文件名未知,或者 DocTest 沒(méi)有從文件中提取。

lineno?

filename 中的行號,這個(gè) DocTest 開(kāi)始的地方,或者行號不可用時(shí)為``None``。 這個(gè)行號相對于文件的開(kāi)頭來(lái)說(shuō)是零的。

docstring?

從測試中提取的字符串,或者如果字符串不可用,或者為 None ,如果測試沒(méi)有從字符串中提取。

Example 對象?

class doctest.Example(source, want, exc_msg=None, lineno=0, indent=0, options=None)?

單個(gè)交互式用例,由一個(gè) Python 語(yǔ)句及其預期輸出組成。 構造函數參數被用來(lái)初始化相同名稱(chēng)的屬性。

Example 定義了以下屬性。 它們由構造函數初始化,不應該被直接修改。

source?

一個(gè)包含該用例源碼的字符串。 源碼由一個(gè) Python 語(yǔ)句組成,并且總是以換行結束;構造函數在必要時(shí)添加一個(gè)換行。

want?

運行這個(gè)用例的源碼的預期輸出(可以是 stdout ,也可以是異常情況下的回溯)。 want 以一個(gè)換行符結束,除非沒(méi)有預期的輸出,在這種情況下它是一個(gè)空字符串。 構造函數在必要時(shí)添加一個(gè)換行。

exc_msg?

用例產(chǎn)生的異常信息,如果這個(gè)例子被期望產(chǎn)生一個(gè)異常;或者為 None ,如果它不被期望產(chǎn)生一個(gè)異常。 這個(gè)異常信息與 traceback.format_exception_only() 的返回值進(jìn)行比較。 exc_msg 以換行結束,除非是 None 。

lineno?

包含本例的字符串中的行號,即本例的開(kāi)始。 這個(gè)行號相對于包含字符串的開(kāi)頭來(lái)說(shuō)是以零開(kāi)始的。

indent?

用例在包含字符串中的縮進(jìn),即在用例的第一個(gè)提示前有多少個(gè)空格字符。

options?

一個(gè)從選項標志到 TrueFalse 的字典映射,用于覆蓋這個(gè)例子的默認選項。 任何不包含在這個(gè)字典中的選項標志都被保留為默認值(由 DocTestRunneroptionflags 指定)。默認情況下,沒(méi)有選項被設置。

DocTestFinder 對象?

class doctest.DocTestFinder(verbose=False, parser=DocTestParser(), recurse=True, exclude_empty=True)?

一個(gè)處理類(lèi),用于從一個(gè)給定的對象的 docstring 和其包含的對象的 docstring 中提取與之相關(guān)的 DocTest 。 DocTest 可以從模塊、類(lèi)、函數、方法、靜態(tài)方法、類(lèi)方法和屬性中提取。

可選的參數 verbose 可以用來(lái)顯示查找器搜索到的對象。 它的默認值是 False (無(wú)輸出)。

可選的參數 parser 指定了 DocTestParser 對象(或一個(gè)可替換的對象),用于從文檔串中提取 doctest 。

如果可選的參數 recurse 是 False ,那么 DocTestFinder.find() 將只檢查給定的對象,而不是任何包含的對象。

如果可選參數 exclude_empty 為 False ,那么 DocTestFinder.find() 將包括對文檔字符串為空的對象的測試。

DocTestFinder 定義了以下方法:

find(obj[, name][, module][, globs][, extraglobs])?

返回 DocTest 的列表,該列表由 obj 的文檔串或其包含的任何對象的文檔串定義。

可選參數 name 指定了對象的名稱(chēng);這個(gè)名稱(chēng)將被用來(lái)為返回的 DocTest 構建名稱(chēng)。 如果沒(méi)有指定*name*,則使用 obj.__name__ 。

可選參數 module 是包含給定對象的模塊。如果沒(méi)有指定模塊或者是 None ,那么測試查找器將試圖自動(dòng)確定正確的模塊。 該對象被使用的模塊:

  • 作為一個(gè)默認的命名空間,如果沒(méi)有指定 globs 。

  • 為了防止 DocTestFinder 從其他模塊導入的對象中提取 DocTest 。 (包含有除 module 以外的模塊的對象會(huì )被忽略)。

  • 找到包含該對象的文件名。

  • 找到該對象在其文件中的行號。

如果 moduleFalse ,將不會(huì )試圖找到這個(gè)模塊。 這是不明確的,主要用于測試 doctest 本身:如果 moduleFalse ,或者是 None 但不能自動(dòng)找到,那么所有對象都被認為屬于(不存在的)模塊,所以所有包含的對象將(遞歸地)被搜索到 doctest 。

每個(gè) DocTest 的 globals 是由 globsextraglobs 組合而成的( extraglobs 的綁定覆蓋 globs 的綁定)。 為每個(gè) DocTest 創(chuàng )建一個(gè)新的 globals 字典的淺層拷貝。如果沒(méi)有指定 globs ,那么它默認為模塊的 __dict__ ,如果指定了或者為 {} ,則除外。 如果沒(méi)有指定 extraglobs ,那么它默認為 {} 。

DocTestParser 對象?

class doctest.DocTestParser?

一個(gè)處理類(lèi),用于從一個(gè)字符串中提取交互式的用例,并使用它們來(lái)創(chuàng )建一個(gè) DocTest 對象。

DocTestParser 定義了以下方法:

get_doctest(string, globs, name, filename, lineno)?

從給定的字符串中提取所有的測試用例,并將它們收集到一個(gè) DocTest 對象中。

globs 、 name 、 filenamelineno 是新的 DocTest 對象的屬性。 更多信息請參見(jiàn) DocTest 的文檔。

get_examples(string, name='<string>')?

從給定的字符串中提取所有的測試用例,并以 Example 對象列表的形式返回。 行數以 0 為基數。 可選參數 name 用于識別這個(gè)字符串的名稱(chēng),只用于錯誤信息。

parse(string, name='<string>')?

將給定的字符串分成用例和中間的文本,并以 Example 和字符串交替的列表形式返回。 Example 的行號以 0 為基數。 可選參數 name 用于識別這個(gè)字符串的名稱(chēng),只用于錯誤信息。

DocTestRunner 對象?

class doctest.DocTestRunner(checker=None, verbose=None, optionflags=0)?

一個(gè)處理類(lèi),用于執行和驗證 DocTest 中的交互式用例。

預期輸出和實(shí)際輸出之間的比較是由一個(gè) OutputChecker 完成的。 這種比較可以用一些選項標志來(lái)定制;更多信息請看 選項標記 部分。 如果選項標志不夠,那么也可以通過(guò)向構造函數傳遞 OutputChecker 的子類(lèi)來(lái)定制比較。

測試運行器的顯示輸出可以通過(guò)兩種方式控制。首先,一個(gè)輸出函數可以被傳遞給 TestRunner.run() ;這個(gè)函數將被調用,并顯示出應該顯示的字符串。 它的默認值是 sys.stdout.write 。 如果捕獲輸出是不夠的,那么也可以通過(guò)子類(lèi)化 DocTestRunner 來(lái)定制顯示輸出,并重寫(xiě) report_start() , report_success() , report_unexpected_exception()report_failure() 方法。

可選的關(guān)鍵字參數 checker 指定了 OutputChecker 對象(或其相似替換),它應該被用來(lái)比較預期輸出和 doctest 用例的實(shí)際輸出。

可選的關(guān)鍵字參數 verbose 控制 DocTestRunner 的詳細程度。 如果 verboseTrue ,那么每個(gè)用例的信息都會(huì )被打印出來(lái),當它正在運行時(shí)。 如果 verboseFalse ,則只打印失敗的信息。 當 verbose 沒(méi)有指定,或者為 None ,如果使用了命令行開(kāi)關(guān) -v ,則使用verbose輸出。

可選的關(guān)鍵字參數 optionflags 可以用來(lái)控制測試運行器如何比較預期輸出和實(shí)際輸出,以及如何顯示失敗。更多信息,請參見(jiàn)章節 選項標記 。

DocTestParser 定義了以下方法:

report_start(out, test, example)?

報告測試運行器即將處理給定的用例。提供這個(gè)方法是為了讓 DocTestRunner 的子類(lèi)能夠定制他們的輸出;它不應該被直接調用。

example 是即將被處理的用。 test包含用例 的測試。 out 是傳遞給 DocTestRunner.run() 的輸出函數。

report_success(out, test, example, got)?

報告給定的用例運行成功。 提供這個(gè)方法是為了讓 DocTestRunner 的子類(lèi)能夠定制他們的輸出;它不應該被直接調用。

example 是即將被處理的用例。 got 是這個(gè)例子的實(shí)際輸出。 test 是包含 example 的測試。 out 是傳遞給 DocTestRunner.run() 的輸出函數。

report_failure(out, test, example, got)?

報告給定的用例運行失敗。 提供這個(gè)方法是為了讓 DocTestRunner 的子類(lèi)能夠定制他們的輸出;它不應該被直接調用。

example 是即將被處理的用例。 got 是這個(gè)例子的實(shí)際輸出。 test 是包含 example 的測試。 out 是傳遞給 DocTestRunner.run() 的輸出函數。

report_unexpected_exception(out, test, example, exc_info)?

報告給定的用例觸發(fā)了一個(gè)異常。 提供這個(gè)方法是為了讓 DocTestRunner 的子類(lèi)能夠定制他們的輸出;它不應該被直接調用。

example 是將要被處理的用例。 exc_info 是一個(gè)元組,包含關(guān)于異常的信息(如由 sys.exc_info() 返回)。 test 是包含 example 的測試。 out 是傳遞給 DocTestRunner.run() 的輸出函數。

run(test, compileflags=None, out=None, clear_globs=True)?

test (一個(gè) DocTest 對象)中運行這些用例,并使用寫(xiě)入函數 out 顯示結果。

這些用例都是在命名空間 test.globs 中運行的。 如果 clear_globs 為 True (默認),那么這個(gè)命名空間將在測試運行后被清除,以幫助進(jìn)行垃圾回收。如果你想在測試完成后檢查命名空間,那么使用 clear_globs=False 。

compileflags 給出了 Python 編譯器在運行例子時(shí)應該使用的標志集。如果沒(méi)有指定,那么它將默認為適用于 globs 的 future-import 標志集。

每個(gè)用例的輸出都使用 DocTestRunner 的輸出檢查器進(jìn)行檢查,結果由 DocTestRunner.report_*() 方法進(jìn)行格式化。

summarize(verbose=None)?

打印這個(gè) DocTestRunner 運行過(guò)的所有測試用例的摘要,并返回一個(gè) named tuple TestResults(failed, attempted) 。

可選的 verbose 參數控制摘要的詳細程度。 如果沒(méi)有指定 verbose ,那么將使用 DocTestRunner 的 verbose 。

OutputChecker 對象?

class doctest.OutputChecker?

一個(gè)用于檢查測試用例的實(shí)際輸出是否與預期輸出相匹配的類(lèi)。 OutputChecker 定義了兩個(gè)方法: check_output() ,比較給定的一對輸出,如果它們匹配則返回 True ; output_difference() ,返回一個(gè)描述兩個(gè)輸出之間差異的字符串。

OutputChecker 定義了以下方法:

check_output(want, got, optionflags)?

如果一個(gè)用例的實(shí)際輸出( got )與預期輸出( want )匹配,則返回 True 。 如果這些字符串是相同的,總是被認為是匹配的;但是根據測試運行器使用的選項標志,也可能有幾種非精確的匹配類(lèi)型。 參見(jiàn)章節 選項標記 了解更多關(guān)于選項標志的信息。

output_difference(example, got, optionflags)?

返回一個(gè)字符串,描述給定用例( example )的預期輸出和實(shí)際輸出( got )之間的差異。 optionflags 是用于比較 wantgot 的選項標志集。

調試?

Doctest 提供了幾種調試 doctest 用例的機制:

  • 有幾個(gè)函數將測試轉換為可執行的 Python 程序,這些程序可以在 Python 調試器, pdb 下運行。

  • DebugRunner 類(lèi)是 DocTestRunner 的一個(gè)子類(lèi),它為第一個(gè)失敗的用例觸發(fā)一個(gè)異常,包含關(guān)于這個(gè)用例的信息。這些信息可以用來(lái)對這個(gè)用例進(jìn)行事后調試。

  • DocTestSuite() 生成的 unittest 用例支持由 unittest.TestCase 定義的 debug() 方法,。

  • 你可以在 doctest 的用例中加入對 pdb.set_trace() 的調用,當這一行被執行時(shí),你會(huì )進(jìn)入 Python 調試器。 然后你可以檢查變量的當前值,等等。 例如,假設 a.py 只包含這個(gè)模塊 docstring

    """
    >>> def f(x):
    ...     g(x*2)
    >>> def g(x):
    ...     print(x+3)
    ...     import pdb; pdb.set_trace()
    >>> f(3)
    9
    """
    

    那么一個(gè)交互式Python會(huì )話(huà)可能是這樣的:

    >>>
    >>> import a, doctest
    >>> doctest.testmod(a)
    --Return--
    > <doctest a[1]>(3)g()->None
    -> import pdb; pdb.set_trace()
    (Pdb) list
      1     def g(x):
      2         print(x+3)
      3  ->     import pdb; pdb.set_trace()
    [EOF]
    (Pdb) p x
    6
    (Pdb) step
    --Return--
    > <doctest a[0]>(2)f()->None
    -> g(x*2)
    (Pdb) list
      1     def f(x):
      2  ->     g(x*2)
    [EOF]
    (Pdb) p x
    3
    (Pdb) step
    --Return--
    > <doctest a[2]>(1)?()->None
    -> f(3)
    (Pdb) cont
    (0, 3)
    >>>
    

將測試轉換為 Python 代碼的函數,并可能在調試器下運行合成的代碼:

doctest.script_from_examples(s)?

將帶有用例的文本轉換為腳本。

參數 s 是一個(gè)包含測試用例的字符串。 該字符串被轉換為 Python 腳本,其中 s 中的 doctest 用例被轉換為常規代碼,其他的都被轉換為 Python 注釋。 生成的腳本將以字符串的形式返回。例如,

import doctest
print(doctest.script_from_examples(r"""
    Set x and y to 1 and 2.
    >>> x, y = 1, 2

    Print their sum:
    >>> print(x+y)
    3
"""))

顯示:

# Set x and y to 1 and 2.
x, y = 1, 2
#
# Print their sum:
print(x+y)
# Expected:
## 3

這個(gè)函數在內部被其他函數使用(見(jiàn)下文),但當你想把一個(gè)交互式 Python 會(huì )話(huà)轉化為 Python 腳本時(shí),也會(huì )很有用。

doctest.testsource(module, name)?

將一個(gè)對象的 doctest 轉換為一個(gè)腳本。

參數 module 是一個(gè)模塊對象,或者一個(gè)模塊帶點(diǎn)的名稱(chēng),包含對其 doctest 感興趣的對象。 參數 name 是具有感興趣的測試的對象的名稱(chēng)(在模塊中)。 結果是一個(gè)字符串,包含該對象的文本串轉換為 Python 腳本,如上面 script_from_examples() 所述。 例如,如果模塊 a.py 包含一個(gè)頂級函數 f() ,那么

import a, doctest
print(doctest.testsource(a, "a.f"))

打印函數 f() 的文檔串的腳本版本,將測試轉換為代碼,其余部分放在注釋中。

doctest.debug(module, name, pm=False)?

對一個(gè)對象的 doctest 進(jìn)行調試。

modulename 參數與上面函數 testsource() 的參數相同。 被命名對象的文本串的合成 Python 腳本被寫(xiě)入一個(gè)臨時(shí)文件,然后該文件在 Python 調試器 pdb 的控制下運行。

module.__dict__ 的一個(gè)淺層拷貝被用于本地和全局的執行環(huán)境。

可選參數 pm 控制是否使用事后調試。 如果 pm 為 True ,則直接運行腳本文件,只有當腳本通過(guò)引發(fā)一個(gè)未處理的異常而終止時(shí),調試器才會(huì )介入。如果是這樣,就會(huì )通過(guò) pdb.post_mortem() 調用事后調試,并傳遞未處理異常的跟蹤對象。如果沒(méi)有指定 pm ,或者是 False ,腳本將從一開(kāi)始就在調試器下運行,通過(guò)傳遞一個(gè)適當的 exec() 調用給 pdb.run() 。

doctest.debug_src(src, pm=False, globs=None)?

在一個(gè)字符串中調試 doctest 。

這就像上面的函數 debug() ,只是通過(guò) src 參數,直接指定一個(gè)包含測試用例的字符串。

可選參數 pm 的含義與上述函數 debug() 的含義相同。

可選的參數 globs 給出了一個(gè)字典,作為本地和全局的執行環(huán)境。 如果沒(méi)有指定,或者為 None ,則使用一個(gè)空的字典。如果指定,則使用字典的淺層拷貝。

DebugRunner 類(lèi),以及它可能觸發(fā)的特殊異常,是測試框架作者最感興趣的,在此僅作簡(jiǎn)要介紹。請看源代碼,特別是 DebugRunner 的文檔串(這是一個(gè)測試?。┮粤私飧嗉毠?。

class doctest.DebugRunner(checker=None, verbose=None, optionflags=0)?

DocTestRunner 的一個(gè)子類(lèi),一旦遇到失敗,就會(huì )觸發(fā)一個(gè)異常。 如果一個(gè)意外的異常發(fā)生,就會(huì )引發(fā)一個(gè) UnexpectedException 異常,包含測試、用例和原始異常。 如果輸出不匹配,那么就會(huì )引發(fā)一個(gè) DocTestFailure 異常,包含測試、用例和實(shí)際輸出。

關(guān)于構造函數參數和方法的信息,請參見(jiàn) DocTestRunner 部分的文檔 高級 API 。

DebugRunner 實(shí)例可能會(huì )觸發(fā)兩種異常。

exception doctest.DocTestFailure(test, example, got)?

DocTestRunner 觸發(fā)的異常,表示一個(gè) doctest 用例的實(shí)際輸出與預期輸出不一致。構造函數參數被用來(lái)初始化相同名稱(chēng)的屬性。

DocTestFailure 定義了以下屬性:

DocTestFailure.test?

當該用例失敗時(shí)正在運行的 DocTest 對象。

DocTestFailure.example?

失敗的 Example 。

DocTestFailure.got?

用例的實(shí)際輸出。

exception doctest.UnexpectedException(test, example, exc_info)?

一個(gè)由 DocTestRunner 觸發(fā)的異常,以提示一個(gè) doctest 用例引發(fā)了一個(gè)意外的異常。 構造函數參數被用來(lái)初始化相同名稱(chēng)的屬性。

UnexpectedException 定義了以下屬性:

UnexpectedException.test?

當該用例失敗時(shí)正在運行的 DocTest 對象。

UnexpectedException.example?

失敗的 Example 。

UnexpectedException.exc_info?

一個(gè)包含意外異常信息的元組,由 sys.ex_info() 返回。

肥皂盒?

正如介紹中提到的, doctest 已經(jīng)發(fā)展到有三個(gè)主要用途:

  1. 檢查 docstring 中的用例。

  2. 回歸測試。

  3. 可執行的文檔/文字測試。

這些用途有不同的要求,區分它們是很重要的。特別是,用晦澀難懂的測試用例來(lái)填充你的文檔字符串會(huì )使文檔變得很糟糕。

在編寫(xiě)文檔串時(shí),要謹慎地選擇文檔串的用例。這是一門(mén)需要學(xué)習的藝術(shù)——一開(kāi)始可能并不自然。用例應該為文檔增加真正的價(jià)值。 一個(gè)好的用例往往可以抵得上許多文字。如果用心去做,這些用例對你的用戶(hù)來(lái)說(shuō)是非常有價(jià)值的,而且隨著(zhù)時(shí)間的推移和事情的變化,收集這些用例所花費的時(shí)間也會(huì )得到很多倍的回報。 我仍然驚訝于我的 doctest 用例在一個(gè)“無(wú)害”的變化后停止工作。

Doctest 也是回歸測試的一個(gè)很好的工具,特別是如果你不吝嗇解釋的文字。 通過(guò)交錯的文本和用例,可以更容易地跟蹤真正的測試,以及為什么。當測試失敗時(shí),好的文本可以使你更容易弄清問(wèn)題所在,以及如何解決。 的確,你可以在基于代碼的測試中寫(xiě)大量的注釋?zhuān)苌儆谐绦騿T這樣做。許多人發(fā)現,使用 doctest 方法反而能使測試更加清晰。 也許這只是因為 doctest 使寫(xiě)散文比寫(xiě)代碼容易一些,而在代碼中寫(xiě)注釋則有點(diǎn)困難。 我認為它比這更深入:當寫(xiě)一個(gè)基于 doctest 的測試時(shí),自然的態(tài)度是你想解釋你的軟件的細微之處,并用例子來(lái)說(shuō)明它們。這反過(guò)來(lái)又自然地導致了測試文件從最簡(jiǎn)單的功能開(kāi)始,然后邏輯地發(fā)展到復雜和邊緣案例。 一個(gè)連貫的敘述是結果,而不是一個(gè)孤立的函數集合,似乎是隨機的測試孤立的功能位。 這是一種不同的態(tài)度,產(chǎn)生不同的結果,模糊了測試和解釋之間的區別。

回歸測試最好限制在專(zhuān)用對象或文件中。 有幾種組織測試的選擇:

  • 編寫(xiě)包含測試案例的文本文件作為交互式例子,并使用 testfile()DocFileSuite() 來(lái)測試這些文件。 建議這樣做,盡管對于新的項目來(lái)說(shuō)是最容易做到的,從一開(kāi)始就設計成使用 doctest 。

  • 定義命名為 _regrtest_topic 的函數,由單個(gè)文檔串組成,包含命名主題的測試用例。 這些函數可以包含在與模塊相同的文件中,或分離出來(lái)成為一個(gè)單獨的測試文件。

  • 定義一個(gè)從回歸測試主題到包含測試用例的文檔串的 __test__ 字典映射。

當你把你的測試放在一個(gè)模塊中時(shí),這個(gè)模塊本身就可以成為測試運行器。 當一個(gè)測試失敗時(shí),你可以安排你的測試運行器只重新運行失敗的測試,同時(shí)調試問(wèn)題。 下面是這樣一個(gè)測試運行器的最小例子:

if __name__ == '__main__':
    import doctest
    flags = doctest.REPORT_NDIFF|doctest.FAIL_FAST
    if len(sys.argv) > 1:
        name = sys.argv[1]
        if name in globals():
            obj = globals()[name]
        else:
            obj = __test__[name]
        doctest.run_docstring_examples(obj, globals(), name=name,
                                       optionflags=flags)
    else:
        fail, total = doctest.testmod(optionflags=flags)
        print("{} failures out of {} tests".format(fail, total))

備注

1

不支持同時(shí)包含預期輸出和異常的用例。試圖猜測一個(gè)在哪里結束,另一個(gè)在哪里開(kāi)始,太容易出錯了,而且這也會(huì )使測試變得混亂。