一、Mock是什么

Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西。准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉指定的Python对象,以达到模拟对象的行为。简单的说,mock库用于如下的场景:

假设你开发的项目叫a,里面包含了一个模块b,模块b中的一个函数c(也就是a.b.c)在工作的时候需要调用发送请求给特定的服务器来得到一个JSON返回值,然后根据这个返回值来做处理。如果要为a.b.c函数写一个单元测试,该如何做?

一个简单的办法是搭建一个测试的服务器,在单元测试的时候,让a.b.c函数和这个测试服务器交互。但是这种做法有两个问题:

  1. 测试服务器可能很不好搭建,或者搭建效率很低。

  2. 你搭建的测试服务器可能无法返回所有可能的值,或者需要大量的工作才能达到这个目的。

那么如何在没有测试服务器的情况下进行上面这种情况的单元测试呢?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四个函数。

四个主要的assert方法:
assert_called_with  是否调用了这个函数
assert_called_once_with  是否只调用了一次这个函数
assert_any_calls  是否调用了这个函数,前两个函数只能判断离它们最近的一次调用,这个是全局的。
 

Mock对象的一般用法是这样的:

  1. 找到你要替换的对象,这个对象可以是一个类,或者是一个函数,或者是一个类实例。

  2. 然后实例化Mock类得到一个mock对象,并且设置这个mock对象的行为,比如被调用的时候返回什么值,被访问成员的时候返回什么值等。

  3. 使用这个mock对象替换掉我们想替换的对象,也就是步骤1中确定的对象。

  4. 之后就可以开始写测试代码,这个时候我们可以保证我们替换掉的对象在测试用例执行的过程中行为和我们预设的一样。

举个例子来说:我们有一个简单的客户端实现,用来访问一个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')
  1. 找到要替换的对象:我们需要测试的是visit_ustack这个函数,那么我们需要替换掉send_request这个函数。

  2. 实例化Mock类得到一个mock对象,并且设置这个mock对象的行为。在成功测试中,我们设置mock对象的返回值为字符串“200”,在失败测试中,我们设置mock对象的返回值为字符串"404"。

  3. 使用这个mock对象替换掉我们想替换的对象。我们替换掉了client.send_request

  4. 写测试代码。我们调用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)。

现在来分析下上面的用例,我们要检查的项目有两个:

  1. visit_ustack()调用了send_request()

  2. 调用的参数是一个字符串

patch和patch.object

在了解了mock对象之后,我们来看两个方便测试的函数:patchpatch.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.objectpatch的效果是一样的,只不过用法有点不同。举例来说,同样是上面这个例子,换成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的入门学习的更多相关文章

  1. 大牛整理最全Python零基础入门学习资料

    大牛整理最全Python零基础入门学习资料 发布时间:『 2017-11-12 11:56 』     帖子类别:『人工智能』  阅读次数:3504 (本文『大牛整理最全Python零基础入门学习资料 ...

  2. 第15.10节 PyQt(Python+Qt)入门学习:Qt Designer可视化设计界面组件与QWidget类相关的组件属性详解

    PyQt学习有阵子了,对章节的骨架基本考虑好了,准备本节就写组件的属性的,结果一是日常工作繁忙,经常晚上还要加班,二是Qt的组件属性很多,只能逐一学习.研究和整理,花的时间有点长,不过终于将可视化设计 ...

  3. Python Mock的入门(转)

    原文:https://segmentfault.com/a/1190000002965620 Mock是什么 Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西. ...

  4. 开发神技能 | Python Mock 的入门

    Mock是什么 Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西.准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代 ...

  5. Python Mock 的入门

    Mock是什么 Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西.准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代 ...

  6. Python 爬虫如何入门学习?

    "入门"是良好的动机,但是可能作用缓慢.如果你手里或者脑子里有一个项目,那么实践起来你会被目标驱动,而不会像学习模块一样慢慢学习. 另外如果说知识体系里的每一个知识点是图里的点,依 ...

  7. C、C++、Java到Python,编程入门学习什么语言好?

    摘要:回顾编程语言几十年来的兴衰起伏,似乎也折射了整个信息产业的变迁消亡,想要在技术的洪流里激流勇进,找准并学精一两门编程语言更加显得至关重要. 最近,TIOBE更新了7月的编程语言榜单,常年霸榜的C ...

  8. Python - twisted web 入门学习之一

    原文地址:http://zhouzhk.iteye.com/blog/765884 python的twisted框架中带了一个web server: twisted web.现在看看怎么用. 一)准备 ...

  9. 第15.11节 PyQt(Python+Qt)入门学习:Qt Designer(设计师)组件Property Editor(属性编辑)界面中主窗口QMainWindow类相关属性详解

    概述 主窗口对象是在新建窗口对象时,选择main window类型的模板时创建的窗口对象,如图: 在属性编辑界面中,主窗口对象与QMainWindow相关的属性包括:iconSize.toolButt ...

随机推荐

  1. js 循环向上滚动

    aaaaaaaaaaaaaaaaa最开头 aaaaaaaaaaa 1 aaaaaaaaaaa 2 aaaaaaaaaaa 3 aaaaaaaaaaa 4 aaaaaaaaaaa 5 aaaaaaaaa ...

  2. ORA-06553: PLS-553: character set name is not recognized, while starting Content Store

    Symptom CM-CFG-5029 Content Manager is unable to determine whether the content store is initialized. ...

  3. C(m,n)算法

    排列组合:C(m,n),m为给定数列,n为要从数列m中取元素的数量,GetResult()获取所有不重复的组合. public class MathCombination<T> { Lis ...

  4. Html5与Css3知识点拾遗(二)

    页面title 选择能简要概括文档内容的文字作为title文字,title核心内容放在前60个字符 分级标题 1.创建分级标题时,避免跳过级别,如h3直接跳到h5,但允许从低级别跳到高级别. 2.不用 ...

  5. 可以替代alert 的漂亮的Js弹框

    1 基本弹框 2确认框 3又一种确认框 4带返回的弹框 5带返回的探矿 6 6 一切尽在 http://t4t5.github.io/sweetalert/

  6. How to transfer developer profile to one mac to another mac

    Export developer profile from old mac. In the Xcode Organizer, select your team in the Teams section ...

  7. noip第4课作业

    1.    计算邮资 [问题描述] 根据邮件的重量和用户是否选择加急计算邮费.计算规则:重量在1000克以内 (包含1000克),基本费8元.超过1000克的部分,每500克加收超重费4元,不足500 ...

  8. QT中的线程与事件循环理解(2)

    1. Qt多线程与Qobject的关系 每一个 Qt 应用程序至少有一个事件循环,就是调用了QCoreApplication::exec()的那个事件循环.不过,QThread也可以开启事件循环.只不 ...

  9. [置顶] 获取网络数据中的数组显示成ListView的简单流程

    首先说一下  这是我自己的个人笔记,如果想看看,不用看细节,可以看流程. 定义一个线程池 ExecutorService pool = Executors.newFixedThreadPool(15) ...

  10. Beta版本冲刺准备

    1. 介绍小组新加入的成员,Ta担任的角色. 姓名 王华俊 爱好 PS ,听音乐等等 编程方向 Java小程序,GUI 团队内担任的角色 美工 让他担任这个角色的理由:不同于别人的直男审美 2. 讨论 ...