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

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


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

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

  • 通過驗證來自一個測試文件或一個測試對象的交互式示例按預(yù)期工作,來進行回歸測試。

  • 為一個包寫指導(dǎo)性的文檔,用輸入輸出的例子來說明。 取決于是強調(diào)例子還是說明性的文字,這有一種 "文本測試 "或 "可執(zhí)行文檔 "的風(fēng)格。

下面是一個小卻完整的示例模塊:

"""
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
$

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

$ 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

以此類推,最終以:

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 你所需要知道的一切!開始上手吧。 下面的章節(jié)提供了完整的細節(jié)。 請注意,在標準的 Python 測試套件和庫中有許多 doctest 的例子。特別有用的例子可以在標準測試文件 Lib/test/test_doctest.py 中找到。

簡單用法:檢查Docstrings中的示例?

開始使用doctest的最簡單方法(但不一定是你將繼續(xù)這樣做的方式)是結(jié)束每個模塊 M 使用:

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

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

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

python M.py

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

用``-v``來運行它來切換,而不是:

python M.py -v

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

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

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

python -m doctest -v example.py

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

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

簡單的用法:檢查文本文件中的例子?

doctest 的另一個簡單應(yīng)用是測試文本文件中的交互式例子。 這可以用 testfile() 函數(shù)來完成;:

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

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

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ā)現(xiàn)這個文檔中的錯誤:

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

testmod() 一樣, testfile() 不會顯示任何東西,除非一個例子失敗。 如果一個例子失敗了,那么失敗的例子和失敗的原因?qū)⒈淮蛴〉絪tdout,使用的格式與 testmod() 相同。

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

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

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

python -m doctest -v example.txt

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

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

它是如何工作的?

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

哪些文件串被檢查了??

模塊的文檔串以及所有函數(shù)、類和方法的文檔串都將被搜索。 導(dǎo)入模塊的對象不被搜索。

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

<name of M>.__test__.K

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

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

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

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

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

fine輸出:

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

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

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

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

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

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

    >>>
    >>> 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
    

    并從預(yù)期的輸出中剝離出與開始該例子的初始 '>>> ' 行中出現(xiàn)的同樣多的前導(dǎo)空白字符。

什么是執(zhí)行上下文??

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

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

異常如何處理??

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

簡單實例:

>>>
>>> [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ā),該測試就會成功,list.remove(x): x not in list 的細節(jié)如圖所示。

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

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

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

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

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

最后三行(以 ValueError 開頭)將與異常的類型和細節(jié)進行比較,其余的被忽略。

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

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

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

有些細節(jié)你應(yīng)該讀一遍,但不需要記住:

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

  • 回溯堆棧的每一行(如果有的話)必須比例子的第一行縮進, 或者 以一個非字母數(shù)字的字符開始?;厮蓊^之后的第一行縮進程度相同,并且以字母數(shù)字開始,被認為是異常細節(jié)的開始。當然,這對真正的回溯來說是正確的事情。

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

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

  • 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'
    

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

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

選項標記?

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

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

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

doctest.DONT_ACCEPT_TRUE_FOR_1?

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

doctest.DONT_ACCEPT_BLANKLINE?

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

doctest.NORMALIZE_WHITESPACE?

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

doctest.ELLIPSIS?

當指定時,預(yù)期輸出中的省略號(...)可以匹配實際輸出中的任何子串。這包括跨行的子串和空子串,所以最好保持簡單的用法。復(fù)雜的用法會導(dǎo)致與``.*``在正則表達式中容易出現(xiàn)的 "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 現(xiàn)在也忽略了與包含被測異常的模塊有關(guān)的任何信息。

doctest.SKIP?

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

SKIP標志也可用于臨時 "注釋" 用例。

doctest.COMPARISON_FLAGS?

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

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

doctest.REPORT_UDIFF?

當指定時,涉及多行預(yù)期和實際輸出的故障將使用統(tǒng)一的差異來顯示。

doctest.REPORT_CDIFF?

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

doctest.REPORT_NDIFF?

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

doctest.REPORT_ONLY_FIRST_FAILURE?

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

doctest.FAIL_FAST?

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

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

3.4 新版功能.

doctest.REPORTING_FLAGS?

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

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

doctest.register_optionflag(name)?

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

MY_FLAG = register_optionflag('MY_FLAG')

指令?

Doctest指令可以用來修改單個例子的 option flags 。 Doctest指令是在一個用例的源代碼后面的特殊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" \| ...

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

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

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]

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

警告?

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

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

是不可靠的!一個變通方法是用:

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

來取代。另一個是使用

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

還有其他的問題,但你會明白的。

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...>

浮點數(shù)在不同的平臺上也會有小的輸出變化,因為Python在浮點數(shù)的格式化上依賴于平臺的C庫,而C庫在這個問題上的質(zhì)量差異很大。:

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

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

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

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

基本API?

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

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*,所有的參數(shù)都是可選的,而且應(yīng)該以關(guān)鍵字的形式指定。

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

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

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

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

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

可選參數(shù) package 是一個 Python 包或一個 Python 包的名字,其目錄應(yīng)被用作模塊相關(guān)文件名的基礎(chǔ)目錄。 如果沒有指定包,那么調(diào)用模塊的目錄將作為模塊相關(guān)文件名的基礎(chǔ)目錄。如果 module_relative 是``False``,那么指定 package 是錯誤的。

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

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

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

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

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

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

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

可選參數(shù) encoding 指定了一個編碼,應(yīng)該用來將文件轉(zhuǎn)換為unicode。

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

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

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

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

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

返回 (failure_count, test_count)

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

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

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

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

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

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

可選參數(shù) name 在失敗信息中使用,默認為 "NoName"

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

可選參數(shù) compileflags 給出了Python編譯器在運行例子時應(yīng)該使用的標志集。默認情況下,或者如果為 None ,標志是根據(jù) globs 中發(fā)現(xiàn)的未來特征集推導(dǎo)出來的。

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

Unittest API?

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

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

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

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

將一個或多個文本文件中的doctest測試轉(zhuǎn)換為一個 unittest.TestSuite

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

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

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

可選參數(shù) module_relative 指定了 paths 中的文件名應(yīng)該如何解釋。

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

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

可選參數(shù) package 是一個Python包或一個Python包的名字,其目錄應(yīng)該被用作 paths 中模塊相關(guān)文件名的基本目錄。 如果沒有指定包,那么調(diào)用模塊的目錄將作為模塊相關(guān)文件名的基礎(chǔ)目錄。 如果 module_relativeFalse ,那么指定 package 是錯誤的。

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

可選的參數(shù) tearDown 指定了測試套件的卸載函數(shù)。 在運行每個文件中的測試后,它會被調(diào)用。 tearDown 函數(shù)將被傳遞一個 DocTest 對象。 setUp函數(shù)可以通過測試的 globs 屬性訪問測試的全局變量。

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

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

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

可選參數(shù) encoding 指定了一個編碼,應(yīng)該用來將文件轉(zhuǎn)換為unicode。

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

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

將一個模塊的doctest測試轉(zhuǎn)換為 unittest.TestSuite 。

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

可選參數(shù) module 提供了要測試的模塊。 它可以是一個模塊對象或一個(可能是帶點的)模塊名稱。 如果沒有指定,則使用調(diào)用此函數(shù)的模塊。

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

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

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

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

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

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

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

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

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

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

doctest.set_unittest_reportflags(flags)?

設(shè)置要使用的 doctest 報告標志。

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

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

unittest 報告標志的值在調(diào)用該函數(shù)之前是有效的,由該函數(shù)返回。

高級 API?

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

高級API圍繞著兩個容器類,用于存儲從 doctest 案例中提取的交互式用例:

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

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

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

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

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

DocTest 對象?

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

應(yīng)該在單一命名空間中運行的doctest用例的集合。構(gòu)造函數(shù)參數(shù)被用來初始化相同名稱的屬性。

DocTest 定義了以下屬性。 它們由構(gòu)造函數(shù)初始化,不應(yīng)該被直接修改。

examples?

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

globs?

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

name?

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

filename?

這個 DocTest 被提取的文件名;或者為``None``,如果文件名未知,或者 DocTest 沒有從文件中提取。

lineno?

filename 中的行號,這個 DocTest 開始的地方,或者行號不可用時為``None``。 這個行號相對于文件的開頭來說是零的。

docstring?

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

Example 對象?

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

單個交互式用例,由一個 Python 語句及其預(yù)期輸出組成。 構(gòu)造函數(shù)參數(shù)被用來初始化相同名稱的屬性。

Example 定義了以下屬性。 它們由構(gòu)造函數(shù)初始化,不應(yīng)該被直接修改。

source?

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

want?

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

exc_msg?

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

lineno?

包含本例的字符串中的行號,即本例的開始。 這個行號相對于包含字符串的開頭來說是以零開始的。

indent?

用例在包含字符串中的縮進,即在用例的第一個提示前有多少個空格字符。

options?

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

DocTestFinder 對象?

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

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

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

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

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

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

DocTestFinder 定義了以下方法:

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

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

可選參數(shù) name 指定了對象的名稱;這個名稱將被用來為返回的 DocTest 構(gòu)建名稱。 如果沒有指定*name*,則使用 obj.__name__ 。

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

  • 作為一個默認的命名空間,如果沒有指定 globs 。

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

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

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

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

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

DocTestParser 對象?

class doctest.DocTestParser?

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

DocTestParser 定義了以下方法:

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

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

globs 、 namefilenamelineno 是新的 DocTest 對象的屬性。 更多信息請參見 DocTest 的文檔。

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

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

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

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

DocTestRunner 對象?

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

一個處理類,用于執(zhí)行和驗證 DocTest 中的交互式用例。

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

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

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

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

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

DocTestParser 定義了以下方法:

report_start(out, test, example)?

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

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

report_success(out, test, example, got)?

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

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

report_failure(out, test, example, got)?

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

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

report_unexpected_exception(out, test, example, exc_info)?

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

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

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

test (一個 DocTest 對象)中運行這些用例,并使用寫入函數(shù) out 顯示結(jié)果。

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

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

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

summarize(verbose=None)?

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

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

OutputChecker 對象?

class doctest.OutputChecker?

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

OutputChecker 定義了以下方法:

check_output(want, got, optionflags)?

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

output_difference(example, got, optionflags)?

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

調(diào)試?

Doctest 提供了幾種調(diào)試 doctest 用例的機制:

  • 有幾個函數(shù)將測試轉(zhuǎn)換為可執(zhí)行的 Python 程序,這些程序可以在 Python 調(diào)試器, pdb 下運行。

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

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

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

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

    那么一個交互式Python會話可能是這樣的:

    >>>
    >>> 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)
    >>>
    

將測試轉(zhuǎn)換為 Python 代碼的函數(shù),并可能在調(diào)試器下運行合成的代碼:

doctest.script_from_examples(s)?

將帶有用例的文本轉(zhuǎn)換為腳本。

參數(shù) s 是一個包含測試用例的字符串。 該字符串被轉(zhuǎn)換為 Python 腳本,其中 s 中的 doctest 用例被轉(zhuǎn)換為常規(guī)代碼,其他的都被轉(zhuǎn)換為 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

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

doctest.testsource(module, name)?

將一個對象的 doctest 轉(zhuǎn)換為一個腳本。

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

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

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

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

對一個對象的 doctest 進行調(diào)試。

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

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

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

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

在一個字符串中調(diào)試 doctest 。

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

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

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

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

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

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

關(guān)于構(gòu)造函數(shù)參數(shù)和方法的信息,請參見 DocTestRunner 部分的文檔 高級 API

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

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

DocTestRunner 觸發(fā)的異常,表示一個 doctest 用例的實際輸出與預(yù)期輸出不一致。構(gòu)造函數(shù)參數(shù)被用來初始化相同名稱的屬性。

DocTestFailure 定義了以下屬性:

DocTestFailure.test?

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

DocTestFailure.example?

失敗的 Example 。

DocTestFailure.got?

用例的實際輸出。

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

一個由 DocTestRunner 觸發(fā)的異常,以提示一個 doctest 用例引發(fā)了一個意外的異常。 構(gòu)造函數(shù)參數(shù)被用來初始化相同名稱的屬性。

UnexpectedException 定義了以下屬性:

UnexpectedException.test?

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

UnexpectedException.example?

失敗的 Example 。

UnexpectedException.exc_info?

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

肥皂盒?

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

  1. 檢查 docstring 中的用例。

  2. 回歸測試。

  3. 可執(zhí)行的文檔/文字測試。

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

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

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

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

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

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

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

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

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

不支持同時包含預(yù)期輸出和異常的用例。試圖猜測一個在哪里結(jié)束,另一個在哪里開始,太容易出錯了,而且這也會使測試變得混亂。