软件测试中最基本的组成单元是测试用例,unittest框架通过TestCase类来构建测试用例,并要求所有自定义的测试类都必须继承该类,它是所有测试用例的基类,传入一个测试方法名,将返回一个测试用例实例。TestCase的子类中实现测试用例的代码既可以单独运行,也可以和其他测试用例构成测试用力集,然后批量执行。
TestCase作为unittest单元测试框架中测试单元的运行实体,单元测试脚本编写员,可以通过它派生出自定义的测试过程与方法。TestCase子类从父类继承的几个特殊的方法,在测试用例执行时均会被依次执行。
TestCase类中定义的几个特殊方法如下。
(1)setUp():每个测试方法运行前运行,测试前的初始化工作
(2)tearDown():每个测试方法运行结束后运行,测试后的清理工作
(3)setUpClass():所有的测试方法运行前运行,单元测试前准备,必须使用@classmethod装饰器进行修饰,setUp()函数之前执行,整个测试过程只执行一次。
(4)tearDownClass():所有的测试方法运行结束后执行,单元测试后期清理,必须使用@classmethod装饰器进行修饰,teardown()之后执行,整个测试过程只执行一次。
最简单的测试用例只需要通过覆盖runTest()方法来执行自定义的测试代码,这种称为静态方法,如下面的实例:
import unittest
import random
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
#初始化递增序列
self.seq = range(10)
def runTest(self):
#从序列seq中随机取一个元素
element = random.choice(self.seq)
#验证随机元素属于列表中
self.assertTrue(element in self.seq, 'aaaa')
class TestDictValueFormatFunctions(unittest.TestCase):
def setUp(self):
#随机打乱原seq的顺序
random.shuffle(self.seq)
self.seq.sort()
self.assertEqual(self.seq, range(10), 'qwe')
#验证执行函数时抛出了异常
self.assertRaises(TypeError,random.shuffle,(1,2,3))
if __name__ == '__main__':
unittest.main()
如果要在unittest单元测试框架中构造上述测试类的一个实例,需要按testCase=TestSequenceFunctions()这行代码实现,
- 并且一个测试用例通常只能对测试模块中一个方法进行单元测试,
- 如果要对测试模块中多个方法进行单元测试,就需要构造多个执行测试类,如上例中的TestSequenceFunctions类和TestDictValueFormatFunctions类,
- 而对于同一测试模块,测试用例之间的可能有着相同的初始状态,如果还是采用上述方法就会出现很多冗余代码,并且还是一项费时的枯燥工作。
unittest框架针对这一问题,给出了动态的解决办法,脚本编写人员只需要写一个测试类来完成对整个测试模块的单元测试,而初始化工作直接在setUp()方法中完成,资源的释放等清理工作直接在tearDown()中完成即可,这种方法规定所有需要被执行的测试方法都以‘test’开头。
具体实例如下:
import unittest
#被测试类
class myclass(object):
@classmethod
def sum(cls,a,b):
return a+b
@classmethod
def sub(cls,a,b):
return a-b
class mytest(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""初始化类固件"""
print ("--------setUpClass" )
@classmethod
def tearDownClass(cls):
"""重构类固件"""
print ("--------tearDownClass")
#初始化工作
def setUp(self):
self.a = 3
self.b = 1
print ("--------setUp")
#退出清理工作
def tearDown(self):
print ("--------tearDown")
#具体的测试用例一定要以test开头
def testsum(self):
#断言两数之和的结果是否是4
res = 3/0
self.assertEqual(myclass.sum(self.a, self.b),4,'test sum fail')
def testsub(self):
#断言两数之差的结果是否为2
self.assertEqual(myclass.sub(self.a, self.b),2,'test sub fail')
if __name__ == '__main__':
unittest.main()
测试结果:
----setUpClass
--setUp
--tearDown
--setUp
--tearDown
----tearDownClass
..
------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
测试结果说明:
从输出结果来看,setUpClass()和tearDownClass()方法在整个测试类的运行过程中只被执行一次,而setUp()和tearDown()方法在每个测试方法执行前和执行后均被调用,被执行了多次。在测试结果输出中,出现了两个点(..),这表示测试类mytest中两个测试方法testsum()和testsub()均被成功执行,一个点表示一个测试方法。
更多说明:
(1)有时在测试结果输出中会出现“E”和“F”字符的情况,这说明测试用例执行失败或者发生了异常,下面分别介绍这两个字符出现的情况。
- 按下图修改程序,然后在执行脚本:
测试结果中出现了一个点(.),但出现了一个F,这表示mytest类中只有一个测试方法执行通过了,另一个失败了。因为3+1不等于3,所以断言失败了。执行结果如下图:
- 再按下图修改脚本程序,修改后再次执行程序:
执行结果如下图:
因为在testsum方法中增加了“res =3/0”,由于除数不能为零,所以程序执行到这里时,抛出ZeroDivisionError异常,但并没有捕获该异常,导致脚本值执行中断。 0
(2)动态方法不再覆盖runTest()方法,而是直接在测试类中编写多个测试方法。mytest类继承自unittest.Testcase类,同时重写了setUp()、setUpClass() 、tearDown()、tearDownClass()方法,并且定义了两个以‘test’开头的方法(主意必须以·test开头,中间可以插入_、等字符)。当然也可以同时在一个.py文件中编写多个自定义测试类,这些自定义测试类都必须继承unittest.TestCase类。