引言

  前面的文章介绍了如何在 Python 的 Unittest 框架中来使用 ddt 实现数据驱动的自动化测试。

在了解了 ddt 的使用后,你是否有过如下疑问:

  • ddt 是如何把你的测试数据转换传给你的测试用例?

  • 当你的一组数据有多个参数时,ddt 是如何 unpack 的?

  • 当你有多组数据时,ddt 拆分测试用例是如何命名的?

主题:今天分享的内容是--探索 ddt 实现数据驱动的秘密。

  通过阅读ddt 源码,我们不难发现其实 ddt 的实现核心就是@ddt(cls)这个装饰器,而这个装饰器的核心代码是 wrapper这个类函数,下面我直接把 wrapper 的源码贴上来,大家一起看看:

def wrapper(cls):# 先遍历被装饰类的name, 和func# 对于func,先看被装饰的是DATA_ATTR还是FILE_ATTRfor name, func in list(cls.__dict__.items()):# 如果被装饰的是DATA_ATTRif hasattr(func, DATA_ATTR):#获取@data提供数据的index和内容并且遍历它们for i, v in enumerate(getattr(func, DATA_ATTR)):# 重新生成新的测试函数名,这个函数名会展示在测试报告中test_name = mk_test_name(name,getattr(v, "__name__", v),i,fmt_test_name)test_data_docstring = _get_test_data_docstring(func, v)# 如果类函数被@unpack装饰if hasattr(func, UNPACK_ATTR):# 如果提供的数据是tuple或者listif isinstance(v, tuple) or isinstance(v, list):# 则添加一个case到测试类中# list或tuple传不定数目的值, 用*v即可。add_test(cls,test_name,test_data_docstring,func,*v)else:# unpack dictionary# 添加一个case到测试类中# dict中传不定数目的值,用**vadd_test(cls,test_name,test_data_docstring,func,**v)else:# 如不需要unpack,则直接添加一个case到测试类add_test(cls, test_name, test_data_docstring, func, v)# 删除原来的测试类delattr(cls, name)# 如果被装饰的是file_dataelif hasattr(func, FILE_ATTR):# 获取file的名称file_attr = getattr(func, FILE_ATTR)# 根据process_file_data解析这个文件# 在解析的最后,会调用mk_test_name生成多个测试用例process_file_data(cls, name, func, file_attr)# 测试用例生成后,会删除原来的测试用例delattr(cls, name)return cls

  

来分析下这段代码, 对于每一个被 @ddt 装饰的测试类,ddt 首先去遍历测试类的自有属性,从而得出这个测试类有哪些测试方法,这部分主要靠这条语句:

# wrapper源码第4行
for name, func in list(cls.__dict__.items()):

然后,ddt 去判断所有的 func(即类函数)里,有没有装饰器 @data 或者 @file_data,主要靠这两条语句:

# 被@data装饰, wrapper源码第6行
if hasattr(func, DATA_ATTR):
# 被file_data 装饰,wrapper源码第47行
elif hasattr(func, FILE_ATTR):

接着程序会进入两条分支:被 @data 装饰,即由 ddt 直接提供数据;被 @file_data 装饰,即数据由外部文件提供。

1.被 @data 装饰,即由 ddt 直接提供数据
  如果数据是直接通过 @data 提供的,那么为每一组数据新生成一个测试用例名称。

# 在本例中, i, v的第一次循环,值为
# i:0 v:['Testing', 'Testing']
# wrapper源码第8行
for i, v in enumerate(getattr(func, DATA_ATTR)):test_name = mk_test_name(name,getattr(v, "__name__", v),i,fmt_test_name)

  test_name 生成使用的是函数 mk_test_name。

  注意:ddt 在此时实现了把你的测试数据转换传给你的测试用例。其实不是通过传递,而是通过把测试数据拆分,并且生成新测试用例的方式来达成的。

而在函数 mk_test_name 里,ddt 更是把原来的测试函数通过特定的规则,拆分成不同的测试函数。

