如何将 Python 的一个类方法变为多个方法?( 二 )


关于第一点 , 它跟 ddt 是相似的 , 只是一些命名风格上的差异 , 以及参数的解析及绑定不同 , 不值得太关注 。

如何将 Python 的一个类方法变为多个方法?

文章插图
最不同的则是 , 怎么令新的测试方法生效?
parameterized 使用的是一种“注入”的方式:
如何将 Python 的一个类方法变为多个方法?

文章插图
inspect 是个功能强大的标准库 , 在此用于获取程序调用栈的信息 。前三句代码的目的是取出 f_locals , 它的含义是“local namespace seen by this frame” , 此处 f_locals 指的就是类的局部命名空间 。
说到局部命名空间 , 你可能会想到 locals , 但是 , 我们之前有文章提到过“locals 与 globals 的读写问题” , locals 是可读不可写的 , 所以这段代码才用了 f_locals 。
pytest 如何实现参数化?
按惯例先看看上篇文章中的写法:
importpytest
@pytest.mark.parametrize("first,second", [(3,1), (-1,0), (1.5,1.0)])
deftest_values(first, second):
assert(first > second)
首先看到“mark” , pytest 里内置了一些标签 , 例如 parametrize、timeout、skipif、xfail、tryfirst、trylast 等 , 还支持用户自定义的标签 , 可以设置执行条件、分组筛选执行 , 以及修改原测试行为等等 。
用法也是非常简单的 , 然而 , 其源码可复杂多了 。我们这里只关注 parametrize , 先看看核心的一段代码:
如何将 Python 的一个类方法变为多个方法?

文章插图
根据传入的参数对 , 它复制了原测试方法的调用信息 , 存入待调用的列表里 。跟前面分析的两个库不同 , 它并没有在此创建新的测试方法 , 而是复用了已有的方法 。在 parametrize 所属的 Metafunc 类往上查找 , 可以追踪到 _calls 列表的使用位置:
如何将 Python 的一个类方法变为多个方法?

文章插图
最终是在 Function 类中执行:
如何将 Python 的一个类方法变为多个方法?

文章插图
好玩的是 , 在这里我们可以看到几行神注释……
如何将 Python 的一个类方法变为多个方法?

文章插图
阅读(粗浅涉猎) pytest 的源码 , 真的是自讨苦吃……不过 , 依稀大致可以看出 , 它在实现参数化时 , 使用的是生成器的方案 , 遍历一个参数则调用一次测试方法 , 而前面的 ddt 和 parameterized 则是一次性把所有参数解析完 , 生成 n 个新的测试方法 , 再交给测试框架去调度 。
对比一下 , 前两个库的思路很清晰 , 而且由于其设计单纯是为了实现参数化 , 不像 pytest 有什么标记和过多的抽象设计 , 所以更易读易懂 。前两个库发挥了 Python 的动态特性 , 设置类属性或者注入局部命名空间 , 而 pytest 倒像是从什么静态语言中借鉴的思路 , 略显笨拙 。
小结
回到标题中的问题“如何将一个方法变为多个方法?”除了在参数化测试中 , 不知还有哪些场景会有此诉求?欢迎留言讨论 。
本文分析了三个测试库的装饰器实现思路 , 通过阅读源码 , 我们可以发现它们各有千秋 , 这个发现本身还挺有意思 。在使用装饰器时 , 表面看它们差异不大 , 但是真功夫的细节都隐藏在底下 。
作者:豌豆花下猫 , 生于广东毕业于武大 , 现为苏漂程序员 , 有一些极客思维 , 也有一些人文情怀 , 有一些温度 , 还有一些态度 , 公众号「Python猫」(python_cat) 。
声明:本文系作者投稿 , 版权归作者所有 。




推荐阅读