【译】Python中如何创建mock?
原文地址:http://engineroom.trackmaven.com/blog/making-a-mockery-of-python/
今天我们来谈论下mock的使用。当然,请不要误会,这里的mock可不是嘲弄的意思。mock是一门技术,通过伪造部分实际代码,从而让我们能够验证剩余代码的正确性。现在我们将通过几个简单的示例演示mock在Python测试代码中的使用,以及这项极其有用的技术是如何帮助我们改善测试代码的。
为什么我们需要mock?
当我们进行单元测试的时候,我们的目标往往是为了测试非常小的代码块,例如一个独立存在的函数或类方法。换句话说,我们只需要针对那个函数内部的代码进行测试。如果测试代码依赖于其他的代码片段,即使被测试的函数没有变化,我们会发现在某种不幸的情形下,这部分内嵌代码的修改可能会破坏原有的测试。看看下面的例子,你将豁然开朗:
# function.py
def add_and_multiply(x, y):
addition = x + y
multiple = multiply(x, y)
return (addition, multiple)
def multiply(x, y):
return x * y
# test.py
import unittest
from function import add_and_multiply
class MyTestCase(unittest.TestCase):
def test_add_and_multiply(self):
x = 3
y = 5
addition, multiple = add_and_multiply(x, y)
self.assertEqual(8, addition)
self.assertEqual(15, multiple)
if __name__ == "__main__":
unittest.main()
$ python test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
在上面的例子中,add_and_multiply
计算两个数的和与乘积并返回。add_and_multiply
调用了另一个函数multiply
进行乘积计算。
假设我们想要摒弃“传统“的数学,并重新定义multiply
函数,在原有的乘积结果上加3。
新的multiply
函数如下:
def multiply(x, y):
return x * y + 3
现在我们遇到一个问题。我们的测试代码没有变化,我们想要测试的函数也没有变化,然而,test_add_and_multiply
却会执行失败:
$ python test.py
F
======================================================================
FAIL: test_add_and_multiply (__main__.MyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 13, in test_add_and_multiply
self.assertEqual(15, multiple)
AssertionError: 15 != 18
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
这个问题之所以会发生,是因为我们的原始测试代码并非真正的单元测试。尽管我们想要测试的是外部函数,但我们隐性的将内部函数也包含进来,因为我们期望的结果是依赖于这个内部函数的行为的。虽然在上面简单的示例中呈现的差异显得毫无意义,但某些场景下,我们需要测试一个复杂的逻辑代码块 - 例如,一个Django视图函数基于某些特定条件调用各种不同的内部功能,从函数调用结果中分离出视图逻辑的测试就显得尤为重要了。
解决这个问题有两种方案。我们要么忽略它,像集成测试那样去进行单元测试,要么求助于mock。第一种方案的缺点是,集成测试仅仅告诉我们函数调用时哪一行代码出问题了,这样更难找到问题根源所在。这并不是说,集成测试没有用处,因为在某些情况下它确实非常有用。不管怎样,单元测试和集成测试用于解决不同的问题,它们应该被同时使用。因此,如果我们想要成为一个好的测试人员,我们会选择另一种方案:mock。
mock是什么?
mock是一个极其优秀的Python包,Python 3已将其纳入标准库。对于我们这些还在UnicodeError遍布的Python 2.x中挣扎的苦逼码农,可以通过pip进行安装:
pip install mock==1.0.1
mock有多种不同的用法。我们可以用它提供猴子补丁功能,创建伪造的对象,甚至可以作为一个上下文管理器。所有这些都是基于一个共同目标的,用副本替换部分代码来收集信息并返回伪造的响应。
mock的文档非常密集,寻找特定的用例信息可能会非常棘手。这里,我们就来看看一个常见的场景 - 替换一个内嵌函数并检查它的输入和输出。
开始mock之旅
让我们用mock来重新编写单元测试。接下来,我们将讨论发生了什么,以及为什么从测试的角度来看它是非常有用的:
# test.py
import mock
import unittest
from function import add_and_multiply
class MyTestCase(unittest.TestCase):
@mock.patch('function.multiply')
def test_add_and_multiply(self, mock_multiply):
x = 3
y = 5
mock_multiply.return_value = 15
addition, multiple = add_and_multiply(x, y)
mock_multiply.assert_called_once_with(3, 5)
self.assertEqual(8, addition)
self.assertEqual(15, multiple)
if __name__ == "__main__":
unittest.main()
至此,我们可以改变multiply
函数来做任何我们想做的 - 它可能返回加3后的乘积,返回None,或返回favourite line from Monty Python and the Holy Grail - 你会发现,我们上面的测试仍然可以通过。这是因为我们mock了multiply函数
。在真正的单元测试场景下,我们并不关心multiply
函数内部发生了什么,从测试add_and_multiply
的角度来看,我们只关心multiply
被正确的参数调用了。这里我们假定有另一个单元测试会针对multiply
的内部逻辑进行测试。
刚才我们做了什么?
咋一看,上面的语法可能不好理解。让我们逐行分析:
@mock.patch('function.multiply')
def test_add_and_multiply(self, mock_multiply):
我们使用mock.patch
装饰器来用mock对象替换multiply
。然后,我们将它作为一个参数mock_multiply
插入到我们的测试代码中。在这个测试的上下文中,任何对multiply
的调用都会被重定向到mock_multiply
对象。
有人会质疑 - “怎么能用对象替换函数!?“别担心!在Python的世界,函数也是对象。通常情况下,当我们调用multiply()
,我们实际执行的是multiply
函数的__call__
方法。然而,恰当的使用mock,对multiply()
的调用将执行我们的mock对象而不是__call__
方法。
mock_multiply.return_value = 15
为了使mock函数可以返回任何东西,我们需要定义其return_value
属性。实际上,当mock函数被调用时,它用于定义mock对象的返回值。
addition, multiple = add_and_multiply(x, y)
mock_multiply.assert_called_once_with(3, 5)
在测试代码中,我们调用了外部函数add_and_multiply
。它会调用内嵌的multiply
函数,如果我们正确的进行了mock,调用将会被我们定义的mock对象取代。为了验证这一点,我们可以用到mock对象的高级特性 - 当它们被调用时,传给它们的任何参数将被储存起来。顾名思义,mock对象的assert_called_once_with
方法就是一个不错的捷径来验证某个对象是否被一组特定的参数调用过。如果被调用了,测试通过。反之,assert_called_once_with
会抛出AssertionError
的异常。
我们从中学到了什么?
好吧,我们遇到了很多实际问题。首先,我们通过mock将multiply
函数从add_and_multiply
中分离出来。这就意味着我们的单元测试只针对add_and_multiply
的内部逻辑。只有针对add_and_multiply
的代码修改将影响测试的成功与否。
其次,我们现在可以控制内嵌函数的输出,以确保外部函数处理了不同的情况。例如,add_and_multiply
可能有逻辑条件依赖于multiply
的返回值:比如说,我们只想在乘积大于10的条件下返回一个值。通过人为设定multiply
的返回值,我们可以模拟乘积小于10的情况以及乘积大于10的情况,从而可以很容易测试我们的逻辑正确性。
最后,我们现在可以验证被mock的函数被调用的次数,并传入了正确的参数。由于我们的mock对象取代了multiply
函数的位置,我们知道任何针对multiply
函数的调用都会被重定向到该mock对象。当测试一个复杂的功能时,确保每一步都被正确调用将是一件非常令人欣慰的事情。
本文系OneAPM工程师编译整理。OneAPM是中国基础软件领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和SQL语句的实时抓取。想阅读更多技术文章,请访问OneAPM官方技术博客。
【译】Python中如何创建mock?的更多相关文章
- [译]Python中的异步IO:一个完整的演练
原文:Async IO in Python: A Complete Walkthrough 原文作者: Brad Solomon 原文发布时间:2019年1月16日 翻译:Tacey Wong 翻译时 ...
- python中动态创建类
class Foo(Bar): pass Foo中有__metaclass__这个属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象(我说的是类对象,请紧 ...
- Python | 面试必问,线程与进程的区别,Python中如何创建多线程?
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题第20篇文章,我们来聊聊Python当中的多线程. 其实关于元类还有很多种用法,比如说如何在元类当中设置参数啦,以及一 ...
- [翻译]Mock 在 Python 中的使用介绍
目录 Mock 在 Python 中的使用介绍 原文链接与说明 恐惧系统调用 一个简单的删除函数 使用 Mock 重构 潜在陷阱 向 'rm' 中加入验证 将文件删除作为服务 方法 1:模拟实例的方法 ...
- Python中变量的属性以及判断方法
1.变量的属性 在Python中,创建一个变量会给这个变量分配三种属性: id ,代表该变量在内存中的地址: type,代表该变量的类型: value,该变量的值: x = 10 print(id(x ...
- Python中的包ImportError
前言 Python中的包给我提供了很好的代码组织,相似的功能模块放在同一个包内,不仅代码结构清晰,而且调用起来也比较方便(可以用*导入) 但是,我们在刚开始使用Python包的时候总是会遇到导入错误& ...
- python中列表生成式
1.简介 列表生成式即List Comprehensions,是Python中用于创建list的生成式. 2.示例 [表达式 循环体 条件语句] #!/usr/bin/env python # - ...
- 什么是python中的元类
所属网站分类: python高级 > 面向对象 作者:goodbody 原文链接: http://www.pythonheidong.com/blog/article/11/ 来源:python ...
- 利用Python中的mock库对Python代码进行模拟测试
这篇文章主要介绍了利用Python中的mock库对Python代码进行模拟测试,mock库自从Python3.3依赖成为了Python的内置库,本文也等于介绍了该库的用法,需要的朋友可以参考下 ...
随机推荐
- Python脚本控制的WebDriver 常用操作 <十> 层级定位
下面将使用WebDriver来模拟操作一个层级定位元素的操作 测试用例场景 在实际的项目测试中,经常会有这样的需求:页面上有很多个属性基本相同的元素,现在需要具体定位到其中的一个.由于属性基本相当,所 ...
- 解决matplotlib中文乱码问题(Windows)
1.修改matplotlibrc文件 进入Python安装目录下的Lib\site-packages\matplotlib\mpl-data目录,打开matplotlibrc文件,删除font.fam ...
- Python 以正确的宽度在盒子中居中打印一个字符
注意://为整除的意思 代码: # -*- coding:UTF-8 -*- sentence = input("Sentence:") screen_width = 80 tex ...
- poj 1564 Sum It Up
题目连接 http://poj.org/problem?id=1564 Sum It Up Description Given a specified total t and a list of n ...
- 68.vivado与modelsim的关联以及器件库编译
vivado软件中也自带仿真工具,但用了几天之后感觉仿真速度有点慢,至少比modelsim慢挺多的.而modelsim是我比较熟悉的一款仿真软件,固然选它作为设计功能的验证.为了将vivado和mod ...
- Jsp实现筛选并压缩文件批量下载
Jsp实现筛选并压缩文件批量下载 首先明确一下需求,网页端点击一下button,传递特定的参数到download.jsp网页,筛选文件,对过滤得到的文件进行压缩,然后返回前端一个压缩包下载. 以下的代 ...
- MySQL 存储过程学习笔记
存储过程框架 DEMILITER $$ -- 重定义符 DROP PROCEDURE IF EXISTS store_procedure$$ -- 如果存在此名的存储过程,先删除 CREATE PRO ...
- 微软职位内部推荐-Software Engineer II-Web app
微软近期Open的职位: The Office App Services team is working on the powerful Office Web Apps including Word ...
- 将SQLServer结果导出为excel文件
相信大家常常会遇到将SqlServer查询结果导出到Excel的问题.如果导出的次数少,直接“Save Results As...”就是了:但是当要分别在每个表取样,那就相当麻烦了.今天就为大家提供一 ...
- gitlab&fengoffice的ldap配置
1.fengoffice配置config/ldap_config.php $config_ldap = array ( 'binddn' => 'cn=admin,dc=xxx,dc=xxx', ...