在这之前我完成了对于接口上的自动化测试:ApiTesting全链路接口自动化测试框架 - 初版(一)

但是对于很多公司而言,数据库的数据校验也尤为重要,另外也有小伙伴给我反馈希望支持。

所以最近几天我特意抽空完成了相关的内容开发,另外修复了第一版中一些小的bug,以下是相关升级说明。


主要升级内容

1、新增数据库查询类封装:目前主要包括MySQL、HBase、Solr、ES,且均使用SQL语法。

2、新增数据库配置文件dbConfig.yml

PyDemo:
# 数据库查询超时时长(不得小于1)
timeout: 3
# MySQL配置信息
mysql_info:
address: 10.88.88.88:3160
db: test
user: test
auth: test
# HBase配置信息(需要启动phoenix查询服务)
hbase_info:
address: 10.88.88.88:8765
db: test
# ES配置信息(需要开放http查询服务)
es_info:
address: 10.88.88.88:9200
db: test
# Solr配置信息
solr_info:
address: 10.88.88.88:8883

3、新增数据库查询方法二次封装:主要读取数据库配置,以及在指定超时时间循环查询结果(Redis由于其结果多样性,暂不提供支持)。

必须满足正则表达式 ^select (.*?) from (.*?) where (.*?)$ (注意大小写)

即以select开头  +  *(所有)或字段名 + from + 表名 + where + 条件 [ + and + 其他条件 ... ]

# -*- coding:utf-8 -*-
# @Time : 2021/03/09
# @Author : Leo Zhang
# @File : queryDatabase.py
# **************************
from comm.utils.readYaml import read_yaml_data
from config import DB_CONFIG, PROJECT_NAME
from comm.db import *
import logging
import time
import re dbcfg = read_yaml_data(DB_CONFIG)[PROJECT_NAME] def query_mysql(sql):
"""查询MySQL数据 :param sql: sql查询语句
:return:
"""
# 获取配置信息
timeout = dbcfg['timeout']
address = dbcfg['mysql_info']['address']
user = dbcfg['mysql_info']['user']
auth = dbcfg['mysql_info']['auth']
db = dbcfg['mysql_info']['db']
# 初始化MySQL
host, port = address.split(':')
mysql = MysqlServer(host, int(port), db, user, auth)
logging.info('执行查询>>> {}'.format(sql))
# 循环查询
for i in range(int(timeout)):
try:
result = mysql.query(sql, is_dict=True)
mysql.close()
if result:
return result
else:
time.sleep(1)
except Exception as e:
raise Exception('查询异常>>> {}'.format(e))
else:
return [] def query_hbase(sql):
"""查询HBase数据 :param sql: sql查询语句
:return:
"""
# 获取配置信息
timeout = dbcfg['timeout']
address = dbcfg['hbase_info']['address']
db = dbcfg['hbase_info']['db']
# 检索SQL语句
exp = r"^select .*? from (.*?) where .*?$"
table = re.findall(exp, sql.strip())[0]
# 添加数据库
if '.' not in table:
sql = sql.strip().replace(table, db+'.'+table)
# 初始化HBase
hbase = PhoenixServer(address)
logging.info('执行查询>>> {}'.format(sql))
# 循环查询
for i in range(int(timeout)):
try:
result = hbase.query(sql, is_dict=True)
if result:
return result
else:
time.sleep(1)
except Exception as e:
raise Exception('查询异常>>> {}'.format(e))
else:
return [] def query_es(sql):
"""查询ES数据 :param sql: sql查询语句
:return:
"""
# 获取配置信息
timeout = dbcfg['timeout']
address = dbcfg['es_info']['address']
db = dbcfg['es_info']['db']
logging.info('执行查询>>> {}'.format(sql))
# 循环查询
for i in range(int(timeout)):
try:
result = elastic_search(address, db, sql)
if result:
return result
else:
time.sleep(1)
except Exception as e:
raise Exception('查询异常>>> {}'.format(e))
else:
return [] def query_solr(sql):
"""查询solr数据 :param sql: sql查询语句
:return:
"""
# 获取配置信息
timeout = dbcfg['timeout']
address = dbcfg['solr_info']['address']
logging.info('执行查询>>> {}'.format(sql))
# 循环查询
for i in range(int(timeout)):
try:
result = search_solr(address, sql)
if result:
return result
else:
time.sleep(1)
except Exception as e:
raise Exception('查询异常>>> {}'.format(e))
else:
return []

