<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 15.2.?應對需求變化 盡管你竭盡努力地分析你的客戶,并點燈熬油地提煉出精確的需求,但需求還是會是不斷變化。大部分客戶在看到產品前不知道他們想要什么。即便知道,也不擅于精確表述出他們的有效需求。即便能表述出來,他們在下一個版本一定會要求更多的功能。因此你需要做好更新測試用例的準備以應對需求的改變。 假設你想要擴展羅馬數字轉換函數的范圍。還記得[沒有哪個字符可以重復三遍以上](../unit_testing/diving_in.html "13.2.?深入")這條規則嗎?呃,現在羅馬人希望給這條規則來個例外,用連續出現 4 個 `M` 字符來表示 `4000`。如果這樣改了,你就可以把轉換范圍從 `1..3999` 擴展到 `1..4999`。但你先要對測試用例進行修改。 ## 例?15.6.?修改測試用例以適應新需求 (`romantest71.py`) 這個文件可以在例子目錄下的 `py/roman/stage7/` 目錄中找到。 如果您還沒有下載本書附帶的樣例程序, 可以 [下載本程序和其他樣例程序](http://www.woodpecker.org.cn/diveintopython/download/diveintopython-exampleszh-cn-5.4b.zip "Download example scripts")。 ``` import roman71 import unittest class KnownValues(unittest.TestCase): knownValues = ( (1, 'I'), (2, 'II'), (3, 'III'), (4, 'IV'), (5, 'V'), (6, 'VI'), (7, 'VII'), (8, 'VIII'), (9, 'IX'), (10, 'X'), (50, 'L'), (100, 'C'), (500, 'D'), (1000, 'M'), (31, 'XXXI'), (148, 'CXLVIII'), (294, 'CCXCIV'), (312, 'CCCXII'), (421, 'CDXXI'), (528, 'DXXVIII'), (621, 'DCXXI'), (782, 'DCCLXXXII'), (870, 'DCCCLXX'), (941, 'CMXLI'), (1043, 'MXLIII'), (1110, 'MCX'), (1226, 'MCCXXVI'), (1301, 'MCCCI'), (1485, 'MCDLXXXV'), (1509, 'MDIX'), (1607, 'MDCVII'), (1754, 'MDCCLIV'), (1832, 'MDCCCXXXII'), (1993, 'MCMXCIII'), (2074, 'MMLXXIV'), (2152, 'MMCLII'), (2212, 'MMCCXII'), (2343, 'MMCCCXLIII'), (2499, 'MMCDXCIX'), (2574, 'MMDLXXIV'), (2646, 'MMDCXLVI'), (2723, 'MMDCCXXIII'), (2892, 'MMDCCCXCII'), (2975, 'MMCMLXXV'), (3051, 'MMMLI'), (3185, 'MMMCLXXXV'), (3250, 'MMMCCL'), (3313, 'MMMCCCXIII'), (3408, 'MMMCDVIII'), (3501, 'MMMDI'), (3610, 'MMMDCX'), (3743, 'MMMDCCXLIII'), (3844, 'MMMDCCCXLIV'), (3888, 'MMMDCCCLXXXVIII'), (3940, 'MMMCMXL'), (3999, 'MMMCMXCIX'), (4000, 'MMMM'), (4500, 'MMMMD'), (4888, 'MMMMDCCCLXXXVIII'), (4999, 'MMMMCMXCIX')) def testToRomanKnownValues(self): """toRoman should give known result with known input""" for integer, numeral in self.knownValues: result = roman71.toRoman(integer) self.assertEqual(numeral, result) def testFromRomanKnownValues(self): """fromRoman should give known result with known input""" for integer, numeral in self.knownValues: result = roman71.fromRoman(numeral) self.assertEqual(integer, result) class ToRomanBadInput(unittest.TestCase): def testTooLarge(self): """toRoman should fail with large input""" self.assertRaises(roman71.OutOfRangeError, roman71.toRoman, 5000) def testZero(self): """toRoman should fail with 0 input""" self.assertRaises(roman71.OutOfRangeError, roman71.toRoman, 0) def testNegative(self): """toRoman should fail with negative input""" self.assertRaises(roman71.OutOfRangeError, roman71.toRoman, -1) def testNonInteger(self): """toRoman should fail with non-integer input""" self.assertRaises(roman71.NotIntegerError, roman71.toRoman, 0.5) class FromRomanBadInput(unittest.TestCase): def testTooManyRepeatedNumerals(self): """fromRoman should fail with too many repeated numerals""" for s in ('MMMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'): self.assertRaises(roman71.InvalidRomanNumeralError, roman71.fromRoman, s) def testRepeatedPairs(self): """fromRoman should fail with repeated pairs of numerals""" for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'): self.assertRaises(roman71.InvalidRomanNumeralError, roman71.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(roman71.InvalidRomanNumeralError, roman71.fromRoman, s) def testBlank(self): """fromRoman should fail with blank string""" self.assertRaises(roman71.InvalidRomanNumeralError, roman71.fromRoman, "") class SanityCheck(unittest.TestCase): def testSanity(self): """fromRoman(toRoman(n))==n for all n""" for integer in range(1, 5000): numeral = roman71.toRoman(integer) result = roman71.fromRoman(numeral) self.assertEqual(integer, result) class CaseCheck(unittest.TestCase): def testToRomanCase(self): """toRoman should always return uppercase""" for integer in range(1, 5000): numeral = roman71.toRoman(integer) self.assertEqual(numeral, numeral.upper()) def testFromRomanCase(self): """fromRoman should only accept uppercase input""" for integer in range(1, 5000): numeral = roman71.toRoman(integer) roman71.fromRoman(numeral.upper()) self.assertRaises(roman71.InvalidRomanNumeralError, roman71.fromRoman, numeral.lower()) if __name__ == "__main__": unittest.main() ``` | | | | --- | --- | | \[1\] | 原來的已知值沒有改變 (它們仍然是合理的測試值) 但你需要添加幾個大于 `4000` 的值。這里我添加了 `4000` (最短的一個),`4500` (次短的一個),`4888` (最長的一個) 和 `4999` (值最大的一個)。 | | \[2\] | “最大輸入”的定義改變了。以前是以 `4000` 調用 `toRoman` 并期待一個錯誤;而現在 `4000-4999` 成為了有效輸入,需要將這個最大輸入提升至 `5000`。 | | \[3\] | “過多字符重復” 的定義也改變了。這個測試以前是以 `'MMMM'` 調用 `fromRoman` 并期待一個錯誤;而現在 `MMMM` 被認為是一個有效的羅馬數字表示,需要將這個“過多字符重復”改為 `'MMMMM'`。 | | \[4\] | 完備測試和大小寫測試原來在 `1` 到 `3999` 范圍內循環。現在范圍擴展了,這個 `for` 循環需要將范圍也提升至 `4999`。 | 現在你的測試用例和新需求保持一致了,但是你的程序代碼還沒有,因此幾個測試用例的失敗是意料之中的事。 ## 例?15.7.?用 `romantest71.py` 測試 `roman71.py` 的結果 ``` fromRoman should only accept uppercase input ... ERROR toRoman should always return uppercase ... ERROR fromRoman should fail with blank string ... ok 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 ... ERROR toRoman should give known result with known input ... ERROR fromRoman(toRoman(n))==n for all n ... ERROR 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 ``` | | | | --- | --- | | \[1\] | 我們的大小寫檢查是因為循環范圍是 `1` 到 `4999`,而 `toRoman` 只接受 `1` 到 `3999` 之間的數,因此測試循環到 `4000` 就會失敗。 | | \[2\] | `fromRoman` 的已知值測試在遇到 `'MMMM'` 就會失敗,因為 `fromRoman` 還認為這是一個無效的羅馬數字表示。 | | \[3\] | `toRoman` 的已知值測試在遇到 `4000` 就會失敗,因為 `toRoman` 仍舊認為這超出了有效值范圍。 | | \[4\] | 完備測試在遇到 `4000` 也會失敗,因為 `toRoman` 也會認為這超出了有效值范圍。 | ``` ====================================================================== ERROR: fromRoman should only accept uppercase input ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage7\romantest71.py", line 161, in testFromRomanCase numeral = roman71.toRoman(integer) File "roman71.py", line 28, in toRoman raise OutOfRangeError, "number out of range (must be 1..3999)" OutOfRangeError: number out of range (must be 1..3999) ====================================================================== ERROR: toRoman should always return uppercase ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage7\romantest71.py", line 155, in testToRomanCase numeral = roman71.toRoman(integer) File "roman71.py", line 28, in toRoman raise OutOfRangeError, "number out of range (must be 1..3999)" OutOfRangeError: number out of range (must be 1..3999) ====================================================================== ERROR: fromRoman should give known result with known input ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage7\romantest71.py", line 102, in testFromRomanKnownValues result = roman71.fromRoman(numeral) File "roman71.py", line 47, in fromRoman raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s InvalidRomanNumeralError: Invalid Roman numeral: MMMM ====================================================================== ERROR: toRoman should give known result with known input ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage7\romantest71.py", line 96, in testToRomanKnownValues result = roman71.toRoman(integer) File "roman71.py", line 28, in toRoman raise OutOfRangeError, "number out of range (must be 1..3999)" OutOfRangeError: number out of range (must be 1..3999) ====================================================================== ERROR: fromRoman(toRoman(n))==n for all n ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage7\romantest71.py", line 147, in testSanity numeral = roman71.toRoman(integer) File "roman71.py", line 28, in toRoman raise OutOfRangeError, "number out of range (must be 1..3999)" OutOfRangeError: number out of range (must be 1..3999) ---------------------------------------------------------------------- Ran 13 tests in 2.213s FAILED (errors=5) ``` 既然新的需求導致了測試用例的失敗,你該考慮修改代碼以便它能再次通過測試用例。(在你開始編寫單元測試時要習慣一件事:被測試代碼永遠不會在編寫測試用例“之前”編寫。正因為如此,你還有一些工作要做,一旦可以通過所有的測試用例,停止編碼。) ## 例?15.8.?為新的需求編寫代碼 (`roman72.py`) 這個文件可以在例子目錄下的 `py/roman/stage7/` 目錄中找到。 ``` """Convert to and from Roman numerals""" import re #Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Define digit mapping romanNumeralMap = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5), ('IV', 4), ('I', 1)) def toRoman(n): """convert integer to Roman numeral""" if not (0 < n < 5000): raise OutOfRangeError, "number out of range (must be 1..4999)" if int(n) <> n: raise NotIntegerError, "non-integers can not be converted" result = "" for numeral, integer in romanNumeralMap: while n >= integer: result += numeral n -= integer return result #Define pattern to detect valid Roman numerals romanNumeralPattern = '^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$' 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 ``` | | | | --- | --- | | \[1\] | `toRoman` 只需要在取值范圍檢查一處做個小改動。將原來的 `0 &lt; n &lt; 4000`,更改為現在的檢查 `0 &lt; n &lt; 5000`。你還要更改你 `raise` 的錯誤信息以反映接受新取值范圍 (`1..4999` 而不再是 `1..3999`)。你不需要改變函數的其他部分,它們已經適用于新的情況。(它們會欣然地為新的 1000 添加 `'M'`,以 `4000` 為例,函數會返回 `'MMMM'` 。之前沒能這樣做是因為到范圍檢查時就被停了下來。) | | \[2\] | 你對 `fromRoman` 也不需要做過多的修改。唯一的修改就在 `romanNumeralPattern`:如果你注意的話,你會發現你只需在正則表達式的第一部分增加一個可選的 `M` 。這就允許最多 4 個 `M` 字符而不再是 3 個,意味著你允許代表 `4999` 而不只是 `3999` 的羅馬數字。`fromRoman` 函數本身是普遍適用的,它并不在意字符被多少次的重復,只是根據重復的羅馬字符對應的數值進行累加。以前沒能處理 `'MMMM'` 是因為你通過正則表達式的檢查強行停止了。 | 你可能會懷疑只需這兩處小改動。嘿,不相信我的話,你自己看看吧: ## 例?15.9.?用 `romantest72.py` 測試 `roman72.py` 的結果 ``` fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with blank string ... ok 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 ---------------------------------------------------------------------- Ran 13 tests in 3.685s OK ``` | | | | --- | --- | | \[1\] | 所有的測試用例都通過了,停止編寫代碼。 | 全面的單元測試意味著不必依賴于程序員的一面之詞:“相信我!”
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看