Mock这个词在英语中有模拟的这个意思,所以咱们能够猜想出这个库的主要功能是模拟一些东西。准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉指定的Python对象,以达到模拟对象的行为。简单的说,mock库用于以下的场景:html
假设你开发的项目叫a,里面包含了一个模块b,模块b中的一个函数c(也就是a.b.c)在工做的时候须要调用发送请求给特定的服务器来获得一个JSON返回值,而后根据这个返回值来作处理。若是要为a.b.c函数写一个单元测试,该如何作?python
一个简单的办法是搭建一个测试的服务器,在单元测试的时候,让a.b.c函数和这个测试服务器交互。可是这种作法有两个问题:ruby
测试服务器可能很很差搭建,或者搭建效率很低。服务器
你搭建的测试服务器可能没法返回全部可能的值,或者须要大量的工做才能达到这个目的。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函数和服务器交互的过程。单元测试
在Python 3.3之前的版本中,须要另外安装mock模块,可使用pip命令来安装:测试
$ sudo pip install mock
而后在代码中就能够直接import进来:编码
import mock
从Python 3.3开始,mock模块已经被合并到标准库中,被命名为unittest.mock,能够直接import进来使用:url
from unittest import 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进来,而后替换掉指定的对象就能够了。
上面讲的是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很方便。
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
相似。
当使用多个装饰方法来装饰测试方法的时候,装饰的顺序很重要,但很容易混乱。
基本上,当装饰方法呗映射到带参数的测试方法中时,装饰方法的工做顺序是反向的。好比下面这个例子:
@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()
mock还未加入标准库。
http://www.voidspace.org.uk/python/mock/index.html
mock已经加入了标准库。
https://docs.python.org/3.4/library/unittest.mock-examples.html
https://docs.python.org/3.4/library/unittest.mock.html