构建测试代码
测试用例是单元测试的基础模块——它在每个场景都需要被创建,用来检查每个场景的正确性。在unittest中,测试用例通过unittest.TestCase类的实例来实现。为了创建测试用例,你必须通过子类继承TestCase或者FunctionTestCase。 TestCase实例的测试代码应该完全独立,这样它就可以单独运行,也可以和其它任意的测试用例组合来运行。 最简单的TestCase子类仅仅包含一个runTest()方法(即以test开头的方法),该方法只是为了执行特定的测试代码。
import unittest
class DefaultWidgetSizeTestCase(unittest.TestCase):
def test_default_widget_size(self):
widget = Widget('The widget')
self.assertEqual(widget.size(), (50, 50))
请注意,为了测试一些东西,我们可以使用TestCase基类提供的assert*()方法。如果测试失败,它将会抛出异常,并且unittest会将该测试用例标识为失败。其他的异常也同样会使测试用例被标识为失败。 测试用例是可以多条的,并且他们的setup可以是重复的。庆幸的是,我们可以通过setUp()方法重用代码,测试框架在运行每个测试用例时自动调用:
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def test_default_widget_size(self):
self.assertEqual(self.widget.size(), (50,50),
'incorrect default size')
def test_widget_resize(self):
self.widget.resize(100,150)
self.assertEqual(self.widget.size(), (100,150),
'wrong size after resize')
注意:测试执行的顺序是根据测试方法的名称按字符串顺序来确定的。
执行测试的时候,如果setUp()方法抛出了异常,它会认为测试遇到错误,并且测试方法将不会执行。
同样,在测试方法之后,我们提供了tearDown()方法:
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def tearDown(self):
self.widget.dispose()
如果setUp()执行成功,无论测试方法是否执行成功,都会执行tearDown()。
一个这样的测试代码所需的工作环境称为一个fixture。通过独有的fixture创建测试用例,用来执行每个特定的测试方法。因此,每次测试都调用一次setUp(),tearDown()和__init()__方法。
建议你根据测试的功能使用TestCase将测试用例组合在一起执行。unittest为此提供了一套机制:测试套件,由unittest的TestSuite类实现。在多数情况下,通过调用unittest.main()可以执行所有模块的测试用例。
但是,如果要自定义测试套件,可以自己设计定义:
def suite():
suite = unittest.TestSuite()
suite.addTest(WidgetTestCase('test_default_widget_size'))
suite.addTest(WidgetTestCase('test_widget_resize'))
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
你可以将测试用例和测试套件与被测试代码定义在相同的模块中(如widget.py),但是将测试代码放在单独的模块中有几个优点,例如test_widget.py:
- 测试模块可以通过命令行独立运行。
- 测试代码可以很方便地从发布代码中分离。
- 在没有必要的情况下,不必为了适应被测试的代码而频繁地更改测试代码。
- 相对于被测试代码,测试代码不应被频繁修改。
- 被测试代码可以更容易被重构。
- 既然用C语言编写的测试模块也在单独模块中,为什么不保持它的一致性?
- 如果测试策略改变,也无需修改被测试的源代码。