本節(jié)將詳細(xì)介紹doctest如何工作:查看它的文檔字符串,它如何查找交互式示例,它使用的執(zhí)行上下文,它如何處理異常以及如何使用選項(xiàng)標(biāo)志來控制其行為。這是編寫doctest示例時(shí)需要了解的信息; 有關(guān)在這些示例上實(shí)際運(yùn)行doctest的信息,請(qǐng)參閱以下各節(jié)。
模塊docstring,以及所有函數(shù),類和方法文檔字符串被搜索。導(dǎo)入到模塊中的對(duì)象不被搜索。
另外,如果M.__test__存在且“為真”,則它必須是字典,并且每個(gè)條目將(字符串)名稱映射到函數(shù)對(duì)象,類對(duì)象或字符串。從中找到的函數(shù)和類對(duì)象文檔字符串M.__test__被搜索,字符串被視為文檔字符串。在輸出,一鍵K在M.__test__出現(xiàn)與名稱
<name of M>.__test__.K
找到的任何類都以相似的方式遞歸搜索,以測(cè)試其包含的方法和嵌套類中的文檔字符串。
在版本2.4中進(jìn)行了更改:“專用名稱”概念已被棄用且不再有記錄。
在大多數(shù)情況下,交互式控制臺(tái)會(huì)話的復(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ù)期的輸出(如果有的話)擴(kuò)展到下一行'>>> '或全空白行。
細(xì)則:
在2.4版本中進(jìn)行了更改:將制表符擴(kuò)展為空格是新的; 以前的版本試圖保留硬標(biāo)簽,結(jié)果令人困惑。
def f(x): ... r'''Backslashes in a raw docstring: m\n''' >>> print f.__doc__ Backslashes in a raw docstring: m\n
否則,反斜杠將被解釋為字符串的一部分。例如,\n以上將被解釋為一個(gè)換行符?;蛘撸梢栽赿octest版本中將每個(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
并且從開始示例的初始行中出現(xiàn)的預(yù)期輸出中刪除了許多主要的空白字符'>>>'。
默認(rèn)情況下,每次doctest發(fā)現(xiàn)一個(gè)文檔字符串進(jìn)行測(cè)試,它采用的是 淺拷貝的M的全局,使運(yùn)行測(cè)試不會(huì)改變模塊真實(shí)的全局,因此,在一個(gè)測(cè)試M不能離開屑不小心讓另外一個(gè)背后測(cè)試工作。這意味著示例可以自由使用任何在頂層定義的M名稱,以及在運(yùn)行的文檔字符串中定義的名稱。示例無法看到其他文檔中定義的名稱。
你可以通過強(qiáng)制使用自己的字典作為執(zhí)行上下文 globs=your_dict來testmod()或testfile()替代。
沒問題,只要回溯是該示例生成的唯一輸出:只需粘貼回溯。[1]由于回溯包含可能快速變化的細(xì)節(jié)(例如,確切的文件路徑和行號(hào)),所以這是doctest很難靈活接受的一種情況。
簡(jiǎn)單的例子:
>>>
>>> [1, 2, 3].remove(42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
該文檔測(cè)試成功,如果ValueError提出,詳情如圖所示。list.remove(x): x not in list
預(yù)期的異常輸出必須以追溯標(biāo)題開頭,該標(biāo)題可以是以下兩行中的任一行,縮寫與示例的第一行相同:
Traceback (most recent call last):
Traceback (innermost last):
traceback頭后面跟著一個(gè)可選的traceback堆棧,其內(nèi)容被doctest忽略?;厮荻褩Mǔ1缓雎?,或者從交互式會(huì)話逐字復(fù)制。
跟蹤堆棧后面是最有趣的部分:包含異常類型和細(xì)節(jié)的行。這通常是追溯的最后一行,但如果異常具有多行詳細(xì)信息,則可以跨越多行:
>>>
>>> raise ValueError('multi\n line\ndetail')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: multi
line
detail
最后三行(以開始ValueError)與異常的類型和細(xì)節(jié)進(jìn)行比較,其余部分將被忽略。
最佳做法是省略追溯堆棧,除非它為示例增加了重要的文檔值。所以最后一個(gè)例子可能更好,因?yàn)椋?/p>
>>>
>>> raise ValueError('multi\n line\ndetail')
Traceback (most recent call last):
...
ValueError: multi
line
detail
請(qǐng)注意,回溯處理非常特別。特別是,在改寫的例子中,使用...獨(dú)立于doctest的 ELLIPSIS選項(xiàng)。這個(gè)例子中的省略號(hào)可以省略,或者可以是三個(gè)(或三百個(gè))逗號(hào)或數(shù)字,或者M(jìn)onty Python skit的縮進(jìn)記錄。
一些細(xì)節(jié)你應(yīng)該閱讀一次,但不需要記?。?/p>
1 1 File "", line 1 1 1 ^ SyntaxError: invalid syntax
由于顯示錯(cuò)誤位置的行出現(xiàn)在異常類型和細(xì)節(jié)之前,因此它們不會(huì)被doctest檢查。例如,即使將^標(biāo)記放在錯(cuò)誤的位置,也會(huì)通過以下測(cè)試:
1 1 File "", line 1 1 1 ^ SyntaxError: invalid syntax
許多選項(xiàng)標(biāo)志控制著doctest行為的各個(gè)方面。這些標(biāo)志的符號(hào)名稱作為模塊常量提供,可以按位或運(yùn)算并傳遞給各種函數(shù)。這些名稱也可以在doctest指令中使用。
第一組選項(xiàng)定義測(cè)試語(yǔ)義,控制doctest如何確定實(shí)際輸出是否與示例預(yù)期輸出相匹配的方面:
doctest.DONT_ACCEPT_TRUE_FOR_1
默認(rèn)情況下,如果預(yù)期的輸出塊只包含1,只是含有實(shí)際輸出塊1或僅True被認(rèn)為是一個(gè)匹配,并類似地用于0對(duì)False。當(dāng)DONT_ACCEPT_TRUE_FOR_1指定時(shí),不允許替換。缺省行為迎合了Python將許多函數(shù)的返回類型從整數(shù)更改為布爾值; 希望“小整數(shù)”輸出的doctests在這些情況下仍然有效。這個(gè)選項(xiàng)可能會(huì)消失,但不會(huì)持續(xù)數(shù)年。
doctest.DONT_ACCEPT_BLANKLINE
默認(rèn)情況下,如果預(yù)期的輸出塊包含僅包含字符串的行<BLANKLINE>,則該行將匹配實(shí)際輸出中的空行。由于真正的空行界定了預(yù)期的輸出,因此這是溝通預(yù)期空行的唯一方式。什么時(shí)候DONT_ACCEPT_BLANKLINE被指定,這個(gè)替代是不允許的。
doctest.NORMALIZE_WHITESPACE
指定時(shí),所有空白(空格和換行符)都被視為相等。預(yù)期輸出中的任何空白序列都將與實(shí)際輸出中的任何空白序列相匹配。默認(rèn)情況下,空白必須完全匹配。NORMALIZE_WHITESPACE當(dāng)預(yù)期輸出的行很長(zhǎng)時(shí),并且您想要在源代碼中的多行中包裝它時(shí),它特別有用。
doctest.ELLIPSIS
指定時(shí),...預(yù)期輸出中的省略號(hào)標(biāo)記()可以匹配實(shí)際輸出中的任何子字符串。這包括跨越行邊界的子字符串和空的子字符串,所以最好保持簡(jiǎn)單的使用。復(fù)雜的用途可能會(huì)導(dǎo)致相同類型的“oops,它匹配得太多了!” .*在正則表達(dá)式中很容易出現(xiàn)意外。
doctest.IGNORE_EXCEPTION_DETAIL
指定時(shí),即使異常詳細(xì)信息不匹配,如果引發(fā)了期望類型的異常,那么期望異常的示例也會(huì)通過。例如,ValueError: 42如果引發(fā)的實(shí)際異常是預(yù)期的例子ValueError: 3*14,但會(huì)失敗,例如,如果TypeError引發(fā)。
它也會(huì)忽略Python 3 doctest報(bào)告中使用的模塊名稱。因此,無論測(cè)試是在Python 2.7還是Python 3.2(或更高版本)下運(yùn)行,這兩種變體都可以與指定的標(biāo)志一起使用:
>>> raise CustomError('message')
Traceback (most recent call last):
CustomError: message
>>> raise CustomError('message')
Traceback (most recent call last):
my_module.CustomError: message
請(qǐng)注意,ELLIPSIS也可以用于忽略異常消息的詳細(xì)信息,但根據(jù)是否將模塊詳細(xì)信息作為異常名稱的一部分進(jìn)行打印,此類測(cè)試可能仍會(huì)失敗。使用IGNORE_EXCEPTION_DETAIL和來自Python 2.3的細(xì)節(jié)也是編寫文檔測(cè)試的唯一明確方式,它不關(guān)心異常細(xì)節(jié),但仍然在Python 2.3或更低版本中繼續(xù)傳遞(這些版本不支持doctest指令并將它們忽略為不相關(guān)的注釋) 。例如:
>>> (1, 2)[3] = 'moo'
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment
雖然Python 2.4中的細(xì)節(jié)更改為“不”而不是“不”,但在Python 2.3以及更高版本的Python版本中通過了指定的標(biāo)志。
在版本2.7中更改:IGNORE_EXCEPTION_DETAIL現(xiàn)在也忽略了與包含被測(cè)異常的模塊有關(guān)的任何信息
doctest.SKIP
指定時(shí),請(qǐng)不要運(yùn)行該示例。這在doctest示例既可用作文檔也可用作測(cè)試用例的情況下非常有用,應(yīng)將其用于文檔目的,但不應(yīng)進(jìn)行檢查。例如,該示例的輸出可能是隨機(jī)的; 或者該示例可能依賴于測(cè)試驅(qū)動(dòng)程序無法使用的資源。
SKIP標(biāo)志也可用于臨時(shí)“注釋”示例。
2.5版本中的新功能。
doctest.COMPARISON_FLAGS
將上面的所有比較標(biāo)志掩蓋起來。
第二組選項(xiàng)控制如何報(bào)告測(cè)試失?。?/p>
doctest.REPORT_UDIFF
指定時(shí),涉及多行預(yù)期和實(shí)際輸出的故障將使用統(tǒng)一差異顯示。
doctest.REPORT_CDIFF
指定時(shí),涉及多行預(yù)期輸出和實(shí)際輸出的故障將使用上下文差異顯示。
doctest.REPORT_NDIFF
指定時(shí),difflib.Differ使用與常用ndiff.py實(shí)用程序相同的算法計(jì)算差異。這是標(biāo)記線內(nèi)和線間差異的唯一方法。例如,如果預(yù)期輸出的一行包含數(shù)字1,其中實(shí)際輸出包含字母l,則會(huì)插入一行,并在其中插入用于標(biāo)記不匹配列位置的插入符號(hào)。
doctest.REPORT_ONLY_FIRST_FAILURE
指定時(shí),顯示每個(gè)doctest中的第一個(gè)失敗示例,但禁止所有其他示例的輸出。這將防止doctest報(bào)告因早期故障而中斷的正確示例; 但它也可能隱藏不正確的例子,不依靠第一次失敗而失敗。當(dāng)REPORT_ONLY_FIRST_FAILURE指定時(shí),剩余的示例仍在運(yùn)行,并仍然計(jì)入報(bào)告的故障總數(shù); 只有輸出被抑制。
doctest.REPORTING_FLAGS
將上面的所有報(bào)告標(biāo)記掩蓋起來。
新的2.4版本:常數(shù)DONT_ACCEPT_BLANKLINE,NORMALIZE_WHITESPACE,ELLIPSIS,IGNORE_EXCEPTION_DETAIL,REPORT_UDIFF,REPORT_CDIFF,REPORT_NDIFF,REPORT_ONLY_FIRST_FAILURE,COMPARISON_FLAGS和REPORTING_FLAGS添加。
還有一種方法可以注冊(cè)新的選項(xiàng)標(biāo)志名稱,但除非您打算doctest通過子類擴(kuò)展內(nèi)部函數(shù),否則這種方法并不有用。
doctest.register_optionflag(name)
用給定名稱創(chuàng)建一個(gè)新選項(xiàng)標(biāo)志,并返回新標(biāo)志的整數(shù)值。register_optionflag()可用于子類化OutputChecker或DocTestRunner創(chuàng)建您的子類支持的新選項(xiàng)。register_optionflag()應(yīng)該總是使用以下習(xí)慣用法來調(diào)用:
MY_FLAG = register_optionflag('MY_FLAG')
New in version 2.4.
Doctest指令可用于修改單個(gè)示例的選項(xiàng)標(biāo)志。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" \| ...
+or -和指令選項(xiàng)名稱之間不允許有空格。指令選項(xiàng)名稱可以是上面解釋的任何選項(xiàng)標(biāo)志名稱。
一個(gè)例子的doctest指令修改了doctest的這個(gè)例子的行為。使用+啟用這個(gè)名字的行為,或-將其禁用。
例如,這個(gè)測(cè)試通過:
>>> print 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]
如果沒有指令,它會(huì)失敗,這是因?yàn)閷?shí)際輸出在單個(gè)數(shù)字列表元素之前沒有兩個(gè)空格,并且因?yàn)閷?shí)際輸出在單行上。這個(gè)測(cè)試也通過了,并且還需要一個(gè)指令來做到這一點(diǎn):
>>> print range(20) # doctest: +ELLIPSIS
[0, 1, ..., 18, 19]
多條指令可用于單條物理線路,用逗號(hào)分隔:
>>> print range(20) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
[0, 1, ..., 18, 19]
如果單個(gè)示例使用多個(gè)指令注釋,則將它們合并:
>>> print range(20) # doctest: +ELLIPSIS
... # doctest: +NORMALIZE_WHITESPACE
[0, 1, ..., 18, 19]
如前例所示,您可以將...行添加到僅包含指令的示例中。當(dāng)一個(gè)例子對(duì)于指令很容易適合同一行時(shí)太長(zhǎng)了,這會(huì)很有用:
>>> print range(5) + range(10,20) + range(30,40) + range(50,60)
... # doctest: +ELLIPSIS
[0, ..., 4, 10, ..., 19, 30, ..., 39, 50, ..., 59]
請(qǐng)注意,由于默認(rèn)情況下所有選項(xiàng)都被禁用,并且指令僅適用于它們出現(xiàn)的示例,因此啟用選項(xiàng)(通過+指令)通常是唯一有意義的選擇。但是,選項(xiàng)標(biāo)志也可以傳遞給運(yùn)行doctests的函數(shù),建立不同的默認(rèn)值。在這種情況下,通過-指令禁用選項(xiàng)可能很有用。
2.4版新增功能:增加了對(duì)doctest指令的支持。
doctest嚴(yán)格要求在預(yù)期產(chǎn)出中要求完全匹配。如果即使單個(gè)字符不匹配,測(cè)試也會(huì)失敗。這可能會(huì)讓你感到驚訝,因?yàn)槟愦_切地知道Python做了什么,并且不能保證輸出。例如,在打印字典時(shí),Python不保證鍵值對(duì)將以任何特定的順序打印,因此像
>>> foo()
{"Hermione": "hippogryph", "Harry": "broomstick"}
很脆弱!一種解決方法是做
>>> foo() == {"Hermione": "hippogryph", "Harry": "broomstick"}
True
代替。另一個(gè)是要做的
>>> d = foo().items()
>>> d.sort()
>>> d
[('Harry', 'broomstick'), ('Hermione', 'hippogryph')]
還有其他的,但你明白了。
另一個(gè)不好的想法是打印嵌入對(duì)象地址的東西,比如
>>> id(1.0) # certain to fail some of the time
7948648
>>> class C: pass
>>> C() # the default repr() for instances embeds an address
<__main__.C instance at 0x00AC18F0>
ELLIPSIS指令為最后一個(gè)示例提供了一個(gè)很好的方法:
>>> C() #doctest: +ELLIPSIS
<__main__.C instance at 0x...>
浮點(diǎn)數(shù)也受到跨平臺(tái)的小輸出變化的影響,因?yàn)镻ython遵循平臺(tái)C庫(kù)進(jìn)行浮點(diǎn)格式化,而C庫(kù)在質(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ù)字在所有平臺(tái)上都是安全的,而且我通常會(huì)編寫一些doctest的例子來生成這種格式的數(shù)字:
>>> 3./4 # utterly safe
0.75
簡(jiǎn)單的分?jǐn)?shù)對(duì)于人們來說也更容易理解,并且這使得更好的文檔。
更多建議: