4.组织测试代码
单元测试的基本组成部分是测试用例——单个必须建立并检查正确性的方案。在unittest中,测试用例都是继承了unittest.TestCase
类的实例。要创建你自己的测试用例,你必须编写TestCase的子类或者使用FunctionTestCase类。
TestCase的实例的测试代码应完全自包含,以便可以单独运行,也可以与任意数量的其他测试用例组合运行。
最简单的TestCase子类将简单地实现一个测试方法,比如以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()
的方法来批量化实现建立,测试框架会在我们每次运行单独的测试时自动调用该方法:
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()
方法抛出一个异常,unittest框架会认为测试以及遇到了一个错误,测试方法都不会被执行。
同样的,我们提供了一个在测试方法运行后整理的tearDown()
方法:
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def tearDown(self):
self.widget.dispose()
如果setUp()
方法成功了,那么不论测试方法成功还是失败,tearDown()
方法都会运行。
测试代码的这种工作环境被称为测试脚手架test fixture。创建一个新的TestCase实例,用来作为用于执行每个单独测试方法的唯一测试脚手架。因此,setUp()
方法、tearDown()
方法和__init__()
在每个测试都会被调用。(每个测试应该指的每个TestCase实例)
推荐你根据测试的功能使用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语言编写的模块的测试无论如何都必须放在单独的模块中,所以这是为什么呢?
- 如果测试策略改变了,也不需要改变源代码