Python 数据驱动 unittest + ddt
一数据驱动测试的含义:
在百度百科上的解释是:
数据驱动测试,即黑盒测试(Black-box Testing),又称为功能测试,是把测试对象看作一个黑盒子。利用黑盒测试法进行动态测试时,需要测试软件产品的功能,不需测试软件产品的内部结构和处理过程。数据驱动测试注重于测试软件的功能性需求,也即数据驱动测试使软件工程师派生出执行程序所有功能需求的输入条件。
在微软网站上的解释是:
数据驱动的单元测试是为数据源中的每一行重复运行的一种单元测试。
数据驱动的单元测试的常用情况是使用多个输入值测试 API。不是编写调用 API 的多个单元测试(每个单元测试均具有一组新的输入),也不是在单元测试中创建一个数组并使用循环代码,而是可以编写执行 API 的单个单元测试方法。然后可以从数据库表的行中进行数据检索以便传递给该测试方法的连续调用。可以使用此技术测试由不同用户(每个用户具有不同角色)使用的应用程序。对于每个用户,数据源中的一行将根据角色指示预期响应。然后,该测试将通过针对每个用户运行功能,对该应用程序进行测试,并验证产生的响应是否与预期响应一致。
在测试工作中,针对某一API接口,或者某一个用户界面的输入框,需要设计大量相关的用例,每一个用例包含实际输入的各种可能的数据。通常的做法是,将测试数据存放到一个数据文件里,然后从数据文件读取,在脚本中循环输入测试数据,并对结果进行验证。而该实现方案,按照微软网站的解释,并不属于数据驱动测试。那么什么是数据驱动测试呢?接下来我们一起看一个实例,便一清二楚了。
数据驱动测试的环境准备:
- 测试框架的选择:在这里选择的是Python开发语言,测试框架使用Unittest和DDT相结合的方式。
在python中, unittest测试框架如下:
import unittest class MyTestCase(unittest.TestCase): def setUp(self): ''' testcase init ... :return: ''' print('setup') def test_sth(self): ''' must use test_*** :return: ''' print('test something') def tearDown(self): ''' testcase release ... :return: ''' print('teardown') if __name__ == '__main__': unittest.main()
Unittest框架包含一个test Fixture,test Fixture由三部分组成,setup,testcase和teardown。Setup过程,是测试用例执行前的初始化过程,teardown过程,是在测试用例执行后,对资源进行释放与回收的过程;而testcase是具体的测试用例。
- 引入ddt框架,需要从ddt官网安装ddt的模块。安装ddt模块后,使用测试驱动框架后,只需要以下几行代码:
import unittest import ddt @ddt.ddt class MyTestCase(unittest.TestCase): def setUp(self): ''' testcase init ... :return: ''' print('setup') @ddt.data(['t1' ,'r1'] , ['t2' , 'r2']) @ddt.unpack def test_sth(self , testdata , expectresult): ''' must use test_*** :return: ''' print('test something') print(colored('%s - %s'%(testdata , expectresult), 'blue')) def tearDown(self): ''' testcase release ... :return: ''' print('teardown') print() if __name__ == '__main__': unittest.main()
首先在头部导入ddt;其次在测试类前声明使用ddt(@ddt.ddt); 第三步,在测试方法前,使用@ddt.data和@unpack进行修饰。而测试数据,在data中进行填加,该demo,有两条测试数据,每条测数据有两个字段,第一个是测试数据,第二个是期望的测试结果。从代码中,可以看到,我们在测试用例的实体中,并未使用循环。那么执行后,是什么样的效果呢?
setup test something t1 - r1 teardown .setup test something t2 - r2 teardown . ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK
可以看到,测试结果有两条测试用例被执行,而非一条测试用例。也就是测试框架,自动将测试数据分在两条测试用例里来执行。通过调试的print语句,我们也可以看到,两条用例执行时的输出情况。
数据驱动测试的实例:
上述,我们对数据驱动的使用进行了介绍,接下来,我们一起来看一下在工作中是如何应用的。目前数据驱动,被用在了搜索app的自动化测试执行过程中。应用的场景是,测试时需要在app里打开不同的垂搜,并验证对应的垂搜页面被正常打开,此时需要传入两个字段,一是垂搜的名称,二是打开垂搜后,页面里的特殊标识,在此,我们选择的是HTML5页面中的xpath。具体的代码示例如下:
在未使用数据驱动框架之前,测试这个场景时,每个垂搜单独实现了一条测试用例,这里面有18个垂搜,因此之前对应的是18条测试用例,可见重复的代码量很高。在使用数据驱动框架后,只需要实现一条测试用例就可以满足需求,同时需要编写的代码量很低。关于数据驱动,网上也有很多其他的解决方案,例如将测试数据写到excel中,再从excel中读取数据。而这一过程,需要编写一定的代码,使用框架后,完全省去了这一过程。
import unittest from ddt import ddt, data, unpack import csv from pprint import pprint def add(a, b): print('*'*5 ,a, b) c = a + b print('c' ,c) return c def addstr(a, b): c = a + b return c def get_csv_data(): value_rows = [] with open('./mfile.csv') as f: f_csv = csv.reader(f) # 忽略表头 next(f_csv) for r in f_csv: value_rows.append([ int(i) for i in r]) pprint(value_rows) return value_rows def write_csv_data(): pass @ddt class Test(unittest.TestCase): @data((1, 1, 2), (1, 2, 3)) @unpack def test_addnum(self, a, b, expected_value): self.assertEqual(add(a, b), expected_value) @data(*get_csv_data()) @unpack def test_addstr(self, a, b, expected_value): self.assertEqual(add(a, b), expected_value) if __name__ == "__main__": suite = unittest.TestLoader().loadTestsFromTestCase(Test) unittest.TextTestRunner(verbosity=2).run(suite)
- 代码复用率高。同一测试逻辑编写一次,可以被多条测试数据复用,提高了测试代码的复用率,同时可以提高测试脚本的编写效率。
- 异常排查效率高。测试框架依据测试数据,每条数据生成一条测试用例,用例执行过程相互隔离,在其中一条失败的情况下,不会影响其他的测试用例。
- 代码的可维护性高。清晰的测试框架,利于其他测试工程师阅读,提高了代码的可维护性。