原文:http://woodpecker.org.cn/diveintopython/unit_testing/testing_for_failure.html
13.5. 負面測試 (Testing for failure)
使用有效輸入確保函數成功通過測試還不夠,你還需要測試無效輸入導致函數失敗的情形。但並不是任何失敗都可以,必須如你預期地失敗。
還記得 toRoman 的其他要求吧:
- toRoman 在輸入值爲 1 到 3999 之外時失敗。
- toRoman 在輸入值爲非整數時失敗。
在 Python 中,函數以引發異常的方式表示失敗。unittest 模塊提供了用於測試函數是否在給定無效輸入時引發特定異常的方法。
例 13.3. 測試 toRoman 的無效輸入
class ToRomanBadInput(unittest.TestCase): def testTooLarge(self): """toRoman should fail with large input""" self.assertRaises(roman.OutOfRangeError, roman.toRoman, 4000) def testZero(self): """toRoman should fail with 0 input""" self.assertRaises(roman.OutOfRangeError, roman.toRoman, 0) def testNegative(self): """toRoman should fail with negative input""" self.assertRaises(roman.OutOfRangeError, roman.toRoman, -1) def testNonInteger(self): """toRoman should fail with non-integer input""" self.assertRaises(roman.NotIntegerError, roman.toRoman, 0.5)
unittest 模塊中的 TestCase 類提供了 assertRaises 方法,它接受這幾個參數:預期的異常、測試的函數,以及傳遞給函數的參數。(如果被測試函數有不止一個參數,把它們按順序全部傳遞給 assertRaises ,它會把這些參數傳給被測的函數。) 特別注意這裏的操作:不是直接調用 toRoman 再手工查看是否引發特定異常 (使用try...except 塊捕捉異常),assertRaises 爲我們封裝了這些。所有你要做的就是把異常 (roman.OutOfRangeError)、函數 (toRoman) 以及 toRoman 的參數 (4000) 傳遞給 assertRaises ,它會調用 toRoman查看是否引發 roman.OutOfRangeError 異常。(還應注意到你是把 toRoman 函數本身當作一個參數,而不是調用它,傳遞它的時候也不是把它的名字作爲一個字符串。我提到過嗎?無論是函數還是異常, Python 中萬物皆對象)。 | |
與測試過大的數相伴的便是測試過小的數。記住,羅馬數字不能表示 0 和負數,所以你要分別編寫測試用例 (testZero 和 testNegative)。在 testZero 中,你測試 toRoman 調用 0 引發的 roman.OutOfRangeError 異常,如果沒能 引發 roman.OutOfRangeError (不論是返回了一個值還是引發了其他異常),則測試失敗。 | |
要求 #3:toRoman 不能接受非整數輸入,所以這裏你測試 toRoman 在輸入 0.5 時引發 roman.NotIntegerError 異常。如果 toRoman 沒有引發 roman.NotIntegerError 異常,則測試失敗。 |
接下來的兩個要求與前三個類似,不同點是他們所針對的是 fromRoman 而不是 toRoman:
- fromRoman 應該能將輸入的有效羅馬數字轉換爲相應的阿拉伯數字表示。
- fromRoman 在輸入無效羅馬數字時應該失敗。
要求 #4 與要求 #1 的處理方法相同,即測試一個已知樣本中的一個個數字對。要求 #5 與 #2 和 #3的處理方法相同,即通過無效輸入確認 fromRoman 引發恰當的異常。
例 13.4. 測試 fromRoman 的無效輸入
class FromRomanBadInput(unittest.TestCase): def testTooManyRepeatedNumerals(self): """fromRoman should fail with too many repeated numerals""" for s in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'): self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s) def testRepeatedPairs(self): """fromRoman should fail with repeated pairs of numerals""" for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'): self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s) def testMalformedAntecedent(self): """fromRoman should fail with malformed antecedents""" for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV', 'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'): self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s)