💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 13.6. 完备性检测 (Testing for sanity) 你经常会发现一组代码中包含互逆的转换函数,一个把 A 转换为 B ,另一个把 B 转换为 A。在这种情况下,创建“完备性检测”可以使你在由 A 转 B 再转 A 的过程中不会出现丢失精度或取整等错误。 考虑这个[要求](index.html#roman.requirements): 1. 如果你给定一个数,把它转化为罗马数字表示,然后再转换回阿拉伯数字表示,你所得到的应该是最初给定的那个数。因此,对于 `1..3999` 中的`n`,`fromRoman(toRoman(n)) == n` 总成立。 ## 例 13.5. 以 `toRoman` 测试 `fromRoman` 的输出 ``` class SanityCheck(unittest.TestCase): def testSanity(self): """fromRoman(toRoman(n))==n for all n""" for integer in range(1, 4000): numeral = roman.toRoman(integer) result = roman.fromRoman(numeral) self.assertEqual(integer, result) ``` | | | | --- | --- | | \[1\] | 你已经见到过 [`range` 函数](../native_data_types/declaring_variables.html#odbchelper.multiassign.range "例 3.20. 连续值赋值"),但这里它以两个参数被调用,返回了从第一个参数 (`1`) 开始到_但不包括_ 第二个参数 (`4000`) 的整数列表。因此,`1..3999` 就是准备转换为罗马数字表示的有效值列表。 | | \[2\] | 我想提一下,这里的 `integer` 并不是一个 Python 关键字,而只是没有什么特别的变量名。 | | \[3\] | 这里的测试逻辑显而易见:把一个数 (`integer`) 转换为罗马数字表示的数 (`numeral`),然后再转换回来 (`result`) 并确保最后的结果和最初的数是同一个数。如果不是,`assertEqual` 便会引发异常,测试也便立刻失败。如果所有的结果都和初始数一致,`assertEqual` 将会保持沉默,整个 `testSanity` 方法将会最终也保持沉默,测试则将会被认定为通过。 | [最后两个要求](index.html#roman.requirements)和其他的要求不同,似乎既武断而又微不足道: 1. `toRoman` 返回的罗马数字应该使用大写字母。 2. `fromRoman` 应该只接受大写罗马数字 (也就是说给定小写字母进行转换时应该失败)。 事实上,它们确实有点武断,譬如你完全可以让 `fromRoman` 接受小写和大小写混合的输入;但他们也不是完全武断;如果 `toRoman` 总是返回大写的输出,那么 `fromRoman` 至少应该接受大写字母输入,不然 “完备性检测” (要求 #6) 就会失败。不管怎么说,_只_ 接受大写输入还是武断的,但就像每个系统都会告诉你的那样,大小写总会出问题,因此事先规定这一点还是有必要的。既然有必要规定,那么也就有必要测试。 ## 例 13.6. 大小写测试 ``` class CaseCheck(unittest.TestCase): def testToRomanCase(self): """toRoman should always return uppercase""" for integer in range(1, 4000): numeral = roman.toRoman(integer) self.assertEqual(numeral, numeral.upper()) def testFromRomanCase(self): """fromRoman should only accept uppercase input""" for integer in range(1, 4000): numeral = roman.toRoman(integer) roman.fromRoman(numeral.upper()) self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, numeral.lower()) ``` | | | | --- | --- | | \[1\] | 关于这个测试用例最有趣的一点不在于它测试了什么,而是它不测试什么。它不会测试 `toRoman` 的返回值是否[正确](testing_for_success.html#roman.testtoromanknownvalues.example "例 13.2. testToRomanKnownValues")或者[一致](testing_for_sanity.html#roman.sanity.example "例 13.5. 以 toRoman 测试 fromRoman 的输出");这些问题由其他测试用例来回答。整个测试用例仅仅测试大写问题。你也许觉得应该将它并入到[完备性测试](testing_for_sanity.html#roman.sanity.example "例 13.5. 以 toRoman 测试 fromRoman 的输出"),毕竟都要遍历整个输入值范围并调用 `toRoman`。\[11\]但是这样将会违背一条[基本规则](testing_for_success.html "13.4. 正面测试 (Testing for success)"):每个测试用例只回答一个的问题。试想一下,你将这个测试并入到完备性测试中,然后遇到了测试失败。你还需要进一步分析以便判定测试用例的哪部分出了问题。如果你需要分析方能找出问题所在,无疑你的测试用例在设计上出了问题。 | | \[2\] | 这有一个和前面相似的情况:尽管 “你知道” `toRoman` 总是返回大写字母,你还是需要把返回值显式地转换成大写字母后再传递给只接受大写的 `fromRoman` 进行测试。为什么?因为 `toRoman` 只返回大写字母是一个独立的需求。如果你改变了这个需求,例如改成总是返回小写字母,那么 `testToRomanCase` 测试用例也应作出调整,但这个测试用例应该仍能通过。这是另外一个[基本规则](testing_for_success.html "13.4. 正面测试 (Testing for success)"):每个测试用例必须可以与其他测试用例隔离工作,每个测试用例是一个“孤岛”。 | | \[3\] | 注意你并没有使用 `fromRoman` 的返回值。这是一个有效的 Python 语法:如果一个函数返回一个值,但没有被使用,Python 会直接把这个返回值扔掉。这正是你所希望的,这个测试用例并不对返回值进行测试,只是测试 `fromRoman` 接受大写字母而不引发异常。 | | \[4\] | 这行有点复杂,但是它与 `ToRomanBadInput` 和 `FromRomanBadInput` 测试很相似。 你在测试以特定值 (`numeral.lower()`,循环中目前罗马数字的小写版) 调用特定函数 (`roman.fromRoman`) 会确实引发特定的异常 (`roman.InvalidRomanNumeralError`)。如果 (在循环中的每一次) 确实如此,测试通过;如果有一次不是这样 (比如引发另外的异常或者不引发异常),测试失败。 | 在下一章中,你将看到如何编写可以通过这些测试的代码。 ## Footnotes \[11\] “除了诱惑什么我都能抗拒。 (I can resist everything except temptation.)”――Oscar Wilde