25.3.4 组织测试代码
单元测试的基本构建是测试用例,必须设置和检查每个场景的正确性。在unittest中,测试用例由unittest的TestCase类的实例表示。为了制作属于自己的测试用例,必须要编写TestCase的子类,或者使用FunctionTestCase。TestCase派生类的对象是一个可以完全运行单个测试方法的对象,以及可选的设置和整理代码。
TestCase实例的测试代码应该是完全自包含的,这样他可以独立运行或者与任意数量的其他测试用例组合运行。
最简单的TestCase子类将简单地重写runTest()方法以执行特定的测试代码。
import unittest
class DefaultWidgetSizeTestCase(unittest.TestCase):
def runTest(self):
widget = Widget('The widget')
self.assertEqual(widget.size(), (50, 50), 'incorrect default size')
请注意,这里我们为了测试某些内容,使用了TestCase基类提供的assert*()方法。如果测试失败,会抛出异常,然后unittest会将这个测试用例标记为失败。其他的异常将会被视为错误。者能够帮助你识别问题的所在:失败是由于不正确的结果引起的,比如预期结果返回一个6而实际结果返回的是3.错误则是由不正确的代码引起的。比如说ValueError、TypeError等。
运行测试用例的方法将会在之后介绍。现在我们构造一个测试用例的实例,我们调用它的构造函数而不带参数。
testCase = DefaultWidgetSizeTestCase()
现在这样的测试用例有很多,并且它们的设置可能是重复的。在前面的例子中,构建100个widget和构建一个widget意味着难看的复制。
幸运的是我们能够通过实现setUp()的方法来分解这些设置代码,当我们运行测试时,测试框架会自动调用这个方法。
import unittest
class SimpleWidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
class DefaultWidgetSizeTestCase(SimpleWidgetTestCase):
def runTest(self):
self.assertEqual(self.widget.size(), (50,50),
'incorrect default size')
class WidgetResizeTestCase(SimpleWidgetTestCase):
def runTest(self):
self.widget.resize(100,150)
self.assertEqual(self.widget.size(), (100,150),
'wrong size after resize')
如果setUp()方法在测试运行的时候抛出异常,框架则会认为测试发生了错误,然后runTest()方法将不会执行。
相似的,我们能够使用tearDown()方法,在runTest()方法运行之后整理:
import unittest
class SimpleWidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def tearDown(self):
self.widget.dispose()
self.widget = None
如果setUp()方法成功,无论runTest()成功或者失败,tearDown()方法都将在runTest()之后运行。
这种测试代码的工作环境叫做夹具(fixture)。
通常,很多小测试用例使用相同的夹具。在这种情况下,我们最终将SimpleWidgetTestCase继承为许多小的单方法类,如 DefaultWidgetSizeTestCase。这是非常耗时的,所以unittest提供了一个更简单的机制:
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def tearDown(self):
self.widget.dispose()
self.widget = None
def test_default_size(self):
self.assertEqual(self.widget.size(), (50,50),
'incorrect default size')
def test_resize(self):
self.widget.resize(100,150)
self.assertEqual(self.widget.size(), (100,150),
'wrong size after resize')
这里我们没有提供runTest()方法,但是新增了两个不同的测试方法。类实例现在将会运行一个test_*()方法,并且为每个实例分别创建和销毁self.widget。创建实例时,我们必须指定要运行的测试方法。我们通过在构造函数中传递方法名来完成此操作。
defaultSizeTestCase = WidgetTestCase('test_default_size')
resizeTestCase = WidgetTestCase('test_resize')
测试用例实例根据测试的功能分组在一起。unittest为此提供了一种机制:由unittest的TestSuite类表示的测试套件:
widgetTestSuite = unittest.TestSuite()
widgetTestSuite.addTest(WidgetTestCase('test_default_size'))
widgetTestSuite.addTest(WidgetTestCase('test_resize'))
为了便于运行测试,我们将在后面看到,最好在每个测试模块提供一个返回预构建测试套件的可调用对象:
def suite():
suite = unittest.TestSuite()
suite.addTest(WidgetTestCase('test_default_size'))
suite.addTest(WidgetTestCase('test_resize'))
return suite
或者
def suite():
tests = ['test_default_size', 'test_resize']
return unittest.TestSuite(map(WidgetTestCase, tests))
由于使用许多类似命名的测试函数创建TestCase子类是一种常见模式,因此unittest提供了一个TestLoader类,可用于自动创建测试套件并使用单个测试填充测试套件。例如:
suite = unittest.TestLoader().loadTestsFromTestCase(WidgetTestCase)
将创建一个将运行WidgetTestCase.test_default_size()和WidgetTestCase.test_resize的测试套件。TestLoader使用'test'方法名称前缀自动识别测试方法。
请注意,各种测试用例的运行顺序是通过对测试函数名称与字符串的内置排序进行排序来确定的。
通常希望将测试用例集合在一起,以便依次运行整个系统的测试。这很容易,因为TestCase实例可以添加到TestSuite一样:
suite1 = module1.TheTestSuite()
suite2 = module2.TheTestSuite()
alltests = unittest.TestSuite([suite1, suite2])
您可以将测试用例和测试套件的定义放置在与测试代码相同的模块中,但将测试代码放置在单独的模块中有几个优点,例如test_widget.py:
-测试模块可以从命令行单独运行。
-测试代码可以更加容易的从提供的代码中分离出来。
-测试代码应该比它测试的代码更加频繁地被修改。
-测试过的代码可以更容易的重构。
-用C编写的模块测试必须在不同的模块中,为什么不统一呢?
-如果测试策略发生变化,则不需要更改源代码。