一、为什么要使用mock, 这是很多人心中存在的疑问。
在编写代码的时候, 要假想系统里面存在着特别多的外部依赖情形, 咱们不需要, 也不乐意把所有的程序模块都跑一遍。
打个简单的比方说,如果是要去验证分享文章到微博的那个功能效果, 那么在每次执行测试任务的时候, 如果非得要去真实地调用那个接口一次, 这样做的话, 不仅办事效率非常低下, 还会制造出来非常多的无用数据, 并且还会因为外部环境里的各种不确定因素而致使测试结果失败。
对于有些耗时更久, 或者无法简单创建测试环境的系统来说, 真实的测试就显得更没有必要了。
我们仅仅只需要知道, 代码是按照预期的逻辑去运行的, 并且同时也调用了那些需要调用的外部接口。还是以分享到微博这个功能作为举例来说明一下情况, 那么关于分享部分的伪码可能会呈现出这样一种具体的状态:
def share():"""Share system generated message to weibo."""
msg = generate_msg()
weibo = get_weibo_client(user_id)
weibo.upload(msg)
我们可以假定存在这样一种技术手段, 当需要对上述代码进行测量的时候, 它能够让全体的代码都得到执行。可是这里有个前提, 就是并不会真正地去运行 weibo.(msg) 这个操作。
与此同时, 系统还能够清楚地统计出每一个函数究竟被调用了几次, 以及每一次被执行的时候所传入的参数到底长什么样。这样一来, 我们编写测试案例的工作就会变得轻松许多了。
所谓的中mock, 其实就是指在软件测试的过程之中, 用来模拟外部服务的一种手段。通常在下述这些具体的应用场景里, 会需要去使用到mock。
a.在进行数据库操作的时候, 实际上是没有必要的去每一次都去执行读写数据库这一行为。
b.HTTP 这个请求, 它在进行网络运作的时候, 那个过程会消耗非常多的时间, 所以说在咱们进行测试工作的时候, 还得去依靠那些外部的服务才可以。
c.外部命令就是那种能够去运行系统命令的操作, 具体的例子可以是文件操作, 也可以是进程操作等等。
二、mock的基本原理
上面也已经提到过了, mock的作用是用来替换代码中所涉及的外部服务。因为属于动态类型的编程语言, 其中的所有元素全部都是对象的形式存在, 所以在程序正式运行之前, 需要将相应的实例、方法、函数以及变量进行整体的替换操作举个例子来说。
>>> import os>>> def myremove(filename):
>>> return filename
>>> os.remove = myremove
>>> print os.remove('test-file')
test-file
以上所给出的那个例子, 是说明这一概念时最简单的情况。假若把相关的代码改动为使用一个叫做Mock的类来处理的话。那么当这个类里面的方法在真正被调用并且执行复写操作的时候。
它是可以依据那些传入进来的参数来决定自己应该去做出怎样的行为的。而且它还具备能够记录每一次调用的能力。通过这种方式。你就能够对Mock究竟做了些什么工作有一个大致的了解吧。
三、mock的使用
(1)如何对某一个函数实行模拟操作呢?
from mock import MockmyMethod = Mock()
myMethod.return_value = 3
myMethod(1, 'a', foo='bar')
myMethod.assert_called_with(1, 'a', foo='bar') # True
myMethod()
myMethod.call_count # 2
想要模拟出一个函数, 直接使用那个 mock.Mock 的实例就可以了, 你能够在初始化的时候设定好返回值, 比如说把它等于 Mock 并且括号里面写个等于三也行, 当然你也能够通过点那个属性的方式来设置也是可以的。
除了, 你还能够mock , 它是一个函数或者是异常在mock 的对象被调用的时候, 它是会用参參一样的参数来被调用。
myMethod = Mock(side_effect=KeyError('whatever'))myMethod()
Traceback (most recent call last):
...
KeyError: 'whatever'
上面所展示的这个样例, 本质上就是在模拟一个异常情况的发生。如果该功能是以函数的形式来呈现的, 那么这个特定的函数就会被进行调用。这种机制的存在, 使得我们在实际操作中能够获得一种能力, 也就是能够通过这种方式, 针对返回数值这一环节来进行动态层面的生成处理。
下面这个样例是用来模拟实现一个函数, 这个函数能够返回输入进去的那个字符串的长度是多少。
def side_effect(str):return len(str)
myMethod = Mock(side_effect=side_effect)
myMethod('sd') # 2
在它存在的时候。mock还分别提供了以下这些种类的语句:
ith
(2)究竟该以怎样的一种方式来对一个类里的方法实施模拟, 这是很多人常常会产生疑问的问题。
如果想要对某个类里面的指定方法进行模拟操作的话, 那么可以使用通过mock所提供的这个叫做patch的方法。
import mockimport Module1
@mock.patch.object(Module1.Class1, 'some_method')
def test(mock_method):
mock_method.return_value = 3
mock_method.side_effect = some_side_effect
m = Module1.Class1()
m.some_method(*args, **kwargs)
assert m.some_method is mock_method
m.some_method.assert_called_with(*args, **kwargs)
(3)应该用什么方式来对一个类进行模拟操作呢?
有的时刻可能需要去模拟一个函数啊亦或是一个类的具体行为, 这个范围是包括进去它的全部属性与全部方法的, 如果说采用手动去一个一个地加入这些内容的话, 那确实是太低的效率了而且特别地容易出错, mock这个技术提供了相关的功能来应对这种情况, 它是依据所提供的那个模板类而生成出来了一个所谓的mock实例。
这属于一个用来模拟某个函数的具体实例。
import mockdef myFunc(a, b, c):
pass
>>> mock_func = mock.create_autospec(myFunc, return_value=3)
>>> mock_func(1,2,3)
>>> mock_func.assert_called_with(1,2,3)
>>> mock_func('a string')
Traceback (most recent call last):
...
TypeError: () takes exactly 3 arguments (1 given)
模拟出来一个类, 这个类别和它的是完全相同:
>>> mock_class = mock.create_autospec(myClass)(4)在平常使用的时候, 其所采用的具体操作方法是怎样的。
这里使用的是返回值等于3, 其作用是来模拟.post网络交互的返回值。这样就省去了真实的网络交互。当然, 也能够用一个方法的返回值来代替3这个返回值。
import jsonimport mock
from django.test import TestCase
class ApiTest(TestCase):
@mock.patch('apps.agent.requests.post')
def test(self, mock_method):
mock_method.return_value = 3
mock_method.side_effect = some_side_effect
res = self.client.post('/url/to/post')
r = json.loads(res.content)
self.assertEqual(0, r['retval'])