mock测试
1. 简述
mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。
典型的应用场景:
- 当某个单元测试依赖另外一个函数,而这个函数还未开发完成,那么可以使用这个函数的mock对象来完成测试。
- 当某个接口测试依赖另一个接口,而这个接口未开发完成,或不方便调用(例如第三方的支付接口),那么可以使用mock服务模拟这个依赖接口来完成。
2. unittest.mock
from unittest.mock import create_autospec
# create_autospec:校验参数个数,再返回固定值;create_autospec方法替代Mock方法。
# 需要被mock的函数
def some_function(a, b, c):
"""
返回True或者False
"""
pass
mock_function = create_autospec(some_function, return_value=True)
mock_function(1,2,3)
True
3. 充值接口的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时可以看到
本文举例讲解unittest
中的mock
from unittest import mock
from pprint import pprint
pprint([i for i in dir(mock.Mock) if not ((i.startswith("_") or i.startswith("__")))])
输出
['assert_any_call',
'assert_called',
'assert_called_once',
'assert_called_once_with',
'assert_called_with',
'assert_has_calls',
'assert_not_called',
'attach_mock',
'call_args',
'call_args_list',
'call_count',
'called',
'configure_mock',
'mock_add_spec',
'mock_calls',
'reset_mock',
'return_value',
'side_effect']
Process finished with exit code 0
- 从上面代码种可以得到Mock类有哪些方法
- 在日常工作中只需要掌握
return_value
和side_effect
两个参数
在日常工作中,往往需要对第三方接口,或者开发还未开发的接口,或者有些需要扣费的接口,不让我们调的接口等等。
在接口测试当中,我们又需要调这些接口怎么办?
unittest
当中的Mock由此而生为我们解决这等问题。不让调的接口我们用假数据进行Mock掉。只要值符合接口文档即可。
- 新建一个
unittest_mock
文件夹 - 在此目录创建一个
payment.py
和test_pay.py
- 模拟用户支付接口调用第三方支付宝接口
第三方支付宝接口,我们往往不能掉,因为需要花钱啊,你难道花钱去测试?
payment.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/12/16 21:08
# @Author : shisuiyi
# @File : payment.py.py
# @Software: win10 Tensorflow1.13.1 python3.9
import requests
class Payment:
"""
定义第三方支付类
"""
def auth(self, card_num, amount):
"""
请求第三方外部支付接口的方法, 返回响应状态码
:param card_num: 卡号
:param amount: 金额
:return: 返回状态码, 200 代表支付成功, 500 代表支付异常, 失败
"""
url = "http://第三方支付url.payment"
data = {"card_num": card_num, "amount": amount} # 请求参数
self.res = requests.post(url, data=data)
return self.res.status_code # 返回的状态码
def pay(self, user_id, card_num, amount):
"""
支付方法
:param user_id: 用户ID
:param card_num: 卡号
:param amount: 支付金额
:return:
"""
try:
status_code = self.auth(card_num, amount)
except TimeoutError:
status_code = self.auth(card_num, amount) # 如果支付超时, 再请求一次
if status_code == 200:
print("[{}]支付[{}]成功!!! 进行扣款并登记支付记录".format(user_id, amount))
return "success"
elif status_code == 500:
print("[{}]支付[{}]失败!!! 不进行扣款".format(user_id, amount))
return "Fail"
- 从上面可以看出
auth
是第三方接口,我们如何mock掉,怎么测试呢?
test_pay.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/12/16 21:09
# @Author : shisuiyi
# @File : test_pay.py.py
# @Software: win10 Tensorflow1.13.1 python3.9
import unittest
from unittest import mock
from mockTest.payment import Payment
class PaymentTest(unittest.TestCase):
"""
测试支付接口
"""
def setUp(self):
self.payment = Payment()
def test_1_success(self):
"""
测试支付成功
:return:
"""
self.payment.auth = mock.Mock(return_value=200)
res = self.payment.pay(user_id=1001, card_num=12345678, amount=5000000)
self.assertEqual('success', res)
def test_2_fail(self):
"""
测试支付失败
:return:
"""
self.payment.auth = mock.Mock(return_value=500)
res = self.payment.pay(user_id=1001, card_num=12345678, amount=5000000)
self.assertEqual('Fail', res)
def test_3_retry_success(self):
"""
测试调用第三方接口超时之后, 再次支付成功
:return:
"""
self.payment.auth = mock.Mock(side_effect=[TimeoutError, 200])
res = self.payment.pay(user_id=1001, card_num=12345678, amount=5000000)
self.assertEqual('success', res)
def test_4_retry_fail(self):
"""
测试调用第三方接口超时之后, 再次支付失败
:return:
"""
# side_effect 第一次会得到列表第一个值抛出TimeoutError的异常,抛出异常payment.py里会再次调用第
# 三方支付接口auth,接着将列表第2个值 500返回给auth,pay函数里面代码判断 500 为失败 Fail.
# side_effect 参数可以等于另外一个函数 函数的返回值,作为Mock函数的返回值,例子这里就不举了。
self.payment.auth = mock.Mock(side_effect=[TimeoutError, 500])
res = self.payment.pay(user_id=1001, card_num=12345678, amount=5000000)
self.assertEqual('Fail', res)
if __name__ == '__main__':
unittest.main()
我们需要测的场景,一般有支付成功, 支付失败。支付超时,回调之后支付成功。支付超时,回调支付失败。其他网络异常支付成功未扣款这里暂时不讨论。先弄懂mock怎么去干这些事的。
self.payment.auth = mock.Mock(return_value=200)
return_value的值直接作为要mock掉的函数auth的返回值,之后无论在哪里调用,返回值都是200
side_effect=[TimeoutError, 200]
分两次给mock掉的函数auth传值,第一次传一个异常TimeoutError,第一次传 200给auth,请看pay函数的代码,超时会再掉一次。
最后跑一下单元测试 看看结果
test_pay.py::PaymentTest::test_2_fail
test_pay.py::PaymentTest::test_3_retry_success
test_pay.py::PaymentTest::test_4_retry_fail
============================== 4 passed in 0.20s ==============================
Process finished with exit code 0
PASSED [ 25%][1001]支付[5000000]成功!!! 进行扣款并登记支付记录
PASSED [ 50%][1001]支付[5000000]失败!!! 不进行扣款
PASSED [ 75%][1001]支付[5000000]成功!!! 进行扣款并登记支付记录
PASSED [100%][1001]支付[5000000]失败!!! 不进行扣款
mock装饰器
一共两种格式
- @patch('module名字.方法名')
- @patch.object(类名, '方法名')
1 # 装饰类演示
2 from mock import Mock, patch
3
4
5 # 单独的相乘函数
6 def multiple(a, b):
7 return a * b
8
9
10 # 单独的捕获Exception函数
11 def is_error():
12 try:
13 os.mkdir("11")
14 return False
15 except Exception as e:
16 return True
17
18
19 # 计算类,包含add方法
20 class calculator(object):
21 def add(self, a, b):
22 return a + b
23
24
25 # 装饰类演示 - 单元测试类
26 class TestProducer(unittest.TestCase):
27
28 # case执行前
29 def setUp(self):
30 self.calculator = calculator()
31
32 # mock一个函数,注意也要指定module
33 @patch('mock_learn.multiple')
34 def test_multiple(self, mock_multiple):
35 mock_multiple.return_value = 3
36 self.assertEqual(multiple(8, 14), 3)
37
38 # mock一个类对象的方法
39 @patch.object(calculator, 'add')
40 def test_add(self, mock_add):
41 mock_add.return_value = 3
42 self.assertEqual(self.calculator.add(8, 14), 3)
43
44 # mock调用方法返回多个不同的值
45 @patch.object(calculator, 'add')
46 def test_effect(self, mock_add):
47 mock_add.side_effect = [1, 2, 3]
48 self.assertEqual(self.calculator.add(8, 14), 1)
49 self.assertEqual(self.calculator.add(8, 14), 2)
50 self.assertEqual(self.calculator.add(8, 14), 3)
51
52 # mock的函数抛出Exception
53 @patch('os.mkdir')
54 def test_exception(self, mkdir):
55 mkdir.side_effect = Exception
56 self.assertEqual(is_error(), True)
57
58 # mock多个函数,注意函数调用顺序
59 @patch.object(calculator, 'add')
60 @patch('mock_learn.multiple')
61 def test_more(self, mock_multiple, mock_add):
62 mock_add.return_value = 1
63 mock_multiple.return_value = 4
64 self.assertEqual(self.calculator.add(3, 3), 1)
65 self.assertEqual(multiple(3, 3), 4)
4. Mock服务
在接口测试的时候,还有一种mock服务。
本质上mock服务就是一个web应用程序,可以自定义接口地址,发送参数,以及响应 结果,实现技术简单。
目前市面上有无数产品,大多数都可以免费使用。