注:为了节省自己的工作效率,让自己有更多的时间来摸鱼学习提升自己,因此用django写了一个自动生成接口测试用例的demo
自动生成接口测试用例demo
1.设计流程图
2.接口用例模板(冒烟,功能,压测)
(见最终生成效果)
3.原始数据,psotman导出的json包
4.涉及到的技术(边写边学习边记录)
Django(视图层、模型层、模板层,登录,注册)
Pytest测试框架(操作yaml、操作ini文件、数据驱动、测试报告生成等)
openpyxl(写入、保存、合并单元格、样式)
html+css(前端能力不强,因此用的比较low)
其它(上传、下载文件,相对绝对路径,django邮件发送,加密,文件操作,jsonpath使用、多层for循环等)
Get到的新的方法:
判断类型的方法:isinstances(obj,类型)
BASE_DIR方法:
Mac终端程序在后台运行方法:命令后 加上 &
获取目录下文件列表:
file = os.listdir(目录)
删除文件:os.remove(file)
删除目录:os.removedirs(空目录)
5.django目录
6. 代码
6.1生成表格的代码
import os
from pathlib import Path
from openpyxl import Workbook
from openpyxl.styles import Alignment, Font, colors, Border, Side, PatternFill
from openpyxl.utils import get_column_letter, column_index_from_string
from json_data import read_file
'''
postman 导出的文件命名格式:项目名_服务名
'''
class AutoCase:
def __init__(self):
self.BASE_DIR = Path(__file__).parent # BSAE_DIR目录
self.smoke_title = ['服务', '接口名称', '测试内容', '用例描述', '预期结果', '实际结果', '测试结果', '接口地址'] # 冒烟表表头
self.function_title = ['服务名', '接口名', 'URL', '接口测试类型', '测试内容', '预期结果', '实际结果', '测试结果', '备注'] # 功能表表头
self.stress_title = ['名称', '策略', '', '平均事务响应时间', 'TPS', '错误百分比', '测试URL', '描述', '是否新增', '结果'] # 压测表表头
# self.test_cases = ["无该字段", "为null", '为\"\"', "为\'\'", "为#", '为**类型'] # 参数校验数据
self.test_cases = ["无该字段","为null",'\"\"',"\'\'","#","为**类型"] # 参数校验数据
self.test_cases_header = ["business1"] # header校验数据
self.business = ["business1"] # 业务逻辑校验
self.wb = Workbook() # 创建文件
self.ws = self.wb.active # 激活sheet表
self.file_path = os.path.join(self.BASE_DIR,
'AI教师-魔拍v1.6(指令拍)_ok-student-middle.postman_collection.json') # 文件路径
print(self.file_path)
self.read_file = eval(read_file(self.file_path)) # 读取文件
self.item = self.read_file['item'] # item数据
self.p_name_s_name = self.read_file['info']['name'] # 项目名-服务名
self.p_name = self.p_name_s_name.split('_')[0] # 项目名
self.s_name = self.p_name_s_name.split('_')[1] # 服务名
self.interface_count = len(self.read_file['item']) # 接口数
self.font_style = Font(name=None, sz=None, b=None, i=None, charset=None, # 字体样式
u=None, strike=None, color=None, scheme=None, family=None, size=12,
bold=None, italic=None, strikethrough=None, underline=None,
vertAlign=None, outline=None, shadow=None, condense=None,
extend=None)
self.alignment_style = Alignment(wrapText=True) # 默认样式
self.alignment_center_style = Alignment(horizontal='center', vertical='center', wrapText=True) # 居中样式
self.border_style = Border(left=Side(style='medium', color=colors.BLACK),
right=Side(style='medium', color=colors.BLACK),
top=Side(style='medium', color=colors.BLACK),
bottom=Side(style='medium')) # 边框样式
self.title_fill_style = PatternFill("solid", fgColor="0099ff") # 表头的颜色-蓝色
self.result_fill_style = PatternFill('solid', fgColor="00cc00") # 测试结果列颜色-绿色
self.test_type_fill_style = PatternFill("solid", fgColor="c0c0c0") # 接口测试类型颜色-灰色
# 接口名称列表
def i_names(self):
i_names = []
for i in range(self.interface_count):
i_name = self.read_file['item'][i]["name"]
i_names.append(i_name)
return i_names
# URL列表
def urls(self):
urls = []
for url in range(self.interface_count):
urls.append(self.read_file['item'][url]['request']['url']['raw'])
return urls
# 创建冒烟表
def create_smoke_sheet(self):
ws_smoke = self.wb.create_sheet('冒烟', 0)
ws_smoke.append(self.smoke_title)
# 表头样式
for row in ws_smoke.iter_rows(min_row=1, max_row=1, min_col=1, max_col=8):
for cell in row:
cell.font = self.font_style
cell.alignment = self.alignment_center_style
cell.border = self.border_style
cell.fill = self.title_fill_style
# 服务名(先填写再合并单元格)
ws_smoke['A2'] = self.s_name
ws_smoke['A2'].font = self.font_style
ws_smoke.merge_cells('A2:A%s' % (self.interface_count + 1))
ws_smoke['A2'].alignment = self.alignment_center_style
# 接口名称列
i_count = 0
for col in ws_smoke.iter_cols(min_col=2, max_col=2, min_row=2, max_row=self.interface_count + 1):
for cell in col:
if i_count > len(self.i_names()) - 1:
print("i_count计数 不能大于 i_names索引长度")
break
cell.value = self.i_names()[i_count].split('_')[0] # 接口名 _ 分割后的第一个
cell.font = self.font_style
cell.alignment = self.alignment_style
i_count += 1
# 测试内容列
for col in ws_smoke.iter_cols(min_col=3, max_col=3, min_row=2, max_row=self.interface_count + 1):
for cell in col:
cell.font = self.font_style
cell.value = '正常情况'
# 用例描述列
for col in ws_smoke.iter_cols(min_col=4, max_col=4, min_row=2, max_row=self.interface_count + 1):
for cell in col:
cell.value = '冒烟'
cell.font = self.font_style
# 预期结果列
for col in ws_smoke.iter_cols(min_col=5, max_col=5, min_row=2, max_row=self.interface_count + 1):
for cell in col:
cell.value = '"code":0'
cell.font = self.font_style
# 实际结果列
for col in ws_smoke.iter_cols(min_col=6, max_col=6, min_row=2, max_row=self.interface_count + 1):
for cell in col:
cell.value = '"code":0'
cell.font = self.font_style
# 测试结果列
for col in ws_smoke.iter_cols(min_col=7, max_col=7, min_row=2, max_row=self.interface_count + 1):
for cell in col:
cell.value = 'pass'
cell.font = self.font_style
cell.fill = self.result_fill_style
u_count = 0
for col in ws_smoke.iter_cols(min_col=8, max_col=8, min_row=2, max_row=self.interface_count + 1):
for cell in col:
if u_count > len(self.urls()) - 1:
print('u_count 必须小于 urls 的最大索引数')
break
cell.value = self.urls()[u_count]
cell.font = self.font_style
cell.alignment = self.alignment_style
u_count += 1
# 设置行号、列宽
print('冒烟表-最大行数:%s' % ws_smoke.max_row)
print('冒烟表-最大列数:%s' % ws_smoke.max_column)
# 行高
for i in range(1, ws_smoke.max_row + 1):
if i == 1:
ws_smoke.row_dimensions[i].height = 30
else:
ws_smoke.row_dimensions[i].height = 15
# 列宽
for i in range(1, ws_smoke.max_column + 1):
# 根据数字得到字母
letter = get_column_letter(i)
if letter == 'H':
ws_smoke.column_dimensions[letter].width = 50
else:
ws_smoke.column_dimensions[letter].width = 20
# 给整个表格添加边框
for row in ws_smoke.iter_rows(min_row=ws_smoke.min_row, max_row=ws_smoke.max_row,
min_col=ws_smoke.min_column, max_col=ws_smoke.max_column):
for cell in row:
cell.border = self.border_style
return ws_smoke
# 创建功能表
def create_function_sheet(self):
ws_function = self.wb.create_sheet('功能', 1)
ws_function.append(self.function_title)
# 表头样式
for row in ws_function.iter_rows(min_row=1, max_row=1, min_col=1, max_col=9):
for cell in row:
cell.alignment = self.alignment_center_style
cell.font = self.font_style
cell.fill = self.title_fill_style
# 测试内容(代码的位置不能变)
keys_list = [] # 每个接口中所有的key
for i in range(self.interface_count):
if self.item[i]['request']['method'] == "GET":
print("第%s个接口-%s 的请求方法是%s" % (
i + 1, self.read_file['item'][i]["name"], self.item[i]['request']['method']))
query = self.item[i]['request']['url']['query']
key_list = []
for j in range(len(query)):
key = query[j]['key']
key_list.append(key)
keys_list.append(key_list) # 加入到keys_list中
elif self.item[i]['request']['method'] == "POST":
print(
"第%s个接口-%s的请求方法是%s" % (i + 1, self.read_file['item'][i]["name"], self.item[i]['request']['method']))
raw = eval(self.item[i]['request']['body']['raw'])
# print(raw)
key_list = []
for key, value in raw.items():
# 将参数中为列表的,
if isinstance(value, list):
# print(value)
key_list.append(key)
for value_dict in value:
# print("value_dict", value_dict)
if not isinstance(value_dict, dict):
print("参数%s列表中的元素,type不是字典,跳过" % value_dict)
continue
for value_dirt_k in value_dict:
# print(value_dirt_k)
key_list.append(value_dirt_k)
else:
key_list.append(key)
keys_list.append(key_list)
else:
raise Exception("暂不支持该请求%s" % self.item[i]['request']['method'])
print("功能表-test_cases:%s" % self.test_cases)
print("功能表-keys_list:%s" % keys_list)
# 正常情况+参数与参数校验数据组合+header校验+业务逻辑校验
key_case_list = []
for i in range(len(keys_list)):
key_case_list.append("正常情况")
for j in range(len(keys_list[i])):
# print(keys_list[i][j])
for c in range(len(self.test_cases)):
test_case_result = keys_list[i][j] + self.test_cases[c]
# print(test_case_result)
key_case_list.append(test_case_result)
# 小列表循环结束加上header校验,业务校验
for header in self.test_cases_header:
key_case_list.append(header)
for business in self.business:
key_case_list.append(business)
print("功能表-key_case_list:", key_case_list)
# 合并单元格
ps_list = [] # 参数开始行号
pe_list = [] # 参数结束行号
hs_list = [] # header开始行号
he_list = [] # header结束行号
bs_list = [] # 业务开始行号
be_list = [] # 业务结束行号
i = 2
n = 0
for key_l in keys_list:
# 参数校验
'''插入数据'''
ws_function.merge_cells("D%s:D%s" % (i, i + len(key_l) * len(self.test_cases)))
ws_function.cell(column=4, row=i).value = "参数校验"
'''样式'''
ws_function['D%s' % i].alignment = self.alignment_center_style
ws_function['D%s' % i].fill = self.test_type_fill_style
'''起始行号'''
ps_list.append(i)
pe_list.append(i + len(key_l) * len(self.test_cases))
'''往下移位置'''
i += len(key_l) * len(self.test_cases) + 1 # 新的行作为header校验的起始行
# header校验
'''插入数据'''
if len(self.test_cases_header) > 0: # 有header时,才添加header
ws_function.merge_cells("D%s:D%s" % (i, i + len(self.test_cases_header) - 1))
ws_function.cell(column=4, row=i).value = "header校验"
'''样式'''
ws_function['D%s' % i].alignment = self.alignment_center_style
ws_function['D%s' % i].fill = self.test_type_fill_style
'''起始行号'''
hs_list.append(i)
he_list.append(i+len(self.test_cases_header) - 1)
'''向下移位置'''
i += len(self.test_cases_header) # 新的行作为业务校验的起始行
# 接口业务逻辑校验
'''插入数据'''
if len(self.business) > 0: # 有业务逻辑校验时,才添加业务校验数据
ws_function.merge_cells("D{}:D{}".format(i, i + len(self.business)-1))
ws_function.cell(column=4, row=i).value = "接口业务逻辑校验"
'''样式'''
ws_function['D%s' % i].alignment = self.alignment_center_style
ws_function['D%s' % i].fill = self.test_type_fill_style
'''起始行号'''
bs_list.append(i)
be_list.append(i+len(self.business) - 1)
'''结束循环,没进入下一个接口单元'''
i += len(self.business) # i变为下一个参数校验起始行
g = len(self.business) + len(self.test_cases_header) + \
len(key_l)*len(self.test_cases)+1 # 每个单元格的cell行号长度
# print(g)
'''以下合并其他的单元格'''
merge_srow_index = i - g # 接口名、URL等的开始行号
# print(merge_srow_index)
# 接口名
'''合并单元格'''
ws_function.merge_cells("B{}:B{}".format(merge_srow_index, i-1))
'''插入数据'''
ws_function.cell(merge_srow_index, 2).value = self.i_names()[n].split('_')[0]
'''样式'''
ws_function["B%s" % merge_srow_index].alignment = self.alignment_center_style
# URL
'''合并单元格'''
ws_function.merge_cells("C{}:C{}".format(merge_srow_index, i-1))
'''插入数据'''
ws_function.cell(merge_srow_index, 3).value = self.urls()[n]
'''样式'''
ws_function["C%s" % merge_srow_index].alignment = self.alignment_center_style
n += 1
# 服务名
'''合并单元格'''
ws_function.merge_cells("A{}:A{}".format(2, ws_function.max_row))
'''插入数据'''
ws_function.cell(row=2, column=1, value=self.s_name)
'''样式'''
ws_function["A2"].alignment = self.alignment_center_style
print("功能表-参数校验开始行号%s" % ps_list)
print("功能表-参数校验结束行号%s" % pe_list)
print("功能表-header校验开始行号%s" % hs_list)
print("功能表-header校验结束行号%s" % he_list)
print("功能表-业务逻辑校验开始行号%s" % bs_list)
print("功能表-业务逻辑校验结束行号%s" % be_list)
# 插入测试内容数据
tc_col_index = column_index_from_string('E')
print("插入测试内容数据的列号是:", tc_col_index)
n = 0
for col in ws_function.iter_cols(min_col=tc_col_index, max_col=tc_col_index,
min_row=2, max_row=len(key_case_list)+1):
for cell in col:
cell.value = key_case_list[n]
n += 1
# 预期结果
# 实际结果
# 测试结果
for col in ws_function.iter_cols(min_row=2, max_row=ws_function.max_row,
min_col=8, max_col=8):
for cell in col:
'''插入数据'''
cell.value = 'pass'
'''样式'''
cell.alignment = self.alignment_center_style
cell.fill = self.result_fill_style
# 备注
# 列宽
print('功能表-最大行数:%s' % ws_function.max_row)
print('功能表-最大列数:%s' % ws_function.max_column)
for i in range(1, ws_function.max_column + 1):
letter = get_column_letter(i)
ws_function.column_dimensions[letter].width = 30
# 行高
for i in range(1, ws_function.max_row + 1):
if i == 1:
ws_function.row_dimensions[i].height = 30
else:
ws_function.row_dimensions[i].height = 15
# 边框
for row in ws_function.iter_rows(min_row=ws_function.min_row, max_row=ws_function.max_row,
min_col=ws_function.min_column, max_col=ws_function.max_column):
for cell in row:
cell.border = self.border_style
return ws_function
# 压测表
def create_stress_sheet(self):
# 创建压测表
ws_stress = self.wb.create_sheet('压测', 2)
# 表头1-服务名
'''合并'''
ws_stress['A1'] = '服务名'
ws_stress.merge_cells('A1:J1')
'''样式'''
ws_stress['A1'].alignment = Alignment(horizontal='center', vertical='center')
ws_stress['A1'].fill = self.test_type_fill_style # 灰色
# 表头2
ws_stress.append(self.stress_title) # ''用于为占位使用
ws_stress.merge_cells('B2:C2')
ws_stress['B2'].alignment = Alignment(horizontal='center')
# 表头样式
for row in ws_stress.iter_rows(min_row=2, max_row=2, min_col=1, max_col=10):
for cell in row:
cell.alignment = self.alignment_center_style
cell.font = self.font_style
cell.fill = self.title_fill_style
# 名称
i = 3 # 每次循环移动位置计数
n = 0 # 累加取URL
r = 0 # 压测表填写的总行数
qps_list = []
for iname in self.i_names():
if iname.split('_')[1] == '压': # 压
if iname.split('_')[2] == '新增': # 新增
r += 3
if iname.split('_')[3] == '读': # 新增-读
'''合并单元格'''
ws_stress.merge_cells("A{}:A{}".format(i, i + 2)) # 接口名称
ws_stress.merge_cells("G{}:G{}".format(i, i + 2)) # 测试URL
ws_stress.merge_cells("H{}:H{}".format(i, i + 2)) # 描述
ws_stress.merge_cells("I{}:I{}".format(i, i + 2)) # 是否新增
'''写入数据'''
ws_stress.cell(row=i, column=1).value = iname.split('_')[0] + "(读)" # 接口名称
ws_stress.cell(row=i, column=9).value = "是" # 是否新增
ws_stress.cell(row=i, column=7).value = self.urls()[n].split('?')[0] # URL
'''样式'''
ws_stress["A{}".format(i)].alignment = self.alignment_center_style
ws_stress["G{}".format(i)].alignment = self.alignment_center_style
ws_stress["H{}".format(i)].alignment = self.alignment_center_style
ws_stress["I{}".format(i)].alignment = self.alignment_center_style
i += 3
n += 1
qps_list.append('200并发')
qps_list.append('250并发')
qps_list.append('300并发')
print("压-新增-读")
else: # 新增-写
'''合并单元格'''
ws_stress.merge_cells("A{}:A{}".format(i, i + 2)) # 接口名称
ws_stress.merge_cells("G{}:G{}".format(i, i + 2)) # 测试URL
ws_stress.merge_cells("H{}:H{}".format(i, i + 2)) # 描述
ws_stress.merge_cells("I{}:I{}".format(i, i + 2)) # 是否新增
'''写入数据'''
ws_stress.cell(row=i, column=1).value = iname.split('_')[0] + "(写)" # 接口名称
ws_stress.cell(row=i, column=9).value = "是" # 是否新增
ws_stress.cell(row=i, column=7).value = self.urls()[n].split('?')[0] # URL
i += 3
n += 1
qps_list.append('100并发')
qps_list.append('150并发')
qps_list.append('200并发')
print("压-新增-写")
else: # 不新增
r += 1
if iname.split('_')[3] == '读': # 非新增-读
ws_stress.cell(row=i, column=1).value = iname.split('_')[0] + "(读)" # 接口名称
ws_stress.cell(row=i, column=9).value = "否" # 是否新增
ws_stress.cell(row=i, column=7).value = self.urls()[n].split('?')[0] # URL
i += 1
n += 1
qps_list.append('200并发')
print("压-非新增-读")
else: # 非新增-写
ws_stress.cell(row=i, column=1).value = iname.split('_')[0] + "(写)" # 接口名称
ws_stress.cell(row=i, column=9).value = "否" # 是否新增
ws_stress.cell(row=i, column=7).value = self.urls()[n].split('?')[0] # URL
i += 1
n += 1
qps_list.append('100并发')
print("压-非新增-写")
else: # 不压
print("压测表—接口%s,不需要压测" % iname)
# print(qps_list)
# print(r)
# 策略-并发
t = 0
for col in ws_stress.iter_cols(min_col=2, max_col=2, min_row=3, max_row=len(qps_list)+2):
for cell in col:
cell.value = qps_list[t]
cell.alignment = self.alignment_center_style
t += 1
# 测试结果
for col in ws_stress.iter_cols(min_col=10, max_col=10, min_row=3, max_row=r+2):
for cell in col:
cell.value = "pass"
cell.alignment = self.alignment_center_style
cell.fill = self.result_fill_style
# 策略--持续时长
for col in ws_stress.iter_cols(min_row=3, max_row=3+r - 1,
min_col=3, max_col=3):
for cell in col:
cell.value = "持续5min"
cell.alignment = self.alignment_center_style
# 平均事务响应时间
# TPS
# 错误百分比
# 测试URL
# 描述
# 是否新增
# 列宽
print('压测表-最大行数:%s' % ws_stress.max_row)
print('压测表-最大列数:%s' % ws_stress.max_column)
for i in range(1, ws_stress.max_column + 1):
letter = get_column_letter(i)
if i == 7:
ws_stress.column_dimensions[letter].width = 40
else:
ws_stress.column_dimensions[letter].width = 20
# 行高
for i in range(1, ws_stress.max_row + 1):
if i == 1:
ws_stress.row_dimensions[i].height = 40
elif i == 2:
ws_stress.row_dimensions[i].height = 35
else:
ws_stress.row_dimensions[i].height = 25
# 全表基础样式 边框+对齐+字体大小+自动换行
for row in ws_stress.iter_rows(min_row=ws_stress.min_row, max_row=ws_stress.max_row,
min_col=ws_stress.min_column, max_col=ws_stress.max_column):
for cell in row:
cell.border = self.border_style
cell.alignment = self.alignment_center_style # 居中样式
cell.font = self.font_style
return ws_stress
# 保存表
def save_wb(self):
return self.wb.save(self.p_name + '.xlsx')
# 主函数
def main(self):
# self.wb.remove('Sheet')
del self.wb['Sheet'] # 删除默认表
print('接口名称列表:%s' % self.i_names())
print('接口RUL列表:%s' % self.urls())
print('-------------------------------------冒烟-------------------------------------')
self.create_smoke_sheet()
print('-------------------------------------功能-------------------------------------')
self.create_function_sheet()
print('-------------------------------------压测-------------------------------------')
self.create_stress_sheet()
print("sheet表名称:%s" % self.wb.sheetnames)
self.save_wb()
if __name__ == '__main__':
AutoCase().main()
6.2发送邮件的代码
"""
这里定义公共的方法
"""
import os
from email.header import make_header
from django.core.mail import EmailMultiAlternatives
from autocase import settings
# 发送邮件方法
def send_email(file_path, recipients, subject, body):
# 指定django的配置文件,同manage.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'autocase.settings')
# 调多种邮件方式方法,并赋予变量
msg = EmailMultiAlternatives(subject=subject, body=body, from_email=settings.DEFAULT_FROM_EMAIL, to=recipients)
# 内容设为html格式类型
msg.attach_alternative(body, 'text/html')
# 发送附件
file = open(file_path, 'rb').read()
file_name = os.path.basename(file_path)
print(file_name)
b_name = make_header(decoded_seq=[(file_name, 'utf-8')]).encode('utf-8') # 这里传了一个解码序列
msg.attach(filename=b_name, content=file)
# 发送邮件
msg.send()
# 判断发送是否成功
if msg.send():
print("邮件发送成功")
else:
raise Exception("邮件发送失败")
7.效果
7.1页面效果
7.2用例表格效果
冒烟表
功能表
压测表
8.结合pytest,来进行自动运行用例
8.1 pytest目录
目录简要说明:
- data:存放测试数据相关
- reoprt:存放allure生成的测试报告
- temp:存放生成allure测试报告的基础json文件
- tests:存放运行用例脚本
- utuils:存放自己封装的各种工具类或方法
9.设计思路:
10.效果图
页面效果
运行结果效果
表格:
11.踩坑
1.定义空列表时,用 l = list(),不用l = [],这样能避免一些不必要的错误