单元测试是在软件开发过程中要进行的最低级别的测试活动,在单元测试活动中,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。单元测试不仅仅是作为无错编码的一种辅助手段在一次性的开发过程中使用,单元测试必须是可重复的,无论是在软件修改,或是移植到新的运行环境的过程中。因此,所有的测试都必须在整个软件系统给的生命周期中进行维护。
在代码完成的时候,对代码进行单元测试,不仅可以帮助我们快速定位错误,而且在修改了代码后,可以有助于我们进行回归测试。
回归测试
回归测试是指修改了旧代码后,重新进行测试以确定修改没有引入新的错误或导致其他代码产生错误。有以下特点:
(1)回归测试是指重复以前的全部或部分的相同测试;
(2)新加入测试的模组,可能对其他模组产生副作用,故须进行某些程度的回归测试;
(3)回归测试的重心,以关键性模组为核心;
大型软件系统的开发是一个很复杂的过程,其中因为人的因素而所产生的错误非常多,因此软件在开发过程必须要有相应的质量保证活动,而软件测试则是保证质量的关键措施。正像软件熵(software entropy)所描述的那样:一个程序从设计很好的状态开始,随着新的功能不断地加入,程序逐渐地失去了原有的结构,最终变成了一团乱麻。测试的目的说起来其实很简单也极具吸引力,那就是写出高质量的软件并解决软件熵这一问题。
软件测试作为一种系统工程,涉及到整个软件开发过程的各个方面,需要管理人员、设计人员、开发人员和测试人员的共同努力。作为软件开发过程中的主要力量,现今的程序员除了要编写实现代码外,还承担着单元测试这一艰巨任务,因此必须采用新的工作模式:
(1)编写和维护一套详尽的单元测试用例;
(2)先构造单元测试和验收测试用例,然后再编写代码;
(3)根据构造的测试用例来编写代码;
单元测试负责对最小的软件设计单元(模块)进行验证,它使用软件设计文档中对模块的描述作为指南,对重要的程序分支进行测试以发现模块中的错误。由于软件模块并不是一个单独的程序,为了进行单元测试还必须编写大量额外的代码,从而无形中增加了开发人员的工作量,目前解决这一问题比较好的方法是使用测试框架。
使用Python语言的开发人员可以使用Steve Purcell编写的PyUnit作为单元测试框架,通过将单元测试融合到PyUnit这一测试框架里,Python程序员可以更容易地增加、管理、执行测试用例,并对测试结果进行分析。此外,使用PyUnit还可以实现自动单元测试(回归测试)。
class Widget:
def __init__(self, size = (40, 40)):
self._size = size
def getSize(self):
return self._size
def resize(self, width, height):
if width 0 or height < 0:
raise ValueError, "illegal size"
self._size = (width, height)
def dispose(self):
pass
from widget import Widget
import unittest
# 执行测试的类
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget()
def tearDown(self):
self.widget = None
def testSize(self):
self.assertEqual(self.widget.getSize(), (40, 40))
# 构造测试集
def suite():
suite = unittest.TestSuite()
suite.addTest(WidgetTestCase("testSize"))
return suite
# 测试
if __name__ == "__main__":
unittest.main(defaultTest = 'suite' )
class ServiceTestCase(unittest.TestCase):
def setUp(self):
self.service = SERVICE()
def tearDown(self):
self.service = None
def testCat(self):
name = "img"
self.assertEquals(self.service.cat("", "server", name), 0)
self.assertEquals(self.service.cat("", "lb", name), 0)
def testRm(self):
name = "img"
self.assertEquals(self.service.rm("", "server", name), 0)
self.assertEquals(self.service.rm("", "lb", name), 0)
def suite():
suite = unittest.makeSuite(ServiceTestCase)
return unittest.TestSuite(suite)
if __name__ == '__main__':
unittest.main()
软件测试中最基本的组成单元是测试用例(test case),PyUnit使用TestCase类来表示测试用例,并要求所有用于执行测试的类都必须从该类继承。TestCase子类实现的测试代码应该是自包含(self contained)的,也就是说测试用例既可以单独运行,也可以和其它测试用例构成集合共同运行。
TestCase在PyUnit测试框架中被视为测试单元的运行实体,Python程序员可以通过它派生自定义的测试过程与方法(测试单元),利用Command和Composite设计模式,多个TestCase还可以组合成测试用例集合。PyUnit测试框架在运行一个测试用例时,TestCase子类定义的setUp()、runTest()和tearDown()方法被依次执行,最简单的测试用例只需覆盖runTest()方法来执行特定的测试代码就可以了。在采用PyUnit这一单元测试框架后,用于测试的代码做了相应的改动:
(1)用import语句引入unittest模块;
(2)让所有执行测试的类都继承于TestCase类,可以将TestCase看成是对特定类进行测试的方法的集合。
(3)在setUp()方法中进行测试前的初始化工作,并在tearDown()方法中执行测试后的清除工作,setUp()和tearDown()都是TestCase类中定义的方法。
(4)在testSize()中调用assertEqual()方法,对Widget类中getSize()方法的返回值和预期值进行比较,确保两者是相等的,assertEqual()也是TestCase类中定义的方法。
(5)提供名为suite()的全局方法,PyUnit在执行测试的过程调用suit()方法来确定有多少个测试用例需要被执行,可以将TestSuite看成是包含所有测试用例的一个容器。