4、更新校验代码:增加数据库字段处理、数据校验方法。

# -*- coding:utf-8 -*-
# @Time : 2021/2/2
# @Author : Leo Zhang
# @File : checkResult.py
# ***************************
import re
import allure
import operator
import logging
from decimal import Decimal
from comm.unit import readRelevance, replaceRelevance
from comm.unit import queryDatabase as qdb def check_json(src_data, dst_data):
"""
校验的json
:param src_data: 检验内容
:param dst_data: 接口返回的数据
:return:
"""
if isinstance(src_data, dict):
for key in src_data:
if key not in dst_data:
raise Exception("JSON格式校验,关键字 %s 不在返回结果 %s 中!" % (key, dst_data))
else:
this_key = key
if isinstance(src_data[this_key], dict) and isinstance(dst_data[this_key], dict):
check_json(src_data[this_key], dst_data[this_key])
elif not isinstance(src_data[this_key], type(dst_data[this_key])):
raise Exception("JSON格式校验,关键字 %s 返回结果 %s 与期望结果 %s 类型不符"
% (this_key, src_data[this_key], dst_data[this_key]))
else:
pass
else:
raise Exception("JSON校验内容非dict格式:{}".format(src_data)) def check_database(actual, expected, mark=''):
"""校验数据库 :param actual: 实际结果
:param expected: 期望结果
:param mark: 标识
:return:
"""
if isinstance(actual, dict) and isinstance(expected, dict):
result = list()
logging.info('校验数据库{}>>>'.format(mark))
content = '\n%(key)-20s%(actual)-40s%(expected)-40s%(result)-10s' \
% {'key': 'KEY', 'actual': 'ACTUAL', 'expected': 'EXPECTED', 'result': 'RESULT'}
for key in expected:
if key in actual:
actual_value = actual[key]
else:
actual_value = None
expected_value = expected[key]
if actual_value or expected_value:
if isinstance(actual_value, (int, float, Decimal)):
if int(actual_value) == int(expected_value):
rst = 'PASS'
else:
rst = 'FAIL'
else:
if str(actual_value) == str(expected_value):
rst = 'PASS'
else:
rst = 'FAIL'
else:
rst = 'PASS'
result.append(rst)
line = '%(key)-20s%(actual)-40s%(expected)-40s%(result)-10s' \
% {'key': key, 'actual': str(actual_value) + ' ',
'expected': str(expected_value) + ' ', 'result': rst}
content = content + '\n' + line
logging.info(content)
allure.attach(name="校验数据库详情{}".format(mark[-1]), body=str(content))
if 'FAIL' in result:
raise AssertionError('校验数据库{}未通过!'.format(mark)) elif isinstance(actual, list) and isinstance(expected, list):
result = list()
logging.info('校验数据库{}>>>'.format(mark))
content = '\n%(key)-25s%(actual)-35s%(expected)-35s%(result)-10s' \
% {'key': 'INDEX', 'actual': 'ACTUAL', 'expected': 'EXPECTED', 'result': 'RESULT'}
for index in range(len(expected)):
if index < len(actual):
actual_value = actual[index]
else:
actual_value = None
expected_value = expected[index]
if actual_value or expected_value:
if isinstance(actual_value, (int, float, Decimal)):
if int(actual_value) == int(expected_value):
rst = 'PASS'
else:
rst = 'FAIL'
else:
if str(actual_value) == str(expected_value):
rst = 'PASS'
else:
rst = 'FAIL'
else:
rst = 'PASS'
result.append(rst)
line = '%(key)-25s%(actual)-35s%(expected)-35s%(result)-10s' \
% {'key': index, 'actual': str(actual_value) + ' ',
'expected': str(expected_value) + ' ', 'result': rst}
content = content + '\n' + line
logging.info(content)
allure.attach(name="校验数据库详情{}".format(mark[-1]), body=str(content))
if 'FAIL' in result:
raise AssertionError('校验数据库{}未通过!'.format(mark)) else:
logging.info('校验数据库{}>>>'.format(mark))
logging.info('ACTUAL: {}\nEXPECTED: {}'.format(actual, expected))
if str(expected) != str(actual):
raise AssertionError('校验数据库{}未通过!'.format(mark)) def check_result(case_data, code, data):
"""
校验测试结果
:param case_data: 用例数据
:param code: 接口状态码
:param data: 返回的接口json数据
:return:
"""
try:
# 获取用例检查信息
check_type = case_data['check_body']['check_type']
expected_code = case_data['check_body']['expected_code']
expected_result = case_data['check_body']['expected_result']
except Exception as e:
raise KeyError('获取用例检查信息失败:{}'.format(e)) # 接口数据校验
if check_type == 'no_check':
with allure.step("不校验接口结果"):
pass elif check_type == 'check_code':
with allure.step("仅校验接口状态码"):
allure.attach(name="实际code", body=str(code))
allure.attach(name="期望code", body=str(expected_code))
allure.attach(name='实际data', body=str(data))
if int(code) != expected_code:
raise Exception("接口状态码错误!\n %s != %s" % (code, expected_code)) elif check_type == 'check_json':
with allure.step("JSON格式校验接口"):
allure.attach(name="实际code", body=str(code))
allure.attach(name="期望code", body=str(expected_code))
allure.attach(name='实际data', body=str(data))
allure.attach(name='期望data', body=str(expected_result))
if int(code) == expected_code:
if not data:
data = "{}"
check_json(expected_result, data)
else:
raise Exception("接口状态码错误!\n %s != %s" % (code, expected_code)) elif check_type == 'entirely_check':
with allure.step("完全校验接口结果"):
allure.attach(name="实际code", body=str(code))
allure.attach(name="期望code", body=str(expected_code))
allure.attach(name='实际data', body=str(data))
allure.attach(name='期望data', body=str(expected_result))
if int(code) == expected_code:
result = operator.eq(expected_result, data)
if not result:
raise Exception("完全校验失败! %s ! = %s" % (expected_result, data))
else:
raise Exception("接口状态码错误!\n %s != %s" % (code, expected_code)) elif check_type == 'regular_check':
if int(code) == expected_code:
try:
result = ""
if isinstance(expected_result, list):
for i in expected_result:
result = re.findall(i.replace("\"", "\""), str(data))
allure.attach('校验完成结果\n', str(result))
else:
result = re.findall(expected_result.replace("\"", "\'"), str(data))
with allure.step("正则校验接口结果"):
allure.attach(name="实际code", body=str(code))
allure.attach(name="期望code", body=str(expected_code))
allure.attach(name='实际data', body=str(data))
allure.attach(name='期望data', body=str(expected_result).replace("\'", "\""))
allure.attach(name=expected_result.replace("\"", "\'") + '校验完成结果',
body=str(result).replace("\'", "\""))
if not result:
raise Exception("正则未校验到内容! %s" % expected_result)
except KeyError:
raise Exception("正则校验执行失败! %s\n正则表达式为空时" % expected_result)
else:
raise Exception("接口状态码错误!\n %s != %s" % (code, expected_code)) else:
raise Exception("无该接口校验方式%s" % check_type) # 判断是否存在数据库校验标识
if 'check_db' in case_data:
check_db = case_data['check_db']
# 获取数据库期望结果:获取期望结果-获取关联值-替换关联值
data['parameter'] = case_data['parameter']
__relevance = readRelevance.get_relevance(data, check_db)
check_db = replaceRelevance.replace(check_db, __relevance) # 循环校验数据库
for each in check_db:
try:
check_type = each['check_type']
execute_sql = each['execute_sql']
expected_result = each['expected_result']
except KeyError as e:
raise KeyError('【check_db】存在错误字段!\n{}'.format(e))
except TypeError:
raise KeyError("【check_db】类型错误,期望<class 'list'>,而不是%s!" % type(expected_result))
if not isinstance(expected_result, list):
raise KeyError("【expected_result】类型错误,期望<class 'list'>,而不是%s!" % type(expected_result)) # 检索SQL语句
exp = r"^select (.*?) from (.*?) where (.*?)$"
res = re.findall(exp, execute_sql.strip())[0]
for r in res:
if not each:
msg = '标准格式: ' + exp
raise Exception('无效SQL>>> {}\n{}'.format(execute_sql, msg))
# 判断数据库检查类型
if check_type == 'mysql':
actual = qdb.query_mysql(execute_sql)
elif check_type == 'hbase':
actual = qdb.query_hbase(execute_sql)
elif check_type == 'solr':
actual = qdb.query_solr(execute_sql)
elif check_type == 'es':
actual = qdb.query_es(execute_sql)
else:
raise Exception("无该数据库校验方式%s" % check_type) # 增加输出并进行数据校验
mark = check_type.replace('check_', '').upper() + '['+res[1]+']'
with allure.step("校验数据库{}".format(mark)):
allure.attach(name="实际结果", body=str(actual))
allure.attach(name='期望结果', body=str(expected_result))
# expected_num = each['expected_num']
# allure.attach(name="实际行数", body=str(len(actual)))
# allure.attach(name='期望行数', body=str(expected_num))
# # 验证数据库实际结果数量是否正确
# if len(actual) != int(expected_num):
# raise AssertionError('校验数据库{}行数未通过!'.format(mark))
# 检查实际结果中第一条结果值 ***************
for index, expected in enumerate(expected_result):
try:
check_database(actual[index], expected, mark+str(index))
except IndexError:
raise IndexError('校验数据库{}失败,期望结果超出实际条目!'.format(mark+str(index)))

