作者 | 豌豆花下猫
责编 | 郭芮
在 Python 中 , 实现参数化测试的几个库 , 是如何做到把一个方法变成多个方法 , 并且将每个方法与相应的参数绑定起来的呢?
我们再提炼一下:在一个类中 , 如何使用装饰器把一个类方法变成多个类方法(或者产生类似的效果)?
# 带有一个方法的测试类
classTestClass:
deftest_func(self):
pass
【如何将 Python 的一个类方法变为多个方法?】# 使用装饰器 , 生成多个类方法
classTestClass:
deftest_func1(self):
pass
deftest_func2(self):
pass
deftest_func3(self):
pass
Python 中装饰器的本质就是移花接木 , 用一个新的方法来替代被装饰的方法 。在实现参数化的过程中 , 我们介绍过的几个库到底用了什么手段/秘密武器呢?
ddt 如何实现参数化?
先回顾一下上篇文章中 ddt 库的写法:
importunittest
fromddt importddt,data,unpack
@ddt
classMyTest(unittest.TestCase):
@data(( 3, 1), ( -1, 0), ( 1.2, 1.0))
@unpack
deftest(self, first, second):
pass
ddt 可提供 4 个装饰器:1 个加在类上的 @ddt , 还有 3 个加在类方法上的 @data、@unpack 和 @file_data(前文未提及) 。
先看看加在类方法上的三个装饰器的作用:
# ddt 版本(win):1.2.1
defdata(*values):
globalindex_len
index_len = len(str(len(values)))
returnidata(values)
defidata(iterable):
defwrApper(func):
setattr(func, DATA_ATTR, iterable)
returnfunc
returnwrapper
defunpack(func):
setattr(func, UNPACK_ATTR, True)
returnfunc
deffile_data(value):
defwrapper(func):
setattr(func, FILE_ATTR, value)
returnfunc
returnwrapper
它们的共同作用是在类方法上 setattr 添加属性 。至于这些属性在什么时候使用?下面看看加在类上的 @ddt 装饰器源码:
文章插图
文章插图
第一层 for 循环遍历了所有的类方法 , 然后是 if/elif 两条分支 , 分别对应 DATA_ATTR/FILE_ATTR , 即对应参数的两种来源:数据(@data)和文件(@file_data) 。
elif 分支有解析文件的逻辑 , 之后跟处理数据相似 , 所以我们把它略过 , 主要看前面的 if 分支 。这部分的逻辑很清晰 , 主要完成的任务如下:
- 遍历类方法的参数键值对
- 根据原方法及参数对 , 创建新的方法名
- 获取原方法的文档字符串
- 对元组和列表类型的参数作解包
- 在测试类上添加新的测试方法 , 并绑定参数与文档字符串
文章插图
分析源码 , 可以看出 , @data、@unpack 和 @file_data 这三个装饰器主要是设置属性并传参 , 而 @ddt 装饰器才是核心的处理逻辑 。
这种将装饰器分散(分别加在类与类方法上) , 再组合使用的方案 , 很不优雅 。为什么就不能统一起来使用呢?后面我们会分析它的难言之隐 , 先按下不表 , 看看其它的实现方案是怎样的?
parameterized 如何实现参数化?
先回顾一下上篇文章中 parameterized 库的写法:
importunittest
fromparameterized importparameterized
classMyTest(unittest.TestCase):
@parameterized.expand([( 3, 1), ( -1, 0), ( 1.5, 1.0)])
deftest_values(self, first, second):
self.assertTrue(first > second)
它提供了一个装饰器类 @parameterized , 源码如下(版本 0.7.1) , 主要做了一些初始的校验和参数解析 , 并非我们关注的重点 , 略过 。
文章插图
我们主要关注这个装饰器类的 expand 方法 , 它的文档注释中写到:
A "brute force" method of parameterizing test cases. Creates new test cases and injects them into the namespace that the wrapped function is being defined in. Useful for parameterizing tests in subclasses of 'UnitTest', where Nose test generators don't work.关键的两个动作是:“creates new test cases(创建新的测试单元)”和“inject them into the namespace…(注入到原方法的命名空间)” 。
推荐阅读
- 如何在Mac下生成ssh密钥?
- 这一次,让你完全理解 HTTPS 到底是如何做到数据传输安全的
- python常用可视化工具
- 程序员告诉你微信营销号如何防封、解封
- 黑客是如何控制你手机的?出现这几种情况,你的手机可能已中招
- 使用Python「秒开」100GB+数据
- 一文教会你如何申请ios开发者账号
- 如何学好陈氏小架太极拳
- 水晶吊灯怎么样 如何选购水晶吊灯
- 房屋阳台风水 如何打造阳台好风水