13.5. 负面测试 (Testing for failure)

使用有效输入确保函数成功通过测试还不够,你还需要测试无效输入导致函数失败的情形。但并不是任何失败都可以,必须如你预期地失败。

还记得 toRoman其他要求吧:

  1. toRoman 在输入值为 13999 之外时失败。
  2. 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) 1

    def testZero(self):                                              
        """toRoman should fail with 0 input"""                       
        self.assertRaises(roman.OutOfRangeError, roman.toRoman, 0)    2

    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)  3
1 unittest 模块中的 TestCase 类提供了 assertRaises 方法,它接受这几个参数:预期的异常、测试的函数,以及传递给函数的参数。(如果被测试函数有不止一个参数,把它们按顺序全部传递给 assertRaises ,它会把这些参数传给被测的函数。) 特别注意这里的操作:不是直接调用 toRoman 再手工查看是否引发特定异常 (使用 try...except捕捉异常),assertRaises 为我们封装了这些。所有你要做的就是把异常 (roman.OutOfRangeError)、函数 (toRoman) 以及 toRoman 的参数 (4000) 传递给 assertRaises ,它会调用 toRoman 查看是否引发 roman.OutOfRangeError 异常。(还应注意到你是把 toRoman 函数本身当作一个参数,而不是调用它,传递它的时候也不是把它的名字作为一个字符串。我提到过吗?无论是函数还是异常, Python 中万物皆对象)。
2 与测试过大的数相伴的便是测试过小的数。记住,罗马数字不能表示 0 和负数,所以你要分别编写测试用例 ( testZerotestNegative)。在 testZero 中,你测试 toRoman 调用 0 引发的 roman.OutOfRangeError 异常,如果没能 引发 roman.OutOfRangeError (不论是返回了一个值还是引发了其他异常),则测试失败。
3 要求 #3toRoman 不能接受非整数输入,所以这里你测试 toRoman 在输入 0.5 时引发 roman.NotIntegerError 异常。如果 toRoman 没有引发 roman.NotIntegerError 异常,则测试失败。

接下来的两个要求与前三个类似,不同点是他们所针对的是 fromRoman 而不是 toRoman

  1. fromRoman 应该能将输入的有效罗马数字转换为相应的阿拉伯数字表示。
  2. 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) 1

    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)
1 没什么新鲜的,与测试 toRoman 无效输入时相同的模式,只是你有了一个新的异常:roman.InvalidRomanNumeralErrorroman.py 中一共要定义三个异常 (另外的两个是 roman.OutOfRangeErrorroman.NotIntegerError)。稍后你在开始编写 roman.py 时将会知道如何定义这些异常。