参考文章:html
http://www.javashuo.com/article/p-sedfzpio-cv.htmlpython
Mock这个词在英语中有模拟的这个意思,所以咱们能够猜想出这个库的主要功能是模拟一些东西。准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉指定的Python对象,以达到模拟对象的行为。简单的说,mock库用于以下的场景:segmentfault
假设你开发的项目叫a,里面包含了一个模块b,模块b中的一个函数c(也就是a.b.c)在工做的时候须要调用发送请求给特定的服务器来获得一个JSON返回值,而后根据这个返回值来作处理。
若是要为a.b.c函数写一个单元测试,该如何作?
一个简单的办法是搭建一个测试的服务器,在单元测试的时候,让a.b.c函数和这个测试服务器交互。可是这种作法有两个问题:服务器
测试服务器可能很很差搭建,或者搭建效率很低。ide
你搭建的测试服务器可能没法返回全部可能的值,或者须要大量的工做才能达到这个目的。函数
那么如何在没有测试服务器的状况下进行上面这种状况的单元测试呢?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模块内部的实现机制,不在本文的讨论范围。本文主要讨论如何用mock模块来解决上面提到的这种单元测试场景。测试
一、在Python 3.3之前的版本中,须要另外安装mock模块,可使用pip命令来安装:google
pip install mock
而后在代码中就能够直接import进来:url
import mock
二、从Python 3.3开始,mock模块已经被合并到标准库中,被命名为unittest.mock,能够直接import进来使用:
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对象的通常用法是这样的:
找到你要替换的对象,这个对象能够是一个类,或者是一个函数,或者是一个类实例。
而后实例化Mock类获得一个mock对象,而且设置这个mock对象的行为,好比被调用的时候返回什么值,被访问成员的时候返回什么值等。
使用这个mock对象替换掉咱们想替换的对象,也就是步骤1中肯定的对象。
以后就能够开始写测试代码,这个时候咱们能够保证咱们替换掉的对象在测试用例执行的过程当中行为和咱们预设的同样。
举个例子来讲:咱们有一个简单的客户端实现,用来访问一个URL,当访问正常时,须要返回状态码200,不正常时,须要返回状态码404。首先,咱们的客户端代码实现以下:
# 文件名:client
import requests def send_requestr(url): r = requests.get(url) return r.status_code def visit_ustack(): return send_requestr("http://www.ustack.com")
外部模块调用visit_ustack()
来访问UnitedStack的官网。下面咱们使用mock对象在单元测试中分别测试访问正常和访问不正常的状况。
import unittest from unittest import mock from . 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进来,而后替换掉指定的对象就能够了
上面讲的是mock对象最基本的用法。下面来看看mock对象的稍微高级点的用法(并非很高级啊,最完整最高级的直接去看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()
调用的参数是一个字符串
在了解了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
相似。
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