mock 简介
py3 已将 mock 集成到 unittest 库中
为的就是更好的进行单元测试
简单理解, 模拟接口返回参数
通俗易懂, 直接修改接口返回参数的值
mock 作用
解决依赖问题, 达到解耦作用
当我们测试某个目标接口 (模块) 时, 该接口依赖其他接口, 当被依赖的接口未开发完成时, 可以用 mock 模拟被依赖接口, 完成目标接口的测试
模拟复杂业务的接口
当我们测试某个目标接口(模块), 该接口依赖一个非常复杂的接口时, 可以用 mock 来模拟这个复杂的业务接口; 也解决接口依赖一样的原理
单元测试
如果某个接口 (模块) 未开发完成时, 又需要编写测试用例, 则可以通过 mock 模拟该接口 (模块) 进行测试
前后端联调
前端开发的页面需要根据后端返回的不同状态码展示不同的页面, 当后端接口未开发完成时, 也可通过 mock 来模拟后端接口返回自己想要的数据
mock 类解读
class Mock(spec=None,side_effect=None,return_value=DEFFAULT,name=None)
secp: 定义 mock 对象的属性值, 可以是列表, 字符串, 甚至一个对象或者实例
side_effect: 可以用来抛出异常或者动态改变返回值, 它必须是一个 iterator(列表), 它会覆盖 return_value
return_value: 定义 mock 方法的返回值, 它可以是一个值, 可以是一个对象(如果存在 side_effect 参数那这个就没有用, 也就是不能同时用)
name: 作为 mock 对象的一个标识, 在 print 时可以看到
mock 实际使用
一个未开发完成的功能如何测试?
- def add(self, a, b):
- """两个数相加"""
- pass
- class TestSub(unittest.TestCase):
- """测试两个数相加用例"""
- def test_sub(self):
- # 创建一个 mock 对象 return_value 代表 mock 一个数据
- mock_add = mock.Mock(return_value=15)
- # 将 mock 对象赋予给被测函数
- add = mock_add
- # 调用被测函数
- result = add(5, 5)
- # 断言实际结果和预期结果
- self.assertEqual(result, 15)
一个完成开发的功能如何测试?
- class SubClass(object):
- def add(self, a, b):
- """两个数相加"""
- return a + b
- class TestSub(unittest.TestCase):
- """测试两个数相加用例"""
- def test_add2(self):
- # 初始化被测函数类实例
- sub = SubClass()
- # 创建一个 mock 对象 return_value 代表 mock 一个数据
- # 传递 side_effect 关键字参数, 会覆盖 return_value 参数值, 使用真实的 add 方法测试
- sub.add = mock.Mock(return_value=15, side_effect=sub.add)
- # 调用被测函数
- result = sub.add(5, 5)
- # 断言实际结果和预期结果 18 self.assertEqual(result, 10)
side_effect: 这里给的参数值是 sub.add 相当于 add 方法的地址, 当我们调用 add 方法时就会调用真实的 add 方法
简单理解成: 传递了 side_effect 参数且值为被测函数地址时, mock 不会起作用; 两者不可共存
另外, side_effect 接受的是一个可迭代序列, 当传递多个值时, 每次调用 mock 时会返回不同的值; 如下
- mock_obj = mock.Mock(side_effect= [1,2,3])
- print(mock_obj())
- print(mock_obj())
- print(mock_obj())
- print(mock_obj())
- # 输出
- Traceback (most recent call last):
- 1
- File "D:/MyThreading/mymock.py", line 37, in <module>
- 2
- print(mock_obj())
- 3
- File "C:\Python36\lib\unittest\mock.py", line 939, in __call__
- return _mock_self._mock_call(*args, **kwargs)
- File "C:\Python36\lib\unittest\mock.py", line 998, in _mock_call
- result = next(effect)
- StopIteration
存在依赖关系的功能如何测试?
- # 支付类
- class Payment:
- def requestOutofSystem(self, card_num, amount):
- '''
- 请求第三方外部支付接口, 并返回响应码
- :param card_num: 卡号
- :param amount: 支付金额
- :return: 返回状态码, 200 代表支付成功, 500 代表支付异常失败
- '''
- # 第三方支付接口请求地址(故意写错)
- url = "http://third.payment.pay/"
- # 请求参数
- data = {"card_num": card_num, "amount": amount}
- response = requests.post(url, data=data)
- # 返回状态码
- return response.status_code
- def doPay(self, user_id, card_num, amount):
- '''
- 支付
- :param userId: 用户 ID
- :param card_num: 卡号
- :param amount: 支付金额
- :return:
- '''
- try:
- # 调用第三方支付接口请求进行真实扣款
- resp = self.requestOutofSystem(card_num, amount)
- print('调用第三方支付接口返回结果:', resp)
- except TimeoutError:
- # 如果超时就重新调用一次
- print('重试一次')
- resp = self.requestOutofSystem(card_num, amount)
- if resp == 200:
- # 返回第三方支付成功, 则进行系统里面的扣款并记录支付记录等操作
- print("{0}支付 {1} 成功!!! 进行扣款并记录支付记录".format(user_id, amount))
- return 'success'
- elif resp == 500:
- # 返回第三方支付失败, 则不进行扣款
- print("{0}支付 {1} 失败!! 不进行扣款!!!".format(user_id, amount))
- return 'fail'
- # 单元测试类
- class payTest(unittest.TestCase):
- def test_pay_success(self):
- pay = Payment()
- # 模拟第三方支付接口返回 200
- pay.requestOutofSystem = mock.Mock(return_value=200)
- resp = pay.doPay(user_id=1, card_num='12345678', amount=100)
- self.assertEqual('success', resp)
- def test_pay_fail(self):
- pay = Payment()
- # 模拟第三方支付接口返回 500
- pay.requestOutofSystem = mock.Mock(return_value=500)
- resp = pay.doPay(user_id=1, card_num='12345678', amount=100)
- self.assertEqual('fail', resp)
- def test_pay_time_success(self):
- pay = Payment()
- # 模拟第三方支付接口首次支付超时, 重试第二次成功
- pay.requestOutofSystem = mock.Mock(side_effect=[TimeoutError, 200])
- resp = pay.doPay(user_id=1, card_num='12345678', amount=100)
- self.assertEqual('success', resp)
- def test_pay_time_fail(self):
- pay = Payment()
- # 模拟第三方支付接口首次支付超时, 重试第二次失败
- pay.requestOutofSystem = mock.Mock(side_effect=[TimeoutError, 500])
- resp = pay.doPay(user_id=1, card_num='12345678', amount=100)
- self.assertEqual('fail', resp)
也许有小伙伴会问, 第三方支付都不能用, 我们的测试结果是否是有效的呢?
通常在测试一个模块的时候, 是可以认为其他模块的功能是正常的, 只针对目标模块进行测试是没有任何问题的, 所以说测试结果也是正确的
mock 装饰器
一共两种格式
- @patch('module 名字. 方法名')
- @patch.object(类名, '方法名')
- # 装饰类演示
- from mock import Mock, patch
- # 单独的相乘函数
- def multiple(a, b):
- return a * b
- # 单独的捕获 Exception 函数
- def is_error():
- try:
- os.mkdir("11")
- return False
- except Exception as e:
- return True
- # 计算类, 包含 add 方法
- class calculator(object):
- def add(self, a, b):
- return a + b
- # 装饰类演示 - 单元测试类
- class TestProducer(unittest.TestCase):
- # case 执行前
- def setUp(self):
- self.calculator = calculator()
- # mock 一个函数, 注意也要指定 module
- @patch('mock_learn.multiple')
- def test_multiple(self, mock_multiple):
- mock_multiple.return_value = 3
- self.assertEqual(multiple(8, 14), 3)
- # mock 一个类对象的方法
- @patch.object(calculator, 'add')
- def test_add(self, mock_add):
- mock_add.return_value = 3
- self.assertEqual(self.calculator.add(8, 14), 3)
- # mock 调用方法返回多个不同的值
- @patch.object(calculator, 'add')
- def test_effect(self, mock_add):
- mock_add.side_effect = [1, 2, 3]
- self.assertEqual(self.calculator.add(8, 14), 1)
- self.assertEqual(self.calculator.add(8, 14), 2)
- self.assertEqual(self.calculator.add(8, 14), 3)
- # mock 的函数抛出 Exception
- @patch('os.mkdir')
- def test_exception(self, mkdir):
- mkdir.side_effect = Exception
- self.assertEqual(is_error(), True)
- # mock 多个函数, 注意函数调用顺序
- @patch.object(calculator, 'add')
- @patch('mock_learn.multiple')
- def test_more(self, mock_multiple, mock_add):
- mock_add.return_value = 1
- mock_multiple.return_value = 4
- self.assertEqual(self.calculator.add(3, 3), 1)
- self.assertEqual(multiple(3, 3), 4)
来源: http://www.bubuko.com/infodetail-3394606.html