前言
在做自动化过程中,通过数据驱动主要是为了将用例数据和用例逻辑进行分离,提高代码的重用率以及方便用例后期的维护管理。很多小伙伴在使用unittest做自动化测试的时候,都是用的ddt这个模块来实现数据驱动的。也有部分小伙伴对ddt内部实现的源码比较感兴趣,前段时间小编在开发unttestreport的时候也写了一版数据驱动,使用的入口和ddt差不多,今天就给大家分析一下unittestreport中数据驱动(ddt),源码实现的过程!
一、快速使用
在给大家分析源码之前先给大家讲解一下unittestreport中的ddt如何使用!unittestreport.dataDriver模块中实现了三个使用方法,支持使用列表(可迭代对象)、json文件、yaml文件来生成测试用例,这边给大家介绍一下使用方法:
-
1、模块导入
from unittestreport.dataDriver import ddt, list_data
-
2、使用案例
-
第一步:使用ddt装饰测试用例类
-
第二步:根据使用的数据选择对应的方法进行驱动
-
第三步:运行上面用例文件,就会发现执行了三条测试用例
-
-
from unittestreport import ddt, list_data
-
@ddt
-
class TestClass(unittest.TestCase):
-
cases = [{'title': '用例1', 'data': '用例参数', 'expected': '预期结果'},
-
{'title': '用例2', 'data': '用例参数', 'expected': '预期结果'},
-
{'title': '用例3', 'data': '用例参数', 'expected': '预期结果'}]
-
@list_data(cases)
-
def test_case(self, data):
-
pass
-
二、源码分析
在上面的使用案例中我们使用了一个ddt的装饰器去装饰测试用例类,一个list_data的装饰器去装饰测试用例方法。
1、ddt
在上面的用例类前面我们用了一个@ddt这行代码的作用到底是什么呢?
# @ddt这个是装饰器的语法,这行代码的作用等同于 TestClass = ddt(TestClass)
我们来看一下ddt里面的源码
-
def ddt(cls):
-
for name, func in list(cls.__dict__.items()):
-
if hasattr(func, "PARAMS"):
-
for index, case_data in enumerate(getattr(func, "PARAMS")):
-
# 生成用例名称,
-
new_test_name = _create_test_name(index, name)
-
# 生成用例描述
-
if isinstance(case_data, dict) and case_data.get("title"):
-
test_desc = case_data.get("title")
-
elif isinstance(case_data, dict) and case_data.get("desc"):
-
test_desc = case_data.get("desc")
-
else:
-
test_desc = func.__doc__
-
func2 = _update_func(new_test_name, case_data, test_desc, func)
-
setattr(cls, new_test_name, func2)
-
else:
-
delattr(cls, name)
-
return cls
从上面的源码我们可以看出来,把测试类当成参数传入data之后,在内部做了一系列操作之后将测试类返回了出来。这一系列操作其实就是根据测试用例数据,创建测试用例方法添加到测试类中,代码中重要步骤如下图描述所示。
对于上面遍历,判断方法是否拥有PARAMS属性,这个PARAMA属性是怎么来的呢?PARAMA属性是@list_data(cases)的时候添加的,接下来我们来看一下list_data的源码
2、list_data
上面案例中@list_data(cases)这行代码的作用等同于test_case = list_data(cases)(test_case),我们来看一下源码。
-
def list_data(datas):
-
"""
-
:param datas: 测试数据
-
:return:
-
"""
-
def wrapper(func):
-
setattr(func, "PARAMS", datas)
-
return func
-
return wrapper
'
运行
运行
通过源码我们可以发现执行@list_data(cases)的时候只做了一件事情,就是给测试用例方法添加了一个PARAMS属性。
通过上面的源码分析,我们来简单的讲一下,关于ddt实现的具体流程。
3、其他的内部实现方法
在ddt这个函数中我们调用了_create_test_name
方法和_update_func
这两个内部方法,那么这两个内部方法中分别做了什么事情呢?接下来分别来看一下
-
_create_test_name
:创建用例的方法名-
def _create_test_name(index, name):
-
if index + 1 < 10:
-
test_name = name + "_0" + str(index + 1)
-
else:
-
test_name = name + "_" + str(index + 1)
-
return test_name
可以看到该方法会根据传进来的index(用例数据的索引)和原用例名进行拼接,生成一个新的用例方法名。
-
-
_update_func
:创建一个新的用例方法-
def _update_func(new_func_name, params, test_desc, func, *args, **kwargs):
-
@wraps(func)
-
def wrapper(self):
-
return func(self, params, *args, **kwargs)
-
wrapper.__wrapped__ = func
-
wrapper.__name__ = new_func_name
-
wrapper.__doc__ = test_desc
-
return wrapper
从上面的代码可以看到,这个函数内部重写定义了一个函数,将原来的函数保存为了这个函数对象的属性,并设置了函数名,以及函数相关的文档字符串,最后将函数返回了。函数内部调研的还是原来的函数(测试用例方法)。
-
4、实现流程的分析
-
1、将所有的测试用例数据保存为测试方法的PARAMS属性。
-
2、遍历测试类的所有属性和方法。
-
3、判断遍历出来的属性或者方法是否拥有PARAMS属性。
-
4、如果拥有PARAMS属性,那么就遍历PARAMS中的测试数据。
-
5、每遍历出来一条数据,就创建一个测试方法,并将测试方法添加为测试类的类属性。
关于数据驱动实现的源码就给大家介绍到这里了,另附unittestreport模块详细的使用文档,里面有关于更多的uniitestreport扩展使用功能哦。
感谢每一个认真阅读我文章的人!!!
作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。
软件测试面试文档
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。