test_name = mk_test_name(name,getattr(v, "__name__", v),i,fmt_test_name)

  mk_test_name 的参数里:

  • name 是原测试函数的名字

  • v 是我们的一组测试数据

  • i 是这组数据的 index

  fmt_test_name 指定新的 test 函数的名字的格式,这个格式是按照原来测试函数名 index 第一个测试数据_第二个测试数据这样的格式。

  例如,我们的测试数据 ['Testing','Testing'] 会被转换成test_baidu_search_1_['Testing', 'Testing']',但是由于符号 '[' 和 '' 以及 ',' 是不合法的字符,故会被 '_' 替换,故最终新生成的测试用例名是test_baidu_search_1___Testing____Testing__ 这块的逻辑在函数 mk_test_name 的最后两行:

# ddt内容函数mk_test_name,test_name处理逻辑如下
test_name = "{0}_{1}_{2}".format(name, index, value)
return re.sub(r'\W|^(?=\d)', '_', test_name)

紧接着,ddt 又去查找你的测试类函数,看它有没有被 @unpack 装饰。如果有,就意味着我们的测试类函数有多个参数,这个时候就需要把我们的测试数据 unpack,这样我们的测试类函数的各个参数才能接收到传入的值。

  这样,ddt 把上一步生成的 test_name 和刚刚 unpack 的值(数据是 list、tuple,还是 dictionary,决定了 unpack 采用 *v 还是 **v),通过 add_test 来新生成一个测试用例,注册到我们的测试类下面,所有这些动作是在下面这段代码里完成的。

# wrapper源码里的18行到43行
if hasattr(func, UNPACK_ATTR):if isinstance(v, tuple) or isinstance(v, list):add_test(cls,test_name,test_data_docstring,func,*v)else:# unpack dictionaryadd_test(cls,test_name,test_data_docstring,func,**v)
else:add_test(cls, test_name, test_data_docstring, func, v)

注意:
  这个时候测试类中是多了测试函数的,多了多少个,要取决于 ddt 提供的测试数据的组数,有几组就生成几个测试用例,并且都注册到原测试类中去;

  unpack 其实就是为了把一个测试用例的多个测试数据全部传入新生成的测试函数中去,这些测试数据和测试函数的参数一一对应。

  最后,ddt 会把最初的那个原始测试类方法给删除(因为原测试函数已经根据各组数据变成了新的测试函数)。

# wrapper源码45行
delattr(cls, name)

  通过这样的方式,ddt 根据测试数据的组数,通过函数 mk_test_name 生成多组测试用例,并通过 add_test 函数注册到 unittest的TestSuite 里去。

2.被 @file_data 装饰,即数据由外部文件提供
  如果测试函数被 @file_data 装饰,ddt 则会先获取 file_data 里的数据文件名称,然后通过函数 process_file_data 里进行下一步处理。

# wrapper源码的第49到52行
file_attr = getattr(func, FILE_ATTR)
process_file_data(cls, name, func, file_attr)

  看起来只有短短的两行,其实 ddt 在函数 process_file_data 内部做了很多操作。

  首先 ddt 会先拿到我们提供的数据文件的绝对地址,并通过后缀名判断它是 yaml 文件还是 json 文件,然后分别调用 yaml 或者 json 的 load 方法拿到文件里提供的数据。

  拿到数据后,最终也是通过 mk_test_name 函数和 add_test 函数,生成多条测试用例,并且注册到 unittest 的 TestSuite 里去。

最后一样是删除原来的测试函数:

# wrapper源码54行
delattr(cls, name)

这就是 ddt 的整个实现逻辑了。

总结

  DDT 的源代码非常经典,代码行数不多,值得我们深读。仔细琢磨并研究透 DDT 的源码,有助于你的测试开发技术提升。建议用单步调试的方式,结合今天分享的内容,边执行测试代码边走读 DDT 代码,这样将更有助于你加深对 DDT 原理的理解。

欢迎关注【无量测试之道】公众号,回复【领取资源】,
Python编程学习资源干货、
Python+Appium框架APP的UI自动化、
Python+Selenium框架Web的UI自动化、
Python+Unittest框架API自动化、

资源和代码 免费送啦~
文章下方有公众号二维码,可直接微信扫一扫关注即可。

备注:我的个人公众号已正式开通,致力于测试技术的分享,包含:大数据测试、功能测试,测试开发,API接口自动化、测试运维、UI自动化测试等,微信搜索公众号:“无量测试之道”,或扫描下方二维码:

 添加关注,让我们一起共同成长!

Unittest 之 DDT 的原理解析相关推荐

  1. Spark Shuffle原理解析

    Spark Shuffle原理解析 一:到底什么是Shuffle? Shuffle中文翻译为"洗牌",需要Shuffle的关键性原因是某种具有共同特征的数据需要最终汇聚到一个计算节 ...

  2. 秋色园QBlog技术原理解析:性能优化篇:用户和文章计数器方案(十七)

    2019独角兽企业重金招聘Python工程师标准>>> 上节概要: 上节 秋色园QBlog技术原理解析:性能优化篇:access的并发极限及分库分散并发方案(十六)  中, 介绍了 ...

  3. Tomcat 架构原理解析到架构设计借鉴

    ‍ 点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 Tomcat 架构原理解析到架构设计借鉴 Tomcat 发展这 ...

  4. 秋色园QBlog技术原理解析:性能优化篇:数据库文章表分表及分库减压方案(十五)...

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...

  5. CSS实现元素居中原理解析

    原文:CSS实现元素居中原理解析 在 CSS 中要设置元素水平垂直居中是一个非常常见的需求了.但就是这样一个从理论上来看似乎实现起来极其简单的,在实践中,它往往难住了很多人. 让元素水平居中相对比较简 ...

  6. 秋色园QBlog技术原理解析:Web之页面处理-内容填充(八)

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...

  7. 秋色园QBlog技术原理解析:UrlRewrite之无后缀URL原理(三)

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 本节,将从 ...

  8. Android之Butterknife原理解析

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! ##前言 Butterknife是一个专注于Android系统的View注入框架, ...

  9. 【深度学习】谷歌大脑EfficientNet的工作原理解析

    [深度学习]谷歌大脑EfficientNet的工作原理解析 文章目录 1 知识点准备1.1 卷积后通道数目是怎么变多的1.2 EfficientNet 2 结构2.1 方式2.2 MBConv卷积块2 ...

最新文章

  1. SRWebSocket源码浅析(上)
  2. ASP.NET Core 1.0 使用 MySQL for EF Core 1.0 (.NET Core 1.0)
  3. 使用Navicat创建数据库,外键出现错误ERROR 1005: Can't create table (errno: 121)
  4. 人脸识别有风险,美国全面禁止,可为什么中国却全面推广?
  5. PWA(Progressive Web App)入门系列:(五)Web Worker
  6. SDO_GEOMETRY Object Type
  7. 简述台式计算机创建家庭组的步骤,如何创建和设置家庭组
  8. C#调用Matlab生成的dll方法
  9. 聚焦IT系统稳定性保障服务 PerfMa笨马网络完成亿元级B轮融资
  10. 速更新!流行的开源邮件客户端 Mozilla Thunderbird 91.3修复多个高危缺陷
  11. [CODEVS1911] 孤岛营救问题(分层图最短路)
  12. 挑战《IT我最大》Windows 7由你秀 活动的丑恶行径
  13. Java的JDK以及maven环境变量配置
  14. 对Javascript“闭包”的简单理解
  15. EXT2/EXT3文件系统
  16. markdown中让表格居中的写法
  17. Dynamics CRM 365零基础入门学习(五)权限管理
  18. 如何快速打开控制面板?如何让控制面板在桌面显示?
  19. JavaSE(9)-细节狂魔:OOP之继承多态?20K字长篇看完,有手就行
  20. win7家庭版计算机管理没有本地用户和组,Win7找不到本地用户和组选项的解决方法...

热门文章

  1. 神州泰岳确认将获飞信合约 称续签只是时间问题
  2. 不定积分-分子分母次数较高
  3. Camtasia Studio mac下载V2020汉化补丁中文版视频录制及剪辑工具软件
  4. linux 证书有效期查看
  5. 超低功耗寝室指纹锁(电池供电)
  6. 《微信小程序案例10》后端服务器与mysql数据库与前端微信小程序
  7. ET200SP CAD授人以渔
  8. wordpress最佳架构_2020年最受欢迎和最佳WordPress主题(专家精选)
  9. 下载Visual Studio 2019离线安装包
  10. 电脑版网页设计布局初级