| 導航:起始頁 > Dive Into Python > 重構 | << >> | ||||
深入 Python :Dive Into Python 中文版Python 從新手到專家 [Dip_5.4b_CPyUG_Release] |
|||||
盡管你很努力地編寫全面的單元測試,但是 bug 還是會出現。我所說的 “bug” 是什么呢?Bug 是你還沒有編寫的測試用例。
>>> import roman5 >>> roman5.fromRoman("")0
| 在前面的章節中你注意到一個空字符串會匹配上那個檢查羅馬數字有效性的正則表達式了嗎?對于最終版本中的正則表達式這一點仍然沒有改變。這就是一個 Bug ,你希望空字符串能夠像其他無效的羅馬數字表示一樣引發 InvalidRomanNumeralError 異常。 |
在重現這個 Bug 并修改它之前你應該編寫一個會失敗的測試用例來說明它。
class FromRomanBadInput(unittest.TestCase): # previous test cases omitted for clarity (they haven't changed) def testBlank(self): """fromRoman should fail with blank string""" self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, "")![]()
因為你的代碼存在一個 Bug,并且你編寫了測試這個 Bug 的測試用例,所以測試用例將會失敗:
fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with blank string ... FAIL fromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ====================================================================== FAIL: fromRoman should fail with blank string ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage6\romantest61.py", line 137, in testBlank self.assertRaises(roman61.InvalidRomanNumeralError, roman61.fromRoman, "") File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ---------------------------------------------------------------------- Ran 13 tests in 2.864s FAILED (failures=1)
現在 你可以修改這個 Bug了。
這個文件可以在例子目錄下的 py/roman/stage6/ 目錄中找到。
def fromRoman(s): """convert Roman numeral to integer""" if not s:raise InvalidRomanNumeralError, 'Input can not be blank' if not re.search(romanNumeralPattern, s): raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result
fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with blank string ... okfromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ---------------------------------------------------------------------- Ran 13 tests in 2.834s OK
這樣編程,并沒有令 Bug 修正變得簡單。簡單的 Bug (就像這一個) 需要簡單的測試用例,復雜 Bug 則需要復雜的測試用例。以測試為核心的氛圍好像 延長了修正 Bug 的時間,因為你需要先貼切地描述出 Bug (編寫測試用例) 然后才去修正它。如果測試用例沒能正確通過,你需要思量這個修改錯了還是測試用例本身出現了 Bug。無論如何,從長遠上講,這樣在測試代碼和代碼之間的反復是值得的,因為這樣會使 Bug 在第一時間就被修正的可能性大大提高。而且不論如何更改,你都可以輕易地重新運行所有 測試用例,新代碼破壞老代碼的機會也變得微乎其微。今天的單元測試就是明天的回歸測試 (regression test)。
<< roman.py, 第 5 階段 |
| 1 | 2 | 3 | 4 | 5 | |
應對需求變化 >> |