python_Mock基本使用
## 1.mock简介
1. py3已将mock集成到unittest库中
2. 为的就是更好的进行单元测试
3. 简单理解,模拟接口返回参数
4. 通俗易懂,直接修改接口返回参数的值
5. 官方文档:https://docs.python.org/zh-cn/3/library/unittest.mock.html
## 2.mock作用
**解决依赖问题,达到解耦作用**
当我们测试某个**目标接口(模块)时**,该**接口依赖其他接口**,当被依赖的接口未开发完成时,可以用**mock模拟被依赖接口**,完成目标接口的测试
## 模拟复杂业务的接口
当我们测试某个**目标接口(模块)**,该接口**依赖一个非常复杂的接口**时****,可以**用mock来模拟这个复杂的业务接口**;也解决接口依赖一样的原理
## 单元测试
如果某个接口(模块)未开发完成时,又需要编写测试用例,则可以通过mock模拟该接口(模块)进行测试
## 前后端联调
前端开发的页面需要根据后端返回的不同状态码展示不同的页面,当后端接口未开发完成时,也可通过mock来模拟后端接口返回自己想要的数据
## 3.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时可以看到
## 4.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(return_value=15, side_effect=sub.add) # 调用被测函数 result = sub.add(5, 5) # 断言实际结果和预期结果 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)
也许有小伙伴会问,第三方支付都不能用,我们的测试结果是否是有效的呢?
通常在测试一个模块的时候,是可以认为**其他模块的功能是正常的,只针对目标模块进行测试是没有任何问题的**,所以说测试结果也是正确的
@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)