5、更新测试用例:新增数据库校验字段,默认无,需自行添加。

test_info:
title: perRelated
host: ${host}
scheme: http
method: POST
address: /api/perRelated/addAudltCard
mime_type: application/x-www-form-urlencoded
headers: ${headers}
timeout: 10
file: false
cookies: false
premise: false
test_case:
- summary: addAudltCard
describe: test_addAudltCard
parameter: addAudltCard_request.json
check_body:
check_type: check_json
expected_code: 200
expected_result: addAudltCard_response.json  # 新增数据库检查标识,要求必须为列表类型,以支持多类型多表校验。
check_db:
   # 检查类型,目前支持一下三种
- check_type: mysql
# 执行sql语句,请遵循格式要求,可使用接口返回作为关联值。
execute_sql: select * from TD_ADULT where ADULT_CODE='${adultCode}'
   # 期望结果,要求必须为列表类型,以支持多条结果校验,且排序与sql查询结果一致,期望结果条数必须小于等于实际结果,期望结果字段数也必须小于等于实际结果。
expected_result:
- ADULT_CODE: ${adultCode}
ADULT_NAME: AUTO99
ADULT_SEX: 1
ADULT_BIRTHDAY: 2015-03-03
ADULT_MOBILE: 19999999999
- check_type: es
execute_sql: select * from adult where CHIL_NAME='AUTO99'
  # 多条结果校验,注意排序需要与实际结果一致。
expected_result:
- CHIL_NAME: AUTO99
CHIL_SEX: 1
CHIL_MOBILE: 19999999999
- CHIL_NAME: AUTO99
CHIL_SEX: 1
CHIL_MOBILE: 19999999999
- check_type: solr
execute_sql: select * from adultsolr320000 where adultName='AUTO99'
expected_result:
- adultName: AUTO99
adultSex: 1
adultMobile: 19999999999
- check_type: hbase
execute_sql: select * from TD_ADULT_YZ where ADULT_CODE=3202112002803000001
expected_result:
- ADULT_CODE: 3202112002803000001

6、测试报告展示

数据库校验展开详情

运行日志示例

C:\Python37\python.exe E:/__SVN__/Auto_Test_Jm/ApiTesting/startup.py
2021-03-12 15:51:37,543 - startup.py - INFO: 不开启自动生成测试用例功能,将直接运行测试!
============================= test session starts =============================
platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 -- C:\Python37\python.exe
cachedir: .pytest_cache
rootdir: E:\__SVN__\Auto_Test_Jm\ApiTesting
plugins: allure-pytest-2.8.18, assume-2.3.3, cov-2.10.1, html-3.0.0, rerunfailures-9.1.1, xdist-2.1.0
collecting ... collected 6 items / 5 deselected / 1 selected PyDemo/testcase/perRelated/test_addAudltCard.py::TestPerrelated::test_addAudltCard[case_data0] 2021-03-12 15:51:37,986 - apiSend.py - INFO: ======================================================================================================================================================
2021-03-12 15:51:37,986 - apiSend.py - INFO: 请求接口:addAudltCard
2021-03-12 15:51:37,986 - apiSend.py - INFO: 请求地址:http://10.88.88.108:30131/api/perRelated/addAudltCard
2021-03-12 15:51:37,986 - apiSend.py - INFO: 请求头: {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
2021-03-12 15:51:37,986 - apiSend.py - INFO: 请求参数: {'params': {'adultAddressregion': '3202000000', 'adultAddressdistrict': 'AUTO99', 'adultCountryCode': '156', 'adultNationCode': '1001', 'adultNoType': '2', 'adultHabiCode': '32021100', 'unitCode': '3202112002', 'adultAccountCode': 1002, 'adultResiCode': 1, 'adultNo': '82389223', 'adultName': 'AUTO99', 'adultSex': '1', 'adultBirthday': '2015-03-03', 'adultMobile': '19999999999', 'adultHabiaddress': 'AUTO99', 'adultArtimove': 0, 'adultIfdead': 0, 'adultHereCode': 1001, 'adultCreatesite': '3202112002', 'adultCreatemanName': '马山山', 'adultCreateman': '3202112002008', 'adultCreatesiteName': '马山社区'}, 'headtoken': 'xu5YwIZFkVGczMn0H0rot2ps7zRIbvrTHNwMXx1sJXg='}
2021-03-12 15:51:37,986 - apiSend.py - INFO: 请求方法: POST
2021-03-12 15:51:38,302 - apiSend.py - INFO: 请求接口结果:
(200, {'callTime': '2021-03-12 15:49:35', 'code': None, 'data': {'adultInforms': [], 'adultInquireBean': None, 'inocStationList': [], 'refuseinocList': [], 'tdAdult': {'accountName': '本县', 'adultAccountCode': 1002, 'adultAccountStr': None, 'adultAddressdistrict': 'AUTO99', 'adultAddressregion': '3202000000', 'adultAparetel': None, 'adultApptime': None, 'adultArtimove': 0, 'adultBirthday': '2015-03-03 00:00:00', 'adultBirthdaystr': None, 'adultCode': '3202112002815000012', 'adultCodeStr': None, 'adultCountryCode': '156', 'adultCreatedate': '2021-03-12 15:49:35', 'adultCreateman': '3202112002008', 'adultCreatemanName': '马山山', 'adultCreatemanNo': None, 'adultCreatesite': '3202112002', 'adultCreatesiteName': '马山社区', 'adultCurdepartment': '3202112002', 'adultCurdepartmentName': '马山社区', 'adultDeaddate': None, 'adultDelmark': 0, 'adultDeltime': None, 'adultEduCode': None, 'adultEwmCard': None, 'adultFatherno': None, 'adultFathernoType': None, 'adultGuard': None, 'adultHabiCode': '32021100', 'adultHabiStr': None, 'adultHabiaddress': 'AUTO99', 'adultHereCode': 1001, 'adultHereStr': None, 'adultIfapp': 0, 'adultIfdead': 0, 'adultIfwxin': 0, 'adultJkdaCode': None, 'adultJobCode': None, 'adultLeavedate': '2021-03-12 15:49:35', 'adultLock': 0, 'adultMarry': None, 'adultMobile': '19999999999', 'adultMotherno': None, 'adultMothernoType': None, 'adultName': 'AUTO99', 'adultNationCode': '1001', 'adultNo': '82389223', 'adultNoType': '2', 'adultRelCode': None, 'adultRemark': None, 'adultResiCode': 1, 'adultResiStr': None, 'adultSchCode': None, 'adultSchName': None, 'adultSex': '1', 'adultTypeCode': None, 'adultWxintime': None, 'age': 6, 'createDate': '2021-03-12 15:49:35', 'createManCode': '3202112002008', 'empCode': None, 'habiName': '滨湖区', 'hasRefInoc': 0, 'hereName': '在册', 'ifInform': None, 'ifInquire': None, 'isqr': 0, 'modifyDate': '2021-03-12 15:49:35', 'modifyManCode': '3202112002008', 'modifyUnitCode': '3202112002', 'moveDate': None, 'photoUrl': None, 'resiName': '常住', 'showPhotoUrl': None, 'sysEditDate': None, 'type': None, 'unitCode': None, 'unitSimpname': None}, 'tdAdultInoculation': [], 'varIndex': []}, 'msg': '返回成功', 'success': True})
2021-03-12 15:51:39,326 - queryDatabase.py - INFO: 执行查询>>> select * from TD_ADULT where ADULT_CODE='3202112002815000012'
2021-03-12 15:51:41,362 - checkResult.py - INFO: 校验数据库MYSQL[TD_ADULT]0>>>
2021-03-12 15:51:41,362 - checkResult.py - INFO:
KEY ACTUAL EXPECTED RESULT
ADULT_CODE 3202112002815000012 3202112002815000012 PASS
ADULT_NAME AUTO99 AUTO99 PASS
ADULT_SEX 1 1 PASS
ADULT_BIRTHDAY 2015-03-03 2015-03-03 PASS
ADULT_MOBILE 19999999999 19999999999 PASS
2021-03-12 15:51:41,363 - queryDatabase.py - INFO: 执行查询>>> select * from adult where CHIL_NAME='AUTO99'
2021-03-12 15:51:41,369 - base.py - INFO: GET http://10.88.88.105:9200/jhmycr%40adult/_search?q=CHIL_NAME%3A%22AUTO99%22+ [status:200 request:0.005s]
2021-03-12 15:51:41,373 - checkResult.py - INFO: 校验数据库ES[adult]0>>>
2021-03-12 15:51:41,373 - checkResult.py - INFO:
KEY ACTUAL EXPECTED RESULT
CHIL_NAME AUTO99 AUTO99 PASS
CHIL_SEX 1 1 PASS
CHIL_MOBILE 19999999999 19999999999 PASS
2021-03-12 15:51:41,374 - checkResult.py - INFO: 校验数据库ES[adult]1>>>
2021-03-12 15:51:41,374 - checkResult.py - INFO:
KEY ACTUAL EXPECTED RESULT
CHIL_NAME AUTO99 AUTO99 PASS
CHIL_SEX 1 1 PASS
CHIL_MOBILE 19999999999 19999999999 PASS
2021-03-12 15:51:41,376 - queryDatabase.py - INFO: 执行查询>>> select * from adultsolr320000 where adultName='AUTO99'
2021-03-12 15:51:41,376 - querySolr.py - INFO: 执行查询>>> GET http://10.88.88.206:8883/solr/adultsolr320000/select?q=adultName:"AUTO99"
2021-03-12 15:51:41,400 - pysolr.py - INFO: Finished 'http://10.88.88.206:8883/solr/adultsolr320000/select/?q=adultName%3A%22AUTO99%22+&wt=json' (get) with body '' in 0.022 seconds, with status 200
2021-03-12 15:51:41,403 - checkResult.py - INFO: 校验数据库SOLR[adultsolr320000]0>>>
2021-03-12 15:51:41,403 - checkResult.py - INFO:
KEY ACTUAL EXPECTED RESULT
adultName AUTO99 AUTO99 PASS
adultSex 1 1 PASS
adultMobile 19999999999 19999999999 PASS
2021-03-12 15:51:41,413 - queryDatabase.py - INFO: 执行查询>>> select * from TEST.TD_ADULT_YZ where ADULT_CODE=3202112002803000001
2021-03-12 15:51:41,438 - checkResult.py - INFO: 校验数据库HBASE[TD_ADULT_YZ]0>>>
2021-03-12 15:51:41,438 - checkResult.py - INFO:
KEY ACTUAL EXPECTED RESULT
ADULT_CODE 3202112002803000001 3202112002803000001 PASS
PASSED ======================= 1 passed, 5 deselected in 3.67s =======================
Report successfully generated to E:\__SVN__\Auto_Test_Jm\ApiTesting\PyDemo\report\html Process finished with exit code 0

running.log


缺陷修复记录

作者:Leozhanggg

出处:https://www.cnblogs.com/leozhanggg/p/14522084.html

源码:https://github.com/Leozhanggg/ApiTesting

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

ApiTesting全链路接口自动化测试框架 - 新增数据库校验(二)的更多相关文章

  1. ApiTesting全链路接口自动化测试框架 - 实战应用

    场景一.添加公共配置 我们在做自动化开始的时候,一般有很多公共的环境配置,比如host.token.user等等,如果这些放在用例中,一旦修改,将非常的不便.麻烦(尤其切换环境). 所以这里我们提供了 ...

  2. ApiTesting全链路自动化测试框架 - 初版发布(一)

    简介 此框架是基于Python+Pytest+Requests+Allure+Yaml+Json实现全链路接口自动化测试. 主要流程:解析接口数据包 ->生成接口基础配置(yml) ->生 ...

  3. 基于python+Testlink+Jenkins实现的接口自动化测试框架V3.0

    基于python+Testlink+Jenkins实现的接口自动化测试框架V3.0 目录 1. 开发环境2. 主要功能逻辑介绍3. 框架功能简介 4. 数据库的创建 5. 框架模块详细介绍6. Tes ...

  4. 基于Python接口自动化测试框架+数据与代码分离(进阶篇)附源码

    引言 在上一篇<基于Python接口自动化测试框架(初级篇)附源码>讲过了接口自动化测试框架的搭建,最核心的模块功能就是测试数据库初始化,再来看看之前的框架结构: 可以看出testcase ...

  5. 基于Python接口自动化测试框架(初级篇)附源码

    引言 很多人都知道,目前市场上很多自动化测试工具,比如:Jmeter,Postman,TestLink等,还有一些自动化测试平台,那为啥还要开发接口自动化测试框架呢?相同之处就不说了,先说一下工具的局 ...

  6. python版接口自动化测试框架源码完整版(requests + unittest)

    python版接口自动化测试框架:https://gitee.com/UncleYong/my_rf [框架目录结构介绍] bin: 可执行文件,程序入口 conf: 配置文件 core: 核心文件 ...

  7. 接口自动化 [授客]基于python+Testlink+Jenkins实现的接口自动化测试框架V3.0

    基于python+Testlink+Jenkins实现的接口自动化测试框架V3.0   by:授客 QQ:1033553122     博客:http://blog.sina.com.cn/ishou ...

  8. 【python3+request】python3+requests接口自动化测试框架实例详解教程

    转自:https://my.oschina.net/u/3041656/blog/820023 [python3+request]python3+requests接口自动化测试框架实例详解教程 前段时 ...

  9. 接口自动化 基于python实现的http+json协议接口自动化测试框架源码(实用改进版)

    基于python实现的http+json协议接口自动化测试框架(实用改进版)   by:授客 QQ:1033553122 欢迎加入软件性能测试交流QQ群:7156436     目录 1.      ...

随机推荐

  1. nyoj-1236 挑战密室

    挑战密室 时间限制:1 s | 内存限制:128 M 提交 状态 排名 题目描述 R组织的特工Dr. Kong 为了寻找丢失的超体元素,不幸陷入WTO密室.Dr. Kong必须尽快找到解锁密码逃离,否 ...

  2. Netty (一) IO 基础篇

    Java IO 演进之路   1.1 必须明白的几个概念 1.1.1 阻塞(Block)和非阻塞(Non-Block) 阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准 ...

  3. linux 基础正则表达式练习

    感谢鸟哥!!! 如果Linux能够直接连网络,使用以下命令还获取文件吧 wget http://linux.vbird.org/linux_basic/0330regularex/regular_ex ...

  4. React 权限管理

    React 权限管理 react in depth JWT token access_token & refresh_token access token & refresh toke ...

  5. JavaScript Inheritance All in One

    JavaScript Inheritance All in One constructor inheritance prototype chain inheritance "use stri ...

  6. Async Programming All in One

    Async Programming All in One Async & Await Frontend (async () => { const url = "https:// ...

  7. vue & components & props & methods & callback

    vue & components & props & methods & callback demo solution 1 & props & data ...

  8. css dark theme & js theme checker

    css dark theme & js theme checker live demo https://codepen.io/xgqfrms/pen/GRprYLm <!DOCTYPE ...

  9. script async / defer

    script async / defer preload / prefetch https://abc.xgqfrms.xyz/ https://javascript.info/script-asyn ...

  10. 用Qt写了个将视频设置为壁纸的软件

    软件功能很简单,使用时占用的资源和播放的视频有关: 依赖于FFplay,Github源码 效果图: