Python unittest mock 黑魔法

陈海波 Hypo

阳光洒满肩,仿佛自由人

自从入坑 TDD 至今,并没有太多的 mock 需求,然而在随着深入到微服务后端组件的开发,依赖的组件越来越多也越来越诡异,便开始真正的正视起 Mock 来。

mock 是 Python 中一个神奇的模块,能够在测试中动态的替换部分逻辑,可以用来解决在跑测试的时候对其他组件依赖的问题。

在 Python 3.3 之后,mock 被加入到 unittest 模块里,不需要单独而在,而在之前,需要单独安装:

对于 mock 模块的学习也非常简单,只需要了解 Mock 类的使用和 Patch 的使用就非常受用了,前者是 mock 的精妙所在,可以创造一个可以替换任意函数、类、对象、方法的 object,并让这个 object 伪装成理想的样子。而 Patch 即是将这个 Mock object 替换的它应该存在的地方。

MOCK

要理解 Mock,必须要先理解 Mock 类:

文档描述很详尽,但是常用的参数 , 和 值得注意。

接受一个函数或异常,当 mock 实例被调用是,便会返回这个函数的返回内容,或者抛出对应的异常:

当对于返回值比较固定的时候, 可能会更加的实用:

除了在 init 的时候对这些值进行注入,还可以通过赋值的方式:

目前只是对 Mock 的简单用法,因为 Mock 的实现很神奇,可以直接调用任何参数、方法,哪怕这些参数、方法并不存在,Mock 实例会直接返回一个新的 Mock 实例,像一棵树一样,非常有趣:

正是有了这样的特性,我们可以直接进行更深入的修改:

自此,如何 Mock 类已经非常清晰了,一个 Mock 实例可以作为 callable 的对象存在,而它本身,却又可以包含"无限"层级的新的的 Mock 实例,因此他可以非常简单的伪造出复杂的结构,比如 Response 等。

然而,在 mock 模块中,提供的并不只有一个 Mock 类,以上只是最基本的,还有更自动和常用的 ,以及可以替换属性的 。(除此之外还有两种 NonCallableMock)

MagicMock

MagicMock 是 Mock 的子类,其区别正如其名,即是实现了很多 magic method,可以举一个简单的例子,对于一个实例,如果没有实现 方法时,被转换成 list 会抛出异常:

如果想正常的转化成列表,那么需要实现这个方法:

而 类则会自作聪明的 mock 掉所有的 magic method,所以不需要自己去实现,而 默认是空数组:

我之所以认为 是自作聪明是因为如果我们需要这些魔法方法,默认的肯定不足以我们使用,我们无法避免自己重写。但因为已经有默认的存在,以至于如果我们手动实现的 Mock 实例存在问题时,往往会被它的默认方法掩盖掉一部分真相。

PropertyMock

PropertyMock 是我又爱又恨的 Mock 类,爱其有用,恨其难写。

在 Mock 类中默认 mock 的是可调用对象,因此,如果是熟悉或者 property 属性的 mock 并不理想:

因此才需要 的存在来拯救世界:

这种写法不做详解,荆轲刺秦王。

补充

最后,作为 Mock 类的补充。Mock 除了提供了伪造数据的功能,还保留了大量的自带断言,可以来 ”事后“ 审计被 mock 的方法调用的情况,在单元测试中,简直神器。

Patch

有强大的 Mock 并不够用,只有真正的把伪造的可调用对象替换进去才算完美,而 Patch 便是负责这个工作。

在讲 mock 模块中实现的各种神奇 Patch 之前,依然先提下最基础的 类:

一目了然,其中 是必选项,即使要替换什么,在基础的 Patch 类中,要求为字符串,比如:,而 new 即使要替换成的 Mock 类实例,默认是一个 实例,正如前面所说,这个实例实现了大量的默认魔法方法,只能能够让被调用时不抛异常而已,如果需要有数据替换等更精细的操作,需要自己定义 Mock 实例。

Patch 的参数基本上都是作为实例化 Mock 实例时使用的,如果自己手动构造 Mock 实例的话,只需要专注于 Patch 本身的功能就好。可以看一个简单的例子:

值得一提的是,Patch 可以作为装饰器或者上下文管理器,前者对于默认的 Mock 实例即可满足需求的场景非常实用,而且 Patch 的额外参数也显得非常好用,后者对于构造负责的 Mock 实例非常好用。

Patch 拓展

Patch 类还有三个非常有用的拓展,分别是替换字典用的 ,多次替换用的 ,和替换实例用的 。

前两个非常简单, 可以替换字典,或者字典类似实例,参考文档上的例子,一目了然:

对于 ,个人感觉有点鸡肋,目前也没真正的用过,作用正如文档所说:

Perform multiple patches in a single call.

文档的例子:

是需要单独拿出来强调的,毕竟和前两者相比,替换一个实例的场景是非常场景的,而且会有非常诡异的替换方式。

其实可以看到,Patch 的参数大同小异,我们只需要关注差异就可以了:

首先依然有 ,并且多出了 参数。对于 Patch 来说,要求 是字符串,而这里则要求是要替换的类,反而 被要求为字符串。除了这点,方法没有其他要注意的了,直接举一个文档的例子:

对于 Python unittest mock 的基础用法算是写完了,可以看到 Mock 和 Patch 都是非常灵性的工具,虽然把实现的奇技淫巧都跟封装了起来,但是其灵活性依然让人惊讶。至于怎么设计 Mock 以应对更复杂的实际场景,应该是另外一种艺术了。

大家晚安。

互联网技术社群,期待您的加入

关注公众号获取更多资源!

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20171219G0YNU200?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券