Python 单元测试框架入门

-- xyb [2004-09-22 01:51:30]

1. Python 单元测试框架入门

作者:Xie Yanbo,版权:创作共用/cc 1.0

Python 中的单元测试框架也叫做 PyUnit,在源代码中是一个叫做 unittest 的模块,它提供了对单元测试的支持。

1.1. 单个测试文件

Python 的单元测试中,一般一个测试程序文件负责测试 Python 的一个模块,或者一个模块中的一个代码文件。它们经常以 test_somemodule.py 或 testSomeModule.py 的名字命名;一般保存在被测试模块的一个子目录 tests 中,也有就保存在该模块的根目录的。

1.1.1. 一个测试文件的例子

拿一个例子来看一下,这个例子也可用做测试文件的模板:

   1 from unittest import TestCase
   2 
   3 class simpleTest(TestCase):
   4     def setUp(self):
   5         pass
   6 
   7     def tearDown(self):
   8         pass
   9 
  10     def testExample(self):
  11         self.assertEqual(len('abc'), 3)
  12 
  13     def testOther(self):
  14         self.assertNotEqual('abc', 'ABC')
  15         self.failUnless(0 == 1)
  16 
  17 if __name__ == '__main__':
  18     import unittest
  19     unittest.main()

如果要编写一个测试程序,需要以 unittest.TestCase 为基类派生,这样 unittest 就可以自动找到这些测试类了。这样的一个测试类中,凡是以“test”开头的方法,都是一个测试用例。比如上面的例子中就有两个,testExample 和 testOther。unittest 会自动统计测试用例的个数,并在最后的测试报告中指出测试成功了几个,有那些失败。TestCase 还有两个很有用的方法:在每个测试用例执行之前,写在 setUp 中的代码都会被执行,用来初始化测试环境;而 tearDown 中的代码则负责清除每个测试用例遗留的垃圾,恢复现场。这两个方法可以一起使用,也可以分别使用,或者象例子中的一样,完全不用。例子中最后的一段代码可以让这个测试文件做为一个单独的程序来运行。

1.1.2. 测试用例

其实对于每个测试用例来说,不外乎三个过程:准备,执行目标,测试结果。比如我们可以把上面 testExample 的一行代码拆开,就可以看的更清楚了:

   1     def testExample(self):
   2         astr = 'abc'                  # 这个测试用例的准备工作
   3         length = len(astr)            # 执行要测试的目标函数、方法
   4         self.assertEqual(length, 3)   # 测试,看看和预期结果是否相符

1.1.3. 测试指令

测试时,可以直接使用 Python 提供的 assert 方法来比较测试结果和预期结果,比如:

    def testSomething(self):
        assert len('abc') == 3

也可以使用 unittest.TestCase 提供的大量测试方法,包括:

  • assertAlmostEqual
  • assertAlmostEquals
  • assertEqual
  • assertEquals
  • assertNotAlmostEqual
  • assertNotAlmostEquals
  • assertNotEqual
  • assertNotEquals
  • assertRaises
  • fail
  • failIf
  • failIfAlmostEqual
  • failIfEqual
  • failUnless
  • failUnlessAlmostEqual
  • failUnlessEqual
  • failUnlessRaises
  • failureException

它们的详细资料请查看 Python 官方文档。我可以告诉你的是,这些方法从命名上很容易理解,能让我们更快的知道测试成功或失败的条件;另外它们全都带有一个名为 msg 的可选参数,在该测试用例出错时它们会被显示在报告中,这也有助于我们更快的知道究竟发生了什么事情。不过,我个人更喜欢不带 msg 参数的 assertEqual 和 assertNotEqual,因为它们在出错时会把参数值打印出来,这些有时对我们调试程序可能更有用处。比如:

self.assertEqual(len('abc'), 0)

会打印出:

AssertionError: 3 != 0

如果加上 msg 参数,这些就看不到了。

1.2. 运行所有测试

大多数模块都会提供一个一次执行所有测试用例的程序文件,这个文件常以 runalltests.py 或 alltests.py 命名,保存在模块的根目录中。下面是它的一个例子文件:

   1 #!/usr/bin/env python
   2 
   3 import unittest
   4 import sys
   5 import os
   6 
   7 sys.path.append(os.curdir)
   8 sys.path.append(os.pardir)
   9 sys.path.append(os.path.join(os.curdir, 'tests'))
  10 
  11 tests = os.listdir(os.curdir)
  12 tests = [n[:-3] for n in tests if n.startswith('test') and n.endswith('.py')]
  13 
  14 teststests = os.path.join(os.curdir, 'tests')
  15 if os.path.isdir(teststests):
  16     teststests = os.listdir(teststests)
  17     teststests = [n[:-3] for n in teststests if n.startswith('test') \
            and n.endswith('.py')]
  18     modules_to_test = tests + teststests
  19 else:
  20     modules_to_test = tests
  21 
  22 def suite():
  23     alltests = unittest.TestSuite()
  24     for module in map(__import__, modules_to_test):
  25         alltests.addTest(unittest.findTestCases(module))
  26     return alltests
  27 
  28 if __name__ == '__main__':
  29     unittest.main(defaultTest='suite')

程序的开头把当前目录、上一级目录和子目录 tests 都放入系统搜寻路径中。这是为了 Python 程序 import 模块代码和测试代码时更容易找到它们。紧接着,在当前目录和子目录 tests 中搜寻所有测试程序,把它们的列表加入到 modules_to_test 中。suite 方法根据前面定义的列表把所有测试用例都找出来。最后通过调用 unittest 的 testrunner 就可以启动我们的所有测试了,我们只要稍等一会儿,它就会把最终的测试报告放到我们面前 :)