python mock 测试

使用mock进行单元测试

mock 成员方法

Mock 类, 返回固定值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from mock import Mock, create_autospec, patch
import unittest2 as unittest
class Person(object):
def __init__(self):
self.__age = 10
def get_fullname(self, first_name, last_name):
return first_name + ' ' + last_name
def get_age(self):
return self.__age
@staticmethod
def get_class_name():
return Person.__name__
class PersonTest1(unittest.TestCase):
def test_should_get_age(self):
p = Person()
# 不mock时,get_age应该返回10
self.assertEqual(p.get_age(), 10)
# mock 掉 get_age 方法,通过 returen_value 让它返回20
p.get_age = Mock(return_value=20)
self.assertEqual(p.get_age(), 20)
def test_should_get_fullname(self):
p = Person()
# mock掉get_fullname,让它返回'James Harden'
p.get_fullname = Mock(return_value='James Harden')
self.assertEqual(p.get_fullname(), 'James Harden')
if __name__ == '__main__':
unittest.main()

create_autospec 效验参数个数, 返回固定值

上面p.get_fullname()并没有传递参数,但是依然可以工作。如果想要校验参数,则需要用到create_autospec模块替换Mock模块

1
2
3
4
5
6
7
8
9
10
11
12
13
from mock import create_autospec
class PersonTest2(unittest.TestCase):
def test_should_get_fullname(self):
p = Person()
p.get_fullname = create_autospec(p.get_fullname, return_value='James Harden')
# 随便给两个参数,依然会返回mock的值
self.assertEqual(p.get_fullname('1', '2'), 'James Harden')
# 如果参数个数不对,会报错TypeError: 'last_name' parameter lacking default value
self.assertEqual(p.get_fullname('1'), 'James Harden')

side_effect, 依次返回指定值

side_effect参数和return_value是相反的。它给mock分配了可替换的结果,覆盖了return_value。简单的说,一个模拟工厂调用将返回
side_effect值,而不是return_value。

1
2
3
4
5
6
7
8
9
10
class PersonTest3(unittest.TestCase):
def test_should_get_age(self):
p = Person()
p.get_age = Mock(side_effect=[10, 11, 12])
# 如果更改如下三行的位置,则会报错
self.assertEqual(p.get_age(), 10)
self.assertEqual(p.get_age(), 11)
self.assertEqual(p.get_age(), 12)

根据参数不同,返回不同的值

1
2
3
4
5
6
7
8
9
class PersonTest4(unittest.TestCase):
def test_should_get_fullname(self):
p = Person()
values = {('James', 'Harden'): 'James Harden', ('Tracy', 'Grady'): 'Tracy Grady'}
p.get_fullname = Mock(side_effect=lambda x, y: values[(x, y)])
self.assertEqual(p.get_fullname('James', 'Harden'), 'James Harden')
self.assertEqual(p.get_fullname('Tracy', 'Grady'), 'Tracy Grady')

抛出异常

1
2
3
4
5
6
7
class PersonTest5(unittest.TestCase):
def test_should_raise_exception(self):
p = Person()
p.get_age = Mock(side_effect=TypeError('integer type'))
# 只要调用p.get_page()就会抛出异常
self.assertRaises(TypeError, p.get_age())

检验是否调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class PersonTest6(unittest.TestCase):
def test_should_validate_method_calling(self):
p = Person()
p.get_fullname = Mock(return_value='James Harden')
# 没调用过
p.get_fullname.assert_not_called() # Python 3.5
p.get_fullname('1', '2')
# 调用过任意次数
p.get_fullname.assert_called() # Python 3.6
# 只调用过一次, 不管参数
p.get_fullname.assert_called_once() # Python 3.6
# 只调用过一次,并且符合指定的参数
p.get_fullname.assert_called_once_with('1', '2')
p.get_fullname('3', '4')
# 只要调用过即可,必须指定参数
p.get_fullname.assert_any_call('1', '2')
# 重置mock,重置之后相当于没有调用过
p.get_fullname.reset_mock()
p.get_fullname.assert_not_called()
# Mock对象里除了return_value, side_effect属性外,
# called表示是否调用过,call_count可以返回调用的次数
self.assertEqual(p.get_fullname.called, False)
self.assertEqual(p.get_fullname.call_count, 0)
p.get_fullname('1', '2')
p.get_fullname('3', '4')
self.assertEqual(p.get_fullname.called, True)
self.assertEqual(p.get_fullname.call_count, 2)

mock 静态方法

静态方法和模块方法需要使用patch来mock

在测试方法参数中得到Mock对象

1
2
3
4
5
6
7
class PersonTest7(unittest.TestCase):
# 以字符串的形式列出静态方法的路径,在测试的参数里会自动得到一个Mock对象
@patch('your.package.module.Person.get_class_name')
def test_should_get_class_name(self, mock_get_class_name):
mock_get_class_name.return_value = 'Guy'
self.assertEqual(Person.get_class_name(), 'Guy')

使用patch.object

1
2
3
4
5
6
7
8
class PersonTest8(unittest.TestCase):
mock_get_class_name = Mock(return_value='Guy')
# 使用patch.object来mock,好处是Person类不是以字符串形式给出的
@patch.object(Person, 'get_class_name')
def test_should_get_class_name(self, mock_get_class_name):
mock_get_class_name.return_value = 'Guy'
self.assertEqual(Person.get_class_name(), 'Guy')

使用with控制作用域

1
2

mock 链式调用

在web 应用里,我们经常需要mock数据库,而访问数据库时经常是链式调用,看个例子:

1
2
def get_person(name):
return Person.objects.filter(name=name).order_by('age')

有个模块方法,返回数据库中所有指定name的人员,并按age排序
mock掉整个数据库访问:

1
2
3
4
5
6
@patch('your.package.module.Person.objects.filter')
def test_should_get_person(self, mock_filter):
# 先得到一个filter的Mock对象,再在return_value中设置一个Mock对象,此时不需要自己再创建
mock_filter.return_value.order_by.return_value = None
self.assertIsNone(get_person())

mock.patch example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@mock.patch.object(Server, 'dao_class')
@mock.patch.object(Server, '_get_provider')
@mock.patch.object(Server, 'get')
@mock.patch.object(ServiceAction, 'reboot_server')
def test_reboot(self, mock_si, mock_g, mock_gp, mock_dc):
ids = ["server-opsz0123"]
server = {}
server['id'] = ids[0]
server['instance_id'] = "d24f7584-8be4-4f50-83f6-c7570054638a"
server['owner_id'] = '68f499a2-09f1-11e7-bbb8-a0369fd8dabe'
server['state'] = 'active'
server['account_id'] = self.test_request_context.current_account['id']
server['create_user_id'] = self.test_request_context.current_user['id']
mock_g.return_value = server
provider = mock.Mock()
mock_gp.return_value = provider ##把provider 赋值给mock_gp
s = Server()
s.reboot(self.test_request_context, ids) ##reboot方法内部会调用reboot_instance方法
provider.reboot_instance.assert_called_with(server['instance_id']) ##注意这里用的provider

参考:使用 Python Mock 类进行单元测试
参考:Python中的模块学习之mock模块