UT-mock篇

python单元测试中mock的用法。

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 是 Python 中一个用于支持单元测试的库,它的主要功能是使用mock 对象替代掉指定的 Python 对象,以达到模拟对象的行为。

import requests

def c(url):
    resp = requests.get(url)
    # further process with resp

先来看个例子,通过例子了解mock的用处,这是一个requests模块发送get请求的函数c。当我们需要对他来进行单元测试时,是不是真的需要跟服务器来一次交互呢?当然肯定是不需要的,这时我们就可以用mock来模拟。如果利用 mock 模块,那么就可以达到这样的效果:使用一个 mock 对象替换掉上面的 requests.get 函数,然后执行函数 c 时, c 调用requests.get 的返回值就能够由我们的 mock 对象来决定,而不需要服务器的参与。简单的说,就是我们用一个 mock 对象替换掉 c 函数和服务器交互的过程。

Mock 对象是 mock 模块中最重要的概念。 Mock 对象就是 mock 模块中的一个类的实例,这个类的实例可以用来替换其他的 Python 对象,来达到模拟的效果。 Mock 类的定义如下:

class unitest.mock.Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, 
                        name=None, spec_set=None, unsafe=False, **kwargs)

·spec: 这是一个字符串或者现有对象(类或实例)构成的列表,实例化的这个 mock 对象只能访问这个传入列表的中的属性,访问其他的会引发 AttributeError。

·side_effect: 这个参数指向一个函数。当 mock 对象被像函数一样调用时,如果该函数的return_value 不是 DEFAULT 时,那么以 side_effect 指定的函数的返回值作为 mock 对象调用的返回值,通常用于抛出异常或改变 return_value。

·return_value: 这个字段可以指定一个对象,当 mock 对象被像函数一样调用时,就返回return_value 指定的对象。

·wraps :指定一个装饰函数,类似于一个装饰器,如果 return_value 不为 DEFAULT ,那么实例化的 mock 对象被调用时不会传递给装饰函数,忽略这个装饰器,直接返回 return_value。

·name: 这个是用来命名一个 mock 对象,只是起到标识作用,当你 print 一个 mock 对象的时候,可以看到它的 name。

·spec_set: 类似于 spec 参数,比它多了一个赋值的功能。如果不为 None ,这个实例化的mock 对象只能设置或者访问 spec_set 中已有的对象,也就是说 spec 规定的对象只能被访问,而 spec_set 中规定的对象还能被重新赋值。

·unsafe: 默认情况下,如果任何属性以 assert 或 assret 开头,将引发AttributeError 。 传递 unsafe = True 将允许访问这些属性( Python3.5 中新引入的参数)。

mock方法

assert methods:

mock_instance.assert_called_with(*argSeq, **argKW)
# 检测单元测试中是否调用过该 mock 对象,并且调用时的参数和括号中的一样
mock_instance.assert_called_once_with(*argSeq, **argKW)
# 和 assert_called_with 一样,但这个 mock 对象只被调用了一次,超过一次会报错
mock_instance.assert_any_calls(*argSeq, **argKW)
# 检测单元测试中该 mock 对象是否调用过括号中的对象
mock_instance.assert_has_calls(argCalls, aFlag=False)
# 检测单元测试中该 mock 对象是否分别调用过 argCalls 中的对象, argCalls 是一个
# 由被调用参数组成的 mock.call 对象列表, aFlag 决定 argCalls 中的对象是否被有序
# 调用。

statistics methods:

mock_instance.called : Boolean
# 表示该 mock 对象是否被调用过
mock_instance.call_count : integer
# 表示该 mock 对象被调用过的次数
mock_instance.call_args : tuple / mock_instance.call_args_list : list
# 表示该 mock 对象被调用是带入的参数,后者用于被多次调用
mock_instance.method_calls : list
# 表示该 mock 对象调用过的函数

mock使用方法

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

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

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

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

mock使用示例

# client.py
import requests

def send_request(url):
    r = requests.get(url)
    return r.status_code

def visit_openstack():
    return send_request('http://www.openstack.org')

# test_client.py
import mock
import unnitest

import client

class TestClient(unitest.TestCase):

	def test_success_request(self):
		url = 'http://www.openstack.org'
		success_send = mock.Mock(return_value='200')
		client.send_request = success_send
		self.assretEqual(client.visit_openstack(), '200')
		success_send.assert_called_once_with(url)
		
	def test_fail_request(self):
		url = 'http://www.openstack.org'
		fail_send = mock.Mock(return_value='404')
		client.send_request = fail_send
		self.assretEqual(client.visit_openstack(), '404')
		fail_send.assert_called_once_with(url)

先看一下被测函数visit_openstack,这个函数中调用了send_request这个函数,而send_request这个函数用requests模块发送了一共get请求并返回一个状态码。理解了被测函数我们开始套用刚才的四步用法:

1 、找到要替换的对象:我们需要测试的是 visit_openstack 这个函数,那么我们需要替换掉 send_request 这个函数。

2 、实例化 Mock 类得到一个 mock 对象来模拟 send_request函数,并且设置这个 mock对象的返回值。发送 get 请求可能成功也可能失败,在成功测试中,我们设置 mock 对象的返回值为字符串“ 200” ,在失败测试中,我们设置 mock 对象的返回值为字符串 “404” 。

3 、使用这个 mock 对象替换掉我们想替换的对象,也就是client.send_request

4 、写测试代码。我们调用 client.visit_openstack(),并且期望它的返回值和我们预设的一样。同时期望 client.send_request 函数被调用过一次,带入的参数与预期的一致,这里运用了assertEqual和assert_called_once_with两个验证方法。

Note

mock 对象的自动创建

当访问一个 mock 对象中不存在的属性或方法时,且这个对象没有设置 spec 和 spec_set 时, mock 会自动建立一个子 mock对象,并且把正在访问的属性指向它,这个功能对于实现多级属性的 mock 很方便。

client = mock.Mock()
client.v2_client.get.return_value = '200'

看上面的代码,就得到了一个 mock 过的 client 实例,调用该实例的 v2_client.get() 方法会得到的返回值是 “200” 。 从上面的例子中还可以看到,指定 mock 对象的 return_value 不一定必须在新建时设置,还可以使用属性赋值的方法设置。

Written on June 10, 2017