Python Mock模块基础用法与单元测试实战
本文详细介绍Python中Mock模块的基本使用方法。Mock已内置于Python3的unittest框架,无需单独安装。文章通过具体案例演示如何利用Mock解决依赖问题、测试未完成功能及模拟复杂接口,重点解析return_value与side_effect参数的区别与应用,帮助开发者掌握高效的单元测试技巧。
Mock模块概述
Mock简介
在早期版本中,Mock是Python的一个第三方库。
自Python 3起,Mock模块已整合至unittest测试框架内,用户无需再单独进行安装操作。
从英文语义来看,Mock意为“模拟”,这暗示了该库的核心功能在于对某些对象进行模拟。
准确而言,Mock是Python中专门用于支持单元测试的库,其核心机制是通过Mock对象替换特定的Python对象,从而模拟该对象的行为。
鉴于Mock已集成到unittest框架中,可以推断其设计初衷是为了提升测试的效率与便利性。
Mock的核心作用
Mock的主要作用
1. 解决依赖问题:在测试某个接口或功能模块时,若该模块依赖于其他尚未开发完成的接口或模块,我们可以利用Mock来模拟这些被依赖的部分,从而完成对目标接口的测试。
通过Mock模拟被依赖的接口,可以确保目标接口的测试不受外部依赖进度的影响。
2. 单元测试:当某个功能尚未开发完成,但需要预先编写测试用例时,可以先使用Mock模拟该功能,以便提前进行代码测试。
3. 模拟复杂业务接口:在实际测试中,若目标接口依赖于极其复杂的第三方业务接口,可以直接使用Mock来模拟该复杂接口的行为。
这一做法的原理与解决接口依赖问题相同,旨在隔离测试环境。
4. 前后端联调:对于前端开发人员,若需根据后台返回的状态展示不同页面,但后台接口尚未开发完成,前端无需停滞工作。
此时,前端可以借助Mock模拟后台接口返回预期的数据,从而继续开发前端功能。
Mock的安装与导入
Mock的安装
对于Python 3,由于Mock模块已整合至unittest框架中,使用时只需在文件开头通过from unittest import mock导入即可。
若使用Python 2,则需先执行pip install mock进行安装,随后再import mock。
Mock基础实例
Mock实例
如何测试尚未开发完成的功能?
假设我们需要为一个实现两数相加的功能编写测试用例,但开发进度缓慢,仅搭建了简单框架,内部逻辑尚未实现。
"""
------------------------------------
@Time : 2019/6/26 14:09
@Auth : linux超
@File : ClassFunc.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import unittest
from unittest import mock
class SubClass(object):
def add(self, a, b):
"""两个数相加"""
pass
class TestSub(unittest.TestCase):
"""测试两个数相加用例"""
def test_sub(self):
sub = SubClass() # 初始化被测函数类实例
sub.add = mock.Mock(return_value=10) # mock add方法 返回10
result = sub.add(5, 5) # 调用被测函数
self.assertEqual(result, 10) # 断言实际结果和预期结果
if __name__ == '__main__':
unittest.main()
测试结果
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Process finished with exit code
测试结果显示,测试用例执行已通过。
实际上,Mock模拟add方法的原理是:使用相同的对象方法接收Mock对象(例如sub.add接收),当调用该Mock对象(sub.add())时,会返回return_value参数所对应的数据。
从表象上看,这似乎模拟了add方法的行为(此为个人理解,如有偏差请忽略)。
你可以进行实验,将用例中的add方法名改为其他名称,测试同样可以通过。
接下来继续探讨。
假设测试用例编写完毕,且开发人员也完成了功能实现(此处可能引发吐槽),既然真实功能已可测试,我们如何在此基础上直接测试真实功能?
如何测试完整功能?
我们对测试代码稍作修改。
"""
------------------------------------
@Time : 2019/6/26 14:09
@Auth : linux超
@File : ClassFunc.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import unittest
from unittest import mock
class SubClass(object):
def add(self, a, b):
"""两个数相加"""
return a + b
class TestSub(unittest.TestCase):
"""测试两个数相加"""
def test_sub(self):
sub = SubClass() # 初始化被测函数类实例
sub.add = mock.Mock(return_value=10, side_effect=sub.add) # 传递side_effect关键字参数, 会覆盖return_value参数值, 使用真实的add方法测试
result = sub.add(5, 11) # 真正的调用被测函数
self.assertEqual(result, 16) # 断言实际结果和预期结果
if __name__ == '__main__':
unittest.main()
side_effect参数详解
side_effect参数
在代码中,我们为Mock方法添加了另一个关键字参数side_effect = sub.add。该参数与return_value的作用相反:当传递此参数时,return_value参数将失效,而side_effect生效。
此处传递的参数值为sub.add,即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
Process finished with exit code 1
当所有值被取完后,将会报错(这一点类似于生成器的原理)。
依赖关系的Mock测试
如何测试存在依赖关系的功能?
假设有一个场景:我们需要测试一个支付接口,但该接口依赖于另一个第三方支付接口。若我们暂时没有权限使用第三方接口,该如何测试自己的接口?
请看以下实例。
假设第三方接口与我们的支付接口定义如下。
"""
------------------------------------
@Time : 2019/6/26 15:09
@Auth : linux超
@File : PayMent.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import requests
class PayApi(object):
@staticmethod
def auth(card, amount):
"""
第三方支付接口
:param card: 卡号
:param amount: 支付金额
:return:
"""
pay_url = "http://www.zhifubao.com" # 第三方支付接口地址
data = {"card": card, "amount": amount}
response = requests.post(pay_url, data=data) # 请求第三方支付接口
return response # 返回状态码
def pay(self, user_id, card, amount):
"""
我们自己的支付接口
:param user_id: 用户id
:param card: 卡号
:param amount: 支付金额
:return:
"""
# 调用第三方支付接口
response = self.auth(card, amount)
try:
if response['status_code'] == '200':
print('用户{}支付金额{}成功'.format(user_id, amount))
return '支付成功'
elif response['status_code'] == '500':
print('用户{}支付失败, 金额不变'.format(user_id))
return '支付失败'
else:
return '未知错误'
except Exception:
return "Error, 服务器异常!"
if __name__ == '__main__':
pass
很明显,第三方支付接口无法访问,因为接口地址是我自定义的,旨在模拟实际中无法使用的第三方接口场景。
编写测试用例
"""
------------------------------------
@Time : 2019/6/26 15:22
@Auth : linux超
@File : testpay.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import unittest
from unittest import mock
from payment.PayMent import PayApi
class TestPayApi(unittest.TestCase):
def test_success(self):
pay = PayApi()
pay.auth = mock.Mock(return_value={'status_code':'200'})
status = pay.pay('1000', '12345', '10000')
self.assertEqual(status, '支付成功')
def test_fail(self):
pay = PayApi()
pay.auth = mock.Mock(return_value={'status_code':'500'})
status = pay.pay('1000', '12345', '10000')
self.assertEqual(status, '支付失败')
def test_error(self):
pay = PayApi()
pay.auth = mock.Mock(return_value={'status_code':'300'})
status = pay.pay('1000', '12345', '10000')
self.assertEqual(status, '未知错误')
def test_exception(self):
pay = PayApi()
pay.auth = mock.Mock(return_value='200')
status = pay.pay('1000', '12345', '10000')
self.assertEqual(status, 'Error, 服务器异常!')
if __name__ == '__main__':
unittest.main()
测试输出结果
....用户1000支付失败, 金额不变
用户1000支付金额10000成功
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
Process finished with exit code 0
从执行结果可以看出,即使第三方支付接口无法使用,我们自己的支付接口测试仍然通过。
也许有人会质疑,既然第三方支付接口不可用,测试结果是否有效?
通常在测试某个模块时,我们可以假设其他模块功能正常,仅针对目标模块进行测试,这是完全合理的,因此测试结果也是正确的。
实际上,上述代码还可以使用另一种方式来编写。
Mock对象的方法
"""
------------------------------------
@Time : 2019/6/26 15:22
@Auth : linux超
@File : testpay.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import unittest
from unittest import mock
from unittest.mock import patch
from payment.PayMent import PayApi
class TestPayApi(unittest.TestCase):
def setUp(self):self.pay = PayApi()
@patch.object(PayApi, 'auth')
def test_success(self, mock_auth):
mock_auth.return_value = {'status_code':'200'}
status = self.pay.pay('1000', '12345', '10000')
self.assertEqual(status, '支付成功')
@patch.object(PayApi, 'auth')
def test_fail(self, mock_auth):
mock_auth.return_value={'status_code':'500'}
status = self.pay.pay('1000', '12345', '10000')
self.assertEqual(status, '支付失败')
@patch.object(PayApi, 'auth')
def test_error(self, mock_auth):
mock_auth.return_value={'status_code':'300'}
status = self.pay.pay('1000', '12345', '10000')
self.assertEqual(status, '未知错误')
@patch.object(PayApi, 'auth')
def test_exception(self, mock_auth):
mock_auth.return_value='200'
status = self.pay.pay('1000', '12345', '10000')
self.assertEqual(status, 'Error, 服务器异常!')
if __name__ == '__main__':
unittest.main()
此外,还可以Mock普通函数、Mock多个方法等,此处不再赘述,写法与上述实例类似。
最后
Mock还有很多自带的功能方法。
Mock功能强大,非一两句话能说完。本文主要介绍了Mock的基本使用方法,相对简单。对于实际应用中如何掌握更强大的方法,还需读者自行摸索。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













