本文档介绍如何使用excel管理接口测试用例并一键执行的实现方式,其中包括 python 读写excel, request库的基本操作,接口用例的设计
源码下载传送门>>APIAtuoTest
# 组装本条用例后的数据
data = {
'data': {
'ip': '112.112.11.11', 'key': 'key'},
'response': {
'resultcode': '200',
'reason': '查询成功',
'result': {
'Country': '中国', 'Province': '云南省', 'City': '昆明市', 'Isp': '电信'},
'error_code': 0
},
'file': 'timg.txt'}
# 取值表达式
var_ip=data.ip,var_key=data.key
# 被取值数据
data = {
'data': {
'ip': '112.112.11.11', 'key': 'key'},
'response': {
'resultcode': '200',
'reason': '查询成功',
'result': {
'Country': '中国', 'Province': '云南省', 'City': '昆明市', 'Isp': '电信'},
'error_code': 0
},
'file': 'timg.txt'}
# 取值结果
{
'var_ip': '112.112.11.11', 'var_key': 'key'}
# 检查表达式
resultcode equal 200,result.Isp equal 电信
# 检查数据
{
"resultcode":"200","reason":"查询成功","result":{
"Country":"中国","Province":"云南省","City":"昆明市","Isp":"电信"},"error_code":0}
# 检查结果
检查点>实际结果:200等于预期结果:200 pass
检查点>实际结果:电信等于预期结果:电信 pass
接口用例含有字段及描述
编号 : int(唯一) 用例id
模块 : str 用例所属模块描述,例如 用户管理之类的一级功能
标题 : str 用例简单描述,例如 新增用户之类的二级功能
测试点 : str 用例测试功能,例如 正确用户数据正常新增用户成功
级别 : str 用例重要程度,范围有(P0,P1,P2,P3),这里依照公司对用例级别的划分
执行顺序 : int(唯一) 这里可以设置用例的执行顺序, 1 为最优先执行,上不封顶
是否执行 : str 用例是否需要执行,范围有(Y,y,N,n),大Y小y代表该条用例需要执行,空或者其它都代表不执行
被依赖用例 : str 本条用例是否被其它用例依赖了测试数据
需依赖数据 : str 本条用例需要依赖的数据
依赖数据取值表达式 : str 用例执行会根据此表达式,自动去匹配 需依赖数据中的内容
依赖数据取值结果 : str 根据需依赖数据取值表达式 取值后自动存入,存入示例 {‘var_ip’: ‘112.112.11.11’, ‘var_key’: ‘key’}
接口地址 : str 接口地址
请求头 : str 请求头
请求方式 : get,post,若需要其它可自行添加
请求数据 : str 请求数据
文件名称 : str 此处添加测试框架中 test_data 目录下的 文件全名,若是批量文件上传接口,则可以录入多个文件全名以英文"逗号"隔开
返回结果 : str 用例执行后的返回
检查点 : str 根据 检查表达式 自动匹配 返回结果中的数据,并把检查结果自动写入 执行结果中
执行结果 : str 检查点匹配后的结果信息,包括每个检查点是否通过 和 若用例中的任何报错信息也会 自动录入
开始时间 : str 用例执行开始时间
结束时间 : str 用例执行结束时间
消耗时间 : str 本条用例执行总共花费时间
执行人 : 用例编写人,这个值可在 setting 文件中配置
封装关于 python 对excel 的基本操作,包括 返回指定行/每行打印/每列打印/读指定位置/写指定位置/按列读/按列写/按行读/按行写等等,下面放了一些实现操作的类方法
# 替换指定位置的值
def replace_data(self, row, cell, data):
if self.mode == 1:
# print(row,cell,data)
self.handle_sheet.write(int(row), int(cell), data)
self.handle.save(self.excel_filename)
return "修改行 %s 列 %s ,修改数据为 %s" % (str(row), str(cell), str(data))
else:
print(self.excel_filename + u" 不存在,无法进行操作!!!")
return 'error: not file.'
# 替换一行
def replace_nrows(self, data, nrows_number):
if self.mode == 1:
for i, item in enumerate(data):
self.handle_sheet.write(nrows_number - 1, i, item)
self.handle.save(self.excel_filename)
return nrows_number, letter(i + 1)
else:
print(self.excel_filename + u" 不存在,无法进行操作!!!")
return 'error: not file.'
# 替换一列
def replace_ncols(self, data, ncols_number):
if self.mode == 1:
for i, item in enumerate(data):
self.handle_sheet.write(i, ncols_number - 1, item)
self.handle.save(self.excel_filename)
return i + 1, letter(ncols_number)
else:
print(self.excel_filename + u" 不存在,无法进行操作!!!")
return 'error: not file.'
# 删除全部
def del_alldata(self):
if self.mode == 1:
replace_number = 0
for i in range(self.sheet.ncols):
for n in range(self.sheet.nrows):
self.handle_sheet.write(n, i, '')
replace_number += 1
self.handle.save(self.excel_filename)
return replace_number
else:
print(self.excel_filename + u" 不存在,无法进行操作!!!")
return 'error: not file.'
# 删除行
def del_nrows(self, nrows_number):
if self.mode == 1:
replace_number = 0
for i in range(self.sheet.ncols):
self.handle_sheet.write(nrows_number - 1, i, '')
replace_number += 1
self.handle.save(self.excel_filename)
return replace_number
else:
print(self.excel_filename + u" 不存在,无法进行操作!!!")
return 'error: not file.'
# 删除列
def del_ncols(self, ncols_number):
if self.mode == 1:
replace_number = 0
for n in range(self.sheet.nrows):
self.handle_sheet.write(n, ncols_number - 1, '')
replace_number += 1
self.handle.save(self.excel_filename)
return replace_number
else:
print(self.excel_filename + u" 不存在,无法进行操作!!!")
return 'error: not file.'
# 删除某个位置
def del_data(self, nrows_number, ncols_number):
if self.mode == 1:
self.handle_sheet.write(nrows_number + 1, ncols_number + 1, '')
self.handle.save(self.excel_filename)
return self.sheet.cell(ncols_number + 1, nrows_number + 1).value
else:
print(self.excel_filename + u" 不存在,无法进行操作!!!")
return 'error: not file.'
# 删除指定数据
def del_value(self, data):
if self.mode == 1:
replace_number = 0
for i in range(self.sheet.ncols):
for n in range(self.sheet.nrows):
if data == self.sheet.cell(n, i).value:
self.handle_sheet.write(n, i, '')
replace_number += 1
self.handle.save(self.excel_filename)
return replace_number
else:
print(self.excel_filename + u" 不存在,无法进行操作!!!")
return 'error: not file.'
# 读取指定位置的数据 return data
def xlsred(self, nrows_number, ncols_number):
if self.mode == 1:
str = self.sheet.cell(nrows_number, ncols_number).value
print("read data : ", str)
print("Data-ncols: ", nrows_number)
print("Data-number: ", ncols_number)
return str
else:
print(self.excel_filename + u" 不存在,无法进行操作!!!")
return 'error: not file.'
# 每列打印 return data
def xlsread_allncols(self):
if self.mode == 1:
data_list = []
for i in range(self.sheet.ncols):
data = []
for n in range(self.sheet.nrows):
if self.sheet.cell(n, i).value != '':
data.append(self.sheet.cell(n, i).value)
data_list.append(data)
return data_list
else:
print(self.excel_filename + u" 不存在,无法进行操作!!!")
return 'error: not file.'
目前框架功能 : 读写excel用例并自动执行后把本身测试结果写入, 后续自行可以 分析测试结果生成 各种版本的测试报告 和 自行推送测试结果至各个平台,如 Email,企业微信,钉钉,微信,短信,平台机器人查看相关产品的接口文档
框架目录
使用说明
1.环境 :python 3.x ,需依赖 xlrd xlwt xlutils
2.依照 TestCase.xls 用例格式完成用例接口用例编写
3.python main.py 执行测试
代码片段
# opt_expression.py
def write_rely_case_data(now_case_id, data_str):
"""
该条用例会被谁依赖并把需要依赖的内容写入被依赖用例的 依赖用例数据 中
:param data_str: 依赖表达式 2,headers,data,response,file
:param now_case_id: 当前测试用例id
:return: 把 被依赖的测试数据 写入 需依赖的用例 依赖用例数据 中
"""
temp_dict = {
}
# 如果传入的 依赖用例 为空,则不需要组装 需依赖数据 并 写入需依赖的用例中
if data_str == "":
return False
# 组装 需依赖 的测试数据
data_list = data_str.split(",")
temp_dict["rely_case_id"] = data_list[0]
temp_dict["all_data"] = {
}
for item in range(1, len(data_list)):
if "headers" == data_list[item]:
case_headers = opt_test_data.read_headers(now_case_id)
if case_headers:
temp_dict["all_data"]["headers"] = eval(case_headers)
elif "data" == data_list[item]:
case_data = opt_test_data.read_data(now_case_id)
if case_data:
temp_dict["all_data"]["data"] = eval(case_data)
elif "response" == data_list[item]:
case_response = opt_test_data.read_response(now_case_id)
if case_response:
temp_dict["all_data"]["response"] = eval(case_response)
elif "file" == data_list[item]:
case_filename = opt_test_data.read_files_name(now_case_id)
temp_dict["all_data"]["file"] = case_filename
# 把组装好的 需依赖数据 写入 需依赖用例的 依赖用例数据 中
opt_test_data.write_rely_on_case_data(int(temp_dict["rely_case_id"]), str(temp_dict["all_data"]))
return temp_dict
def dict_str(test_data_str):
"""
将从测试用例中读取出来的字符串转换成字典
:param test_data_str: 读取出来的字符串
:return: 字典
"""
if test_data_str:
return eval(test_data_str)
return None
def get_expression_data(pattern, data_dict):
"""
根据传入表达式pattern匹配数据data中的数据
:param pattern: 表达式
:param data_dict: 匹配数据
:return: dict
{'var1': 'headers', 'var2': 'response'}
"""
var_dict = {
}
pattern_list = pattern.split(",")
# print(pattern_list)
for item in pattern_list:
temp_list = item.split("=")
# 获取变量名称
temp_var = temp_list[0]
# 分析表达式
var_dict[temp_var] = _dict_path_value(temp_list[1], data_dict)
return var_dict
def replace_data(var_data, data_str):
"""
根据传入的 var_data, 修改 data 中的字符串
:param var_data: 表达式从依赖用例数据中取出的变量字典
:param data_str: 需要替换的对象字符串(从用例中取出的请求头等)
:return: 修改过的 请求数据 dict
"""
for item in var_data:
data_str = data_str.replace(item, var_data[item])
return data_str
def assert_data(case_id, assert_str):
"""
根据assert_str的断言,判断 实际结果 和 预期结果 之间的关系
:param assert_str: 断言 "headers.key1 equal key1_123"
:param case_id: 测试用例id
:return: pass fail
"""
result_str = ""
actual = eval(opt_test_data.read_response(case_id))
assert_list = assert_str.split(",")
for item in assert_list:
my_assert = re.findall(r"\s\S+\s", item)[0].strip()
pattern_left = re.findall(r"\S+\s", item)[0].strip()
pattern_right = re.findall(r"\s\S+", item)[1].strip()
if "equal" == my_assert:
data_left = _dict_path_value(pattern_left, actual)
data_right = pattern_right
if data_left == data_right:
result_str += "检查点>实际结果:%s等于预期结果:%s pass \n" % (data_left, data_right)
else:
result_str += "检查点>实际结果:%s不等于预期结果:%s fail \n" % (data_left, data_right)
elif "notequal" == my_assert:
data_left = _dict_path_value(pattern_left, actual)
data_right = pattern_right
if data_left != data_right:
result_str += "检查点>实际结果:%s等于预期结果:%s pass \n" % (data_left, data_right)
else:
result_str += "检查点>实际结果:%s不等于预期结果:%s fail \n" % (data_left, data_right)
elif "in" == my_assert:
data_left = _dict_path_value(pattern_left, actual)
data_right = pattern_right
if data_left in data_right:
result_str += "检查点>实际结果:%s等于预期结果:%s pass \n" % (data_left, data_right)
else:
result_str += "检查点>实际结果:%s不等于预期结果:%s fail \n" % (data_left, data_right)
elif "notin" == my_assert:
data_left = _dict_path_value(pattern_left, actual)
data_right = pattern_right
if data_left not in data_right:
result_str += "检查点>实际结果:%s等于预期结果:%s pass \n" % (data_left, data_right)
else:
result_str += "检查点>实际结果:%s不等于预期结果:%s fail \n" % (data_left, data_right)
return result_str
def _dict_path_value(path, data):
"""
根据path获取dict_data里面的值
"""
num = 1
pahts = path.split(".")
mydict = {
}
for p in pahts:
if num == 1:
mydict = data[p + ""]
num = num + 1
else:
mydict = mydict[p + ""]
num = num + 1
return mydict
# opt_test_data.py
def read_is_run(case_id):
"""读取该条用例的 是否执行 信息"""
is_run = remove_str_all_break(get_case_row_data(case_id)[6])
if len(is_run) <= 0:
return None
return is_run
def write_is_run(case_id, content):
"""修改该条用例的 是否执行 信息"""
case_row_number = get_case_row_number(case_id)
test_case_data().replace_data(case_row_number, 6, content)
def read_rely_on_case(case_id):
"""读取该条用例的 被依赖用例/依赖内容 信息"""
rely_on_case = remove_str_all_break(get_case_row_data(case_id)[7])
if len(rely_on_case) <= 0:
return None
return rely_on_case
def write_rely_on_case(case_id, content):
"""修改该条用例的 依赖用例 信息"""
case_row_number = get_case_row_number(case_id)
test_case_data().replace_data(case_row_number, 7, content)
def read_rely_on_case_data(case_id):
"""读取该条用例的 需依赖用例数据 信息"""
case_data = remove_str_all_break(get_case_row_data(case_id)[8])
if len(case_data) <= 0:
return None
return case_data
def write_rely_on_case_data(case_id, content):
"""修改该条用例的 需依赖用例数据 信息"""
case_row_number = get_case_row_number(case_id)
test_case_data().replace_data(case_row_number, 8, content)
def read_rely_on_case_expression(case_id):
"""读取该条用例的 需依赖用例取值表达式 信息"""
case_expression = remove_str_all_break(get_case_row_data(case_id)[9])
if len(case_expression) <= 0:
return None
return case_expression
def write_rely_on_case_expression(case_id, content):
"""修改该条用例的 需依赖用例取值表达式 信息"""
case_row_number = get_case_row_number(case_id)
test_case_data().replace_data(case_row_number, 9, content)
def read_rely_on_case_expression_result(case_id):
"""读取该条用例的 需依赖用例取值表达式取值结果 信息"""
expression_result = remove_str_all_break(get_case_row_data(case_id)[10])
if len(expression_result) <= 0:
return None
return expression_result
def write_rely_on_case_expression_result(case_id, content):
"""修改该条用例的 需依赖用例取值表达式取值结果 信息"""
case_row_number = get_case_row_number(case_id)
test_case_data().replace_data(case_row_number, 10, content)
def read_api_path(case_id):
"""读取该条用例的 api地址 信息"""
api_path = remove_str_all_break(get_case_row_data(case_id)[11])
if len(api_path) <= 0:
return None
return api_path
def write_api_path(case_id, content):
"""修改该条用例的 api地址 信息"""
case_row_number = get_case_row_number(case_id)
test_case_data().replace_data(case_row_number, 11, content)
def read_headers(case_id):
"""读取该条用例的 请求头 信息"""
headers = remove_str_all_break(get_case_row_data(case_id)[12])
if len(headers) <= 0:
return None
return headers
def write_headers(case_id, content):
"""修改该条用例的 请求头 信息"""
case_row_number = get_case_row_number(case_id)
test_case_data().replace_data(case_row_number, 12, content)
def read_method(case_id):
"""读取该条用例的 请求方法 信息"""
method = remove_str_all_break(get_case_row_data(case_id)[13])
if len(method) <= 0:
return None
return method
def write_method(case_id, content):
"""修改该条用例的 请求方法 信息"""
case_row_number = get_case_row_number(case_id)
test_case_data().replace_data(case_row_number, 13, content)
# request_client.py
def main(self):
# 获取所有需要执行的用例id列表
all_test_count_list = opt_test_data.get_test_run_order()
for item in all_test_count_list:
# 判断本条用例是否需要被执行
is_run = opt_test_data.read_is_run(item)
try:
if is_run == "Y" or is_run == "y":
# 写入开始执行时间
opt_test_data.write_start_time(item, my_time.now_time())
# 判断本条用例是否需要依赖数据
# 获取 需依赖用例的数据 和 需依赖用例的表达式
rely_on_case_data = opt_test_data.read_rely_on_case_data(item)
rely_on_case_expression = opt_test_data.read_rely_on_case_expression(item)
if rely_on_case_data and rely_on_case_expression:
# 准备本条需要依赖的数据
self.write_rely_data(item, rely_on_case_expression, rely_on_case_data)
# 如果 接口地址\请求头\请求方法\请求数据\文件名称 中需要依赖数据,则使用依赖数据替换
self.replace_request_data(item)
# 接下来流程一样
self._send_end(item)
# 不需要依赖数据
else:
# 接下来流程一样
self._send_end(item)
# 打印一下本条用例基本情况
self.print_now_status(item)
else:
# 本条用例不执行,执行下一条
continue
except Exception as e:
self.print_now_status(item)
print(e)
print(traceback.print_exc())
opt_test_data.write_status(item, str(traceback.format_exc()))
finally:
# 写入本条用例执行完成时间
self._end_time(item)
# 写入本条用例的执行人
self._write_executor(item)
# 计算本条用例总共花费时间并写入消耗时间中
self._write_total_time(item)