一、Mock是什么
Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西。准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉指定的Python对象,以达到模拟对象的行为。简单的说,mock库用于如下的场景:
假设你开发的项目叫a,里面包含了一个模块b,模块b中的一个函数c(也就是a.b.c)在工作的时候需要调用发送请求给特定的服务器来得到一个JSON返回值,然后根据这个返回值来做处理。如果要为a.b.c函数写一个单元测试,该如何做?
一个简单的办法是搭建一个测试的服务器,在单元测试的时候,让a.b.c函数和这个测试服务器交互。但是这种做法有两个问题:
测试服务器可能很不好搭建,或者搭建效率很低。
你搭建的测试服务器可能无法返回所有可能的值,或者需要大量的工作才能达到这个目的。
那么如何在没有测试服务器的情况下进行上面这种情况的单元测试呢?Mock模块就是答案。上面已经说过了,mock模块可以替换Python对象。我们假设a.b.c的代码如下:
import requests
def c(url):
resp = requests.get(url)
# further process with resp
如果利用mock模块,那么就可以达到这样的效果:使用一个mock对象替换掉上面的requests.get函数,然后执行函数c时,c调用requests.get的返回值就能够由我们的mock对象来决定,而不需要服务器的参与。简单的说,就是我们用一个mock对象替换掉c函数和服务器交互的过程。
二、Mock的安装和导入
在Python 3.3以前的版本中,需要另外安装mock模块,可以使用pip命令来安装:
$ sudo pip install mock
然后在代码中就可以直接import进来:
import mock
从Python 3.3开始,mock模块已经被合并到标准库中,被命名为unittest.mock,可以直接import进来使用:
from unittest import mock
三、Mock对象
基本用法
Mock对象是mock模块中最重要的概念。Mock对象就是mock模块中的一个类的实例,这个类的实例可以用来替换其他的Python对象,来达到模拟的效果。Mock类的定义如下:
class Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, **kwargs)
这里给出这个定义只是要说明下Mock对象其实就是个Python类而已,当然,它内部的实现是很巧妙的,有兴趣的可以去看mock模块的代码。
mock主要有name,return_value,side_effect,和spec四个函数。
Mock对象的一般用法是这样的:
找到你要替换的对象,这个对象可以是一个类,或者是一个函数,或者是一个类实例。
然后实例化Mock类得到一个mock对象,并且设置这个mock对象的行为,比如被调用的时候返回什么值,被访问成员的时候返回什么值等。
使用这个mock对象替换掉我们想替换的对象,也就是步骤1中确定的对象。
之后就可以开始写测试代码,这个时候我们可以保证我们替换掉的对象在测试用例执行的过程中行为和我们预设的一样。
举个例子来说:我们有一个简单的客户端实现,用来访问一个URL,当访问正常时,需要返回状态码200,不正常时,需要返回状态码404。首先,我们的客户端代码实现如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
def send_request(url):
r = requests.get(url)
return r.status_code
def visit_ustack():
return send_request('http://www.ustack.com')
外部模块调用visit_ustack()
来访问UnitedStack的官网。下面我们使用mock对象在单元测试中分别测试访问正常和访问不正常的情况。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest
import mock
import client
class TestClient(unittest.TestCase):
def test_success_request(self):
success_send = mock.Mock(return_value='200')
client.send_request = success_send
self.assertEqual(client.visit_ustack(), '200')
def test_fail_request(self):
fail_send = mock.Mock(return_value='404')
client.send_request = fail_send
self.assertEqual(client.visit_ustack(), '404')
找到要替换的对象:我们需要测试的是
visit_ustack
这个函数,那么我们需要替换掉send_request
这个函数。实例化Mock类得到一个mock对象,并且设置这个mock对象的行为。在成功测试中,我们设置mock对象的返回值为字符串“200”,在失败测试中,我们设置mock对象的返回值为字符串"404"。
使用这个mock对象替换掉我们想替换的对象。我们替换掉了
client.send_request
写测试代码。我们调用
client.visit_ustack()
,并且期望它的返回值和我们预设的一样。
上面这个就是使用mock对象的基本步骤了。在上面的例子中我们替换了自己写的模块的对象,其实也可以替换标准库和第三方模块的对象,方法是一样的:先import进来,然后替换掉指定的对象就可以了。
四、稍微高级点的用法
class Mock的参数
上面讲的是mock对象最基本的用法。下面来看看mock对象的稍微高级点的用法(并不是很高级啊,最完整*的直接去看mock的文档即可,后面给出)。
先来看看Mock这个类的参数,在上面看到的类定义中,我们知道它有好几个参数,这里介绍最主要的几个:
name: 这个是用来命名一个mock对象,只是起到标识作用,当你print一个mock对象的时候,可以看到它的name。
return_value: 这个我们刚才使用过了,这个字段可以指定一个值(或者对象),当mock对象被调用时,如果side_effect函数返回的是DEFAULT,则对mock对象的调用会返回return_value指定的值。
side_effect: 这个参数指向一个可调用对象,一般就是函数。当mock对象被调用时,如果该函数返回值不是DEFAULT时,那么以该函数的返回值作为mock对象调用的返回值。
其他的参数请参考官方文档。
mock对象的自动创建
当访问一个mock对象中不存在的属性时,mock会自动建立一个子mock对象,并且把正在访问的属性指向它,这个功能对于实现多级属性的mock很方便。
client = mock.Mock()
client.v2_client.get.return_value = '200'
这个时候,你就得到了一个mock过的client实例,调用该实例的v2_client.get()
方法会得到的返回值是"200"。
从上面的例子中还可以看到,指定mock对象的return_value还可以使用属性赋值的方法。
对方法调用进行检查
mock对象有一些方法可以用来检查该对象是否被调用过、被调用时的参数如何、被调用了几次等。实现这些功能可以调用mock对象的方法,具体的可以查看mock的文档。这里我们举个例子。
还是使用上面的代码,这次我们要检查visit_ustack()
函数调用send_request()
函数时,传递的参数类型是否正确。我们可以像下面这样使用mock对象。
class TestClient(unittest.TestCase):
def test_call_send_request_with_right_arguments(self):
client.send_request = mock.Mock()
client.visit_ustack()
self.assertEqual(client.send_request.called, True)
call_args = client.send_request.call_args
self.assertIsInstance(call_args[0][0], str)
Mock对象的called属性表示该mock对象是否被调用过。
Mock对象的call_args表示该mock对象被调用的tuple,tuple的每个成员都是一个mock.call
对象。mock.call
这个对象代表了一次对mock对象的调用,其内容是一个tuple,含有两个元素,第一个元素是调用mock对象时的位置参数(*args),第二个元素是调用mock对象时的关键字参数(**kwargs)。
现在来分析下上面的用例,我们要检查的项目有两个:
visit_ustack()
调用了send_request()
调用的参数是一个字符串
patch和patch.object
在了解了mock对象之后,我们来看两个方便测试的函数:patch
和patch.object
。这两个函数都会返回一个mock内部的类实例,这个类是class _patch
。返回的这个类实例既可以作为函数的装饰器,也可以作为类的装饰器,也可以作为上下文管理器。使用patch
或者patch.object
的目的是为了控制mock的范围,意思就是在一个函数范围内,或者一个类的范围内,或者with
语句的范围内mock掉一个对象。我们看个代码例子即可:
class TestClient(unittest.TestCase):
def test_success_request(self):
status_code = '200'
success_send = mock.Mock(return_value=status_code)
with mock.patch('client.send_request', success_send):
from client import visit_ustack
self.assertEqual(visit_ustack(), status_code)
def test_fail_request(self):
status_code = '404'
fail_send = mock.Mock(return_value=status_code)
with mock.patch('client.send_request', fail_send):
from client import visit_ustack
self.assertEqual(visit_ustack(), status_code)
这个测试类和我们刚才写的第一个测试类一样,包含两个测试,只不过这次不是显示创建一个mock对象并且进行替换,而是使用了patch
函数(作为上下文管理器使用)。
patch.object
和patch
的效果是一样的,只不过用法有点不同。举例来说,同样是上面这个例子,换成patch.object
的话是这样的:
def test_fail_request(self):
status_code = '404'
fail_send = mock.Mock(return_value=status_code)
with mock.patch.object(client, 'send_request', fail_send):
from client import visit_ustack
self.assertEqual(visit_ustack(), status_code)
就是替换掉一个对象的指定名称的属性,用法和setattr
类似。
五、装饰的顺序
当使用多个装饰方法来装饰测试方法的时候,装饰的顺序很重要,但很容易混乱。
基本上,当装饰方法呗映射到带参数的测试方法中时,装饰方法的工作顺序是反向的。比如下面这个例子:
@mock.patch('mymodule.sys')
@mock.patch('mymodule.os')
@mock.patch('mymodule.os.path')
def test_something(self, mock_os_path, mock_os, mock_sys):
pass
注意到了吗,我们的装饰方法的参数是反向匹配的? 这是有部分原因是因为Python的工作方式。下面是使用多个装饰方法的时候,实际的代码执行顺序:
patch_sys(patch_os(patch_os_path(test_something)))
由于这个关于sys的补丁在最外层,因此会在最后被执行,使得它成为实际测试方法的最后一个参数。请特别注意这一点,并且在做测试使用调试器来保证正确的参数按照正确的顺序被注入。
六、简单示例
示例一
1》定义modular.py文件,内容如下:
# coding=utf-8
#设置编码,utf-8可支持中英文
class Count():
def add(self):
pass
2》定义mock_demo01.py文件,内容如下:
# coding=utf-8
#设置编码,utf-8可支持中英文
import mock
import unittest
from modular import Count
# test Count class
class TestCount(unittest.TestCase):
def test_add(self):
#首先,调用被测试类Count()
count = Count()
#通过Mock类模拟被调用的方法add()方法,return_value 定义add()方法的返回值。
count.add = mock.Mock(return_value=13)
#接下来,相当于在正常的调用add()方法,传两个参数8和5,然后会得到相加的结果13。然后,13的结果是我们在上一步就预先设定好的。
result = count.add(8,5)
#最后,通过assertEqual()方法断言,返回的结果是否是预期的结果13。
self.assertEqual(result,13)
if __name__ == '__main__':
unittest.main()
示例二
1》定义modular.py文件,内容如下:
# coding=utf-8
#设置编码,utf-8可支持中英文
class Count():
def add(self, a, b):
return a + b
2》定义mock_demo02.py文件,内容如下:
# coding=utf-8
#设置编码,utf-8可支持中英文
import mock
import unittest
from modular import Count
# test Count class
class TestCount(unittest.TestCase):
def test_add(self):
#首先,调用被测试类Count()
count = Count()
#side_effect参数和return_value是相反的。它给mock分配了可替换的结果,覆盖了return_value。
## 简单的说,一个模拟工厂调用将返回side_effect值,而不是return_value。
#所以,设置side_effect参数为Count类add()方法,那么return_value的作用失效。
count.add = mock.Mock(return_value=13, side_effect=count.add)
#这次将会真正的调用add()方法,得到的返回值为16(8+8)。通过print打印结果。
result = count.add(8, 8)
print(result)
#检查mock方法是否获得了正确的参数。
count.add.assert_called_with(8, 8)
##最后,通过assertEqual()方法断言,返回的结果是否是预期的结果16。
self.assertEqual(result, 16)
if __name__ == '__main__':
unittest.main()
示例三
1》定义function.py文件,内容如下:
# coding=utf-8
#设置编码,utf-8可支持中英文
def add_and_multiply(x, y):
addition = x + y
multiple = multiply(x, y)
return (addition, multiple)
def multiply(x, y):
return x * y+3
2》定义func_test.py文件,内容如下:
# coding=utf-8
#设置编码,utf-8可支持中英文
from mock import patch
import unittest
import function
class MyTestCase(unittest.TestCase):
"""
patch()装饰/上下文管理器可以很容易地模拟类或对象在模块测试。
在测试过程中,您指定的对象将被替换为一个模拟(或其他对象),并在测试结束时还原。
这里模拟function.py文件中multiply()函数。
在定义测试用例中,将mock的multiply()函数(对象)重命名为 mock_multiply对象。
"""
@patch("function.multiply")
def test_add_and_multiply(self, mock_multiply):
x = 3
y = 5
#设定mock_multiply对象的返回值为固定的15。
mock_multiply.return_value = 15
#在此之前已经模拟function文件中multiply方法的返回值为15,因此下面执行过程中addition, multiple的值分别是8和15
addition, multiple = function.add_and_multiply(x, y)
#检查ock_multiply方法的输入参数是否与上面方法调用时候function文件中multiply方法的输入参数一致。
mock_multiply.assert_called_once_with(3, 5)
self.assertEqual(8, addition)
self.assertEqual(15, multiple)
if __name__ == "__main__":
unittest.main()
七、官方文档
Python 2.7
mock还未加入标准库。
http://www.voidspace.org.uk/python/mock/index.html
Python 3.4
mock已经加入了标准库。
https://docs.python.org/3.4/library/unittest.mock-examples.html
https://docs.python.org/3.4/library/unittest.mock.html
Python Mock的入门学习的更多相关文章
-
大牛整理最全Python零基础入门学习资料
大牛整理最全Python零基础入门学习资料 发布时间:『 2017-11-12 11:56 』 帖子类别:『人工智能』 阅读次数:3504 (本文『大牛整理最全Python零基础入门学习资料 ...
-
第15.10节 PyQt(Python+Qt)入门学习:Qt Designer可视化设计界面组件与QWidget类相关的组件属性详解
PyQt学习有阵子了,对章节的骨架基本考虑好了,准备本节就写组件的属性的,结果一是日常工作繁忙,经常晚上还要加班,二是Qt的组件属性很多,只能逐一学习.研究和整理,花的时间有点长,不过终于将可视化设计 ...
-
Python Mock的入门(转)
原文:https://segmentfault.com/a/1190000002965620 Mock是什么 Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西. ...
-
开发神技能 | Python Mock 的入门
Mock是什么 Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西.准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代 ...
-
Python Mock 的入门
Mock是什么 Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西.准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代 ...
-
Python 爬虫如何入门学习?
"入门"是良好的动机,但是可能作用缓慢.如果你手里或者脑子里有一个项目,那么实践起来你会被目标驱动,而不会像学习模块一样慢慢学习. 另外如果说知识体系里的每一个知识点是图里的点,依 ...
-
C、C++、Java到Python,编程入门学习什么语言好?
摘要:回顾编程语言几十年来的兴衰起伏,似乎也折射了整个信息产业的变迁消亡,想要在技术的洪流里激流勇进,找准并学精一两门编程语言更加显得至关重要. 最近,TIOBE更新了7月的编程语言榜单,常年霸榜的C ...
-
Python - twisted web 入门学习之一
原文地址:http://zhouzhk.iteye.com/blog/765884 python的twisted框架中带了一个web server: twisted web.现在看看怎么用. 一)准备 ...
-
第15.11节 PyQt(Python+Qt)入门学习:Qt Designer(设计师)组件Property Editor(属性编辑)界面中主窗口QMainWindow类相关属性详解
概述 主窗口对象是在新建窗口对象时,选择main window类型的模板时创建的窗口对象,如图: 在属性编辑界面中,主窗口对象与QMainWindow相关的属性包括:iconSize.toolButt ...
随机推荐
-
java多线程通信 例子
package com.cl.www.thread; public class NumberHolder { private Integer number = 0; // 增加number publi ...
-
js读写Cookie问题(Cookie存储时长、Cookie存储域)汇总
在采集网站用户行为数据/使用js对用户行为做交互时,经常会使用到Cookie,了解Js Cookie的读写,以及一些细节,非常重要. 什么是Cookie 所谓Cookie,只是一条极为短小的信息, ...
-
Asp.net(C#) windows 服务{用于实现计划任务,事件监控等}
什么是windows服务? 一个Windows服务程序是在Windows操作系统下能完成特定功能的可执行的应用程序.Windows服务程序虽然是可执行的,但是它不像一般的可执行文件通过双击就 ...
-
WinForm------TextEdit控件内容字体变*号
"属性" -> “Properties” -> “LookAndFeel” -> “PasswordChar”
-
poj1617---columnar encryption
题意:给出keyword,如BATBOY,A的ascii值最小,所以第二列最先输出,B有两个,左边的先输出,也就是说,接下来输出第一列和第4列, 所以每一个字母都带有一个ascii值和一个序号,用结构 ...
-
ecplise中设置字体大小和背景
1 将ecplise中的代码背景设置为豆沙色 2 设置ecplise中的字体大小
-
Stf-windows版本
Stf-windows Stf 原项目地址:https://github.com/openstf/stf . 介绍 用于Web端设备远程管理 系统支持 支持Android版本2.3.3 (SDK10) ...
-
R读取一个数据框 Dataframe,删去其中的某一列
可以参考:http://blog.sina.com.cn/s/blog_80572f5d0101anxw.html
-
LeetCode Permutation in String
原题链接在这里:https://leetcode.com/problems/permutation-in-string/description/ 题目: Given two strings s1 an ...
-
ASP.NET 最全的POST提交数据和接收数据 —— (1) 用url传参方式
//1.对象提交,字典方式 //接口方:public ActionResult GetArry(Car model) public void PostResponse() { HttpWebReque ...