1、Mock实现原理与实现机制
在某些时候,后端在开发接口的时候,处理逻辑非常复杂,在测试的时候,后端在未完成接口的情况下该如何去测试呢?
1)什么是Mock?
Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西。准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉指定的Python对象,以达到模拟对象的行为。
2)为什么要使用Mock?
之所以使用mock测试,是因为真实场景很难实现或者短期实现起来很困难。
主要场景有:
3)Mock对象适用场景
需要将当前被测单元和其依赖模块独立开来,构造一个独立的测试环境,不关注被测单元的依赖对象,只关注被测单元的功能逻辑。
比如被测代码中需要依赖第三方接口返回值进行逻辑处理,可能因为网络或者其他环境因素,调用第三方经常会中断或者失败,无法对被测单元进行测试,这个时候就可以使用mock技术来将被测单元和依赖模块独立开来,使得测试可以进行下去。
场景如下:
被测单元依赖的模块尚未开发完成,而被测单元需要依赖模块的返回值进行后续处理。
前后端项目中,后端接口开发完成之前,接口联调;
依赖的上游项目的接口尚未开发完成,需要接口联调测试;
比如service层的代码中,包含对Dao层的调用,但是,DAO层代码尚未实现,被测单元依赖的对象较难模拟或者构造比较复杂。
比如,支付宝支付的异常条件有很多,但是模拟这种异常条件很复杂或者无法模拟,比如,查询聚划算的订单结果,无法在测试环境进行模拟。
4)Mock测试存在的问题
使用Mock测试有时可以提高团队的开发效率,但当B、C都开发完成代码后,这时应该把E2E测试代码从使用Mock测试改为调用真实的模块,以避免出现模块之间集成部分漏测的问题。
这里说mock存在的问题,主要是让开发和测试不要过分的依赖/相信mock接口。
使用mock时,切记的几点:
测试人员不应该被覆盖率高的E2E自动化测试所迷惑,覆盖率高不代表没有问题。尤其在接手新项目中,需要查看E2E测试中有没有使用Mock测试,进一步去判断这些地方使用Mock测试是否合理,这些Mock测试是否应该换成真实模块间的调用和集成。
当把mock接口换成实际接口后,测试/开发也必须把之前的测试重新做一遍。
ps: 当你使用mock接口来提高效率,请注意:你的工作量其实是比 直接只用实际接口 多了 一倍的。
如果测试时,偷懒,替换成实际接口后,只是简单测试,那么 当实际接口和mock预期接口有差异时,故障便和你相遇了。
建议: mock接口只能主流程联调/ 异常返回测试,不要过分依赖mock接口进行测试。
测试完毕,上线前,请一定确保 为了mock而做的相关代码/配置文件的修改,已经完全恢复了。
建议:上线checklist中条条列出,并上线前review
2、Mock的使用
1)如何使用mock
思路:
通过代码制造假的输出(结果)
通过代码去模拟假的接口返回数据(模拟的是:访问真实接口的过程就可以省略)
2)Mock的安装和导入
在Python 3.3以前的版本中,需要另外安装mock模块,可以使用pip命令来安装:
pip install mock
然后在代码中就可以直接import进来:
import mock
从Python 3.3开始,mock模块已经被合并到标准库中,被命名为unittest.mock,可以直接import进来使用:
from unittest import mock
mock的本质:
就算接口未开发完,依据约定好的格式要求,进行数据和对象的模拟
摆脱环境问题,如测试服务器可能很不好搭建,或者搭建效率很低。
举个栗子:
基本代码
import requests
url = 'http://localhost:8090/login'
data = {
"username":"xiaoqiang",
"password":"1"
}
res=requests.post(url,data).json()
print(res)
接口的呈现角度看应该是个方法,更合理,那么就有了如下:
import requests
url = 'http://localhost:8090/login'
data = {
"username": "xiaoqiang",
"password": "1"
}
def requests_post(url, data):
res = requests.post(url, data).json()
print(res)
return res
其实本质来看,就是要模拟的是返回值
3)使用mock进行数据模拟
示例代码
# -*- coding: utf-8 -*-
import unittest
import mock
import requests
def post_request(url, data):
"""
POST请求
"""
res = requests.post(url, data).json()
print(res)
return res
def get_request(url):
"""
get请求,返回code码
"""
res = requests.get(url).status_code
print(res)
return res
class TestCase(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
def test_case01(self):
"""
mock测试案例1
"""
url = 'http://localhost:8090/login'
# 跟开发约定好的返回示例
data = {
"username": "xiaoqiang",
}
# 模拟返回的值
mock_test = mock.Mock(return_value=data)
# 执行结果的值
post_request = mock_test
# 本质我们想模拟的就是他呀!
# res=post_request(url, data)
# 这么写将会被执行,要去掉方法
# res = post_request(url, data)
# 正确写法
res = post_request
self.assertEqual("111", res())
def test_case02(self):
"""
mock测试案例2
"""
# 模拟返回的值
mock_test =mock.Mock(return_value='200')
# 执行结果的值
get_request = mock_test
res=get_request
self.assertEqual('200',res())
@classmethod
def tearDownClass(cls) -> None:
super().tearDownClass()
if __name__ == '__main__':
unittest.main()
3、mockrunner的使用
1)启动服务
java -jar moco-runner-0.12.0-standalone.jar http -p 8008 -c config.json
效果:
2)配置config.json文件
写好配置config.json文件,就可以进行mock数据啦,示例如下:
[
{
"response": {
"text": "This is moco demo!"
}
},
{
"description": "带参数的get请求示例",
"request": {
"uri": "/getwithparam",
"method": "get",
"queries": {
"name": "ztt",
"age": "18"
}
},
"response": {
"text": "ztt come back"
}
},
{
"description": "带参数的post请求示例",
"request": {
"uri": "/postwithparam",
"forms": {
"name": "ztt",
"age": "18"
}
},
"response": {
"json":{
"name": "ztt",
"message": "ztt is coming"
}
}
}
]
访问示例:http://localhost:8008/getwithparam?name=ztt&age=18
注意:保存即是热部署
4、Mock在单元测试中的实际使用
1)requests的封装
示例代码
# -*- coding: utf-8 -*-
import json
import sys
import requests
import os
base_path = os.getcwd()
base_path = os.path.dirname(base_path)
sys.path.append(base_path)
host = 'http://localhost:8090/'
class BaseRequest:
@staticmethod
def send_post(url, data, **kwargs):
"""
发送POST请求
@param url: 请求路径
@param data: 请求参数
@param kwargs: 多参
@return: 响应信息
"""
res = requests.post(host + url, headers=data, **kwargs).text
return res
@staticmethod
def send_get(url, data, **kwargs):
"""
发送GET请求
@param url: 请求路径
@param data: 请求参数
@param kwargs: 多参
@return: 响应信息
"""
res = requests.get(host + url, params=data, **kwargs).text
return res
@staticmethod
def send_put(url, data, **kwargs):
"""
发送PUT请求
@param url: 请求路径
@param data: 请求参数
@param kwargs: 多参
@return: 响应信息
"""
res = requests.put(host + url, params=data, **kwargs).text
return res
@staticmethod
def send_delete(url, data, **kwargs):
"""
发送DELETE请求
@param url: 请求路径
@param data: 请求参数
@param kwargs: 多参
@return: 响应信息
"""
res = requests.delete(host + url, params=data, **kwargs).text
return res
@staticmethod
def read_json():
"""
读取json文件
@return: json文件内容
"""
with open(base_path + "\\config\\user.json", encoding='utf-8') as f:
data = json.load(f)
return data
@staticmethod
def get_jsonvalue(caseName, paramName):
"""
获取配置文件中入参值
@param caseName: 用例方法
@param paramName: 用例请求参数名字
@return: 请求参数值
"""
json_str = request.read_json()
request_param = json_str[caseName]
request_param[paramName]
def run_main(self, method, url, data, **kwargs):
"""
执行方法传url
@param method: 请求方法
@param url: 请求路径
@param data:请求参数
@param kwargs: 多参
@return: 响应信息
"""
if method == "get":
res = self.send_get(url, data, **kwargs)
elif method == "post":
res = self.send_post(url, data, **kwargs)
elif method == "put":
res = self.send_put(url, data, **kwargs)
else:
res = self.send_delete(url, data, **kwargs)
try:
res = json.loads(res)
except:
print("不是json,只是")
return res
request = BaseRequest()
if __name__ == '__main__':
json_str = request.read_json()
json_str = json_str['test_getStudents']
print(json_str['method'])
2、结合封装mock的实际应用
示例代码
# -*- coding: utf-8 -*-
import unittest
import mock
from base.base_request import request
config = request.read_json()
class TestCase(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
def test_getStudents(self):
"""
测试get请求获取学生接口
"""
param = config['test_getStudents']
method = param['method']
url = param['url']
data = param['data']
# 模拟的请求方法
mock_method = mock.Mock(return_value=data)
request.run_main = mock_method
res = request.run_main(method, url, None)
print(res)
self.assertEqual(res['msg'], 'success')
def test_studentAdd(self):
"""
入参为json,带请求头的post请求
:return:
"""
param = config['test_studentAdd']
method = param['method']
url = param['url']
headers = param['headers']
request_body = param['request_body']
data = param['data']
# 模拟的请求方法
request.run_main = mock.Mock(return_value=data)
res = request.run_main(method, url, headers, json=request_body)
print(res)
self.assertEqual(res['msg'], 'success')
def test_upload(self):
"""
上传文件接口
:return:
"""
param = config['test_upload']
method = param['method']
url = param['url']
data = param['data']
file = open('D:\\data.xls', 'rb')
files = {'file': file}
# 模拟方法调用
request.run_main = mock.Mock(return_value=data)
res = request.run_main(method, url, None, files=files)
file.close()
print(res)
self.assertEqual(res['msg'], '上传文件成功!')
def test_studentUpdate(self):
"""
put请求示例
:return:
"""
param = config['test_studentUpdate']
method = param['method']
url = param['url']
params = param['params']
data = param['data']
# 模拟方法调用
request.run_main = mock.Mock(return_value=data)
res = request.run_main(method, url, params)
print(res)
self.assertEqual(res['msg'], 'success')
def test_studentDelete(self):
"""
删除接口
:return:
"""
param = config['test_studentDelete']
method = param['method']
url = param['url']
data = param['data']
# 模拟方法调用
request.run_main = mock.Mock(return_value=data)
res = request.run_main(method, url, None)
print(res)
self.assertEqual(res['msg'], 'success')
@classmethod
def tearDownClass(cls) -> None:
super().tearDownClass()
下面是我整理的2023年最全的软件测试工程师学习知识架构体系图 |
成功是需要付出辛勤汗水的,但是只要我们坚信自己有能力实现梦想,勇往直前,不畏艰难,坚持不懈,就一定能够取得成功的喜悦。
不要因为一次失败而灰心丧气,失去信心。相反,要更加坚强,更有勇气,更加坚定地向前走,相信自己能够创造无限可能。
成功的秘诀在于坚持不懈,只有通过不断的磨练和提升自己,才能够真正地取得成功。要始终保持积极的态度,勇往直前,为自己而战。