构建测试代码

测试用例是单元测试的基础模块——它在每个场景都需要被创建,用来检查每个场景的正确性。在unittest中,测试用例通过unittest.TestCase类的实例来实现。为了创建测试用例,你必须通过子类继承TestCase或者FunctionTestCase。 TestCase实例的测试代码应该完全独立,这样它就可以单独运行,也可以和其它任意的测试用例组合来运行。 最简单的TestCase子类仅仅包含一个runTest()方法(即以test开头的方法),该方法只是为了执行特定的测试代码。

  1. import unittest
  2. class DefaultWidgetSizeTestCase(unittest.TestCase):
  3. def test_default_widget_size(self):
  4. widget = Widget('The widget')
  5. self.assertEqual(widget.size(), (50, 50))

请注意,为了测试一些东西,我们可以使用TestCase基类提供的assert*()方法。如果测试失败,它将会抛出异常,并且unittest会将该测试用例标识为失败。其他的异常也同样会使测试用例被标识为失败。 测试用例是可以多条的,并且他们的setup可以是重复的。庆幸的是,我们可以通过setUp()方法重用代码,测试框架在运行每个测试用例时自动调用:

  1. import unittest
  2. class WidgetTestCase(unittest.TestCase):
  3. def setUp(self):
  4. self.widget = Widget('The widget')
  5. def test_default_widget_size(self):
  6. self.assertEqual(self.widget.size(), (50,50),
  7. 'incorrect default size')
  8. def test_widget_resize(self):
  9. self.widget.resize(100,150)
  10. self.assertEqual(self.widget.size(), (100,150),
  11. 'wrong size after resize')

注意:测试执行的顺序是根据测试方法的名称按字符串顺序来确定的。

执行测试的时候,如果setUp()方法抛出了异常,它会认为测试遇到错误,并且测试方法将不会执行。

同样,在测试方法之后,我们提供了tearDown()方法:

  1. import unittest
  2. class WidgetTestCase(unittest.TestCase):
  3. def setUp(self):
  4. self.widget = Widget('The widget')
  5. def tearDown(self):
  6. self.widget.dispose()

如果setUp()执行成功,无论测试方法是否执行成功,都会执行tearDown()。

一个这样的测试代码所需的工作环境称为一个fixture。通过独有的fixture创建测试用例,用来执行每个特定的测试方法。因此,每次测试都调用一次setUp(),tearDown()和__init()__方法。

建议你根据测试的功能使用TestCase将测试用例组合在一起执行。unittest为此提供了一套机制:测试套件,由unittest的TestSuite类实现。在多数情况下,通过调用unittest.main()可以执行所有模块的测试用例。

但是,如果要自定义测试套件,可以自己设计定义:

  1. def suite():
  2. suite = unittest.TestSuite()
  3. suite.addTest(WidgetTestCase('test_default_widget_size'))
  4. suite.addTest(WidgetTestCase('test_widget_resize'))
  5. return suite
  6. if __name__ == '__main__':
  7. runner = unittest.TextTestRunner()
  8. runner.run(suite())

你可以将测试用例和测试套件与被测试代码定义在相同的模块中(如widget.py),但是将测试代码放在单独的模块中有几个优点,例如test_widget.py:

  • 测试模块可以通过命令行独立运行。
  • 测试代码可以很方便地从发布代码中分离。
  • 在没有必要的情况下,不必为了适应被测试的代码而频繁地更改测试代码。
  • 相对于被测试代码,测试代码不应被频繁修改。
  • 被测试代码可以更容易被重构。
  • 既然用C语言编写的测试模块也在单独模块中,为什么不保持它的一致性?
  • 如果测试策略改变,也无需修改被测试的源代码。