最后输出的结果效果:
本框架最后的文件结构如下图所示
Util 文件:
在 Util.py 文件中,我们封装了程序主体功能,包括构建唯一电话号码、发送
请求获取返回结果和结果断言,函数具体功能如下:
1. get_uniquenumber:处理用户名里面的数字,使用户名唯一,该函数使用
uniquenumber.txt 文件
2. send_request:发送请求,获取返回结果,send_request 函数处理请求数据类型为
字典格式和非字典格式,请求类型为 post,get,put 等类型的请求
3. assert_result:断言结果
# encoding = utf-8
import requests
def get_uniquenumber():
"""
@param data_file:
@return:
!!!前置条件:创建 uniquenumber.txt 文件存储数字,然后每次读取 uniquenumber 里面的数字,
使用后将数字+1 再写回去,可保证数字完全不会重复使用
"""
with open("uniquenumber.txt", "r+", encoding="utf-8") as fp:
uniquenumber = int(fp.read().strip())
fp.seek(0, 0)
fp.write(str(uniquenumber + 1))
# print("uniquenumber:{}".format(uniquenumber))
return uniquenumber
def send_request(url, data, headers, request_type):
"""
请求接口,得到返回数据
"""
# print("res in send_request:{}".format(res))
if request_type == "post":
res = requests.post(url, data=data, headers=headers)
elif request_type == "get":
res = requests.get(url, data=data, headers=headers)
elif request_type == "put":
res = requests.put(url, data=data, headers=headers)
return res
def assert_result(response, key_word):
"""
断言函数
@param response: 结果响应对象
@param key_word: 断言关键字
@return:
"""
try:
assert key_word in response.text # 断言注册成功
return True
except AssertionError as e:
return False
except:
return False
uniquenumber.txt 文件:
该文件内容只有一个数字,用于在 get_uniquenumber()函数中,返回唯一的数字并更新这个
文件,返回的数字会拼接用户名字符串,生成唯一的用户名,避免在注册接口中出现重复的
情况。
server_info.py 文件:
在 server_info.py 文件中我们放入接口的 url 信息,其中 ip 和端口用变量的形式存储,各个
接口的地址是以字符串替换的方式保存在对应的变量中,方便统一为维护,内容如下所示
test_data.py 文件
在 test_data.py 文件中我们放入测试用例数据,每一条数据表示一条用例(在文件中,一条
数据对应一行数据),用例中不同的数据信息用”||”来分隔。
注意,第一部分内容看上去是表示用例的名称(如”register”, “login”等),实际上这个关键
字对应的是在 server_info.py 中存储的变量,在经过处理后,这个关键字会被当做变量来处
理,并用 server_info.py 中同名变量存储的值所代替。
那么,test_data.py 中每一条数据从左到右依次表示为接口地址、接口请求数据(包含使用
参数化和使用关联)、断言关键字、关联表达式和请求方式共 5 个数据。
其中参数化部分是以”${}”为标识,使用关联以”%{}”为标识
register||{"mobile": "138${unique_num1}", "codeIdentify": 2, "platform": 2}||"code":0||sms_code----"code":"(\w+)"||post
login||{"phone": "138${num1}", "sms_code": "%{sms_code}", "client_id": "c4", "client_secret": "secret", "grant_type": "sms_code"}||"access_token":||access_token----"access_token":"(\S+?)"||post
register||{"mobile": "138${unique_num1}", "codeIdentify": 1, "platform": 2}||"code":0||sms_code----"code":"(\w+)"||post
login||{"phone": "138${num1}", "sms_code": "%{sms_code}", "client_id": "c4", "client_secret": "secret", "grant_type": "sms_code"}||"access_token":||access_token----"access_token":"(\S+?)"||post
register||{"mobile": "138${unique_num1}", "codeIdentify": 2, "platform": }||"code":0||sms_code----"code":"(\w+)"||post
login||{"phone": "138${num1}", "sms_code": "%{sms_code}", "client_id": "c4", "client_secret": "secret", "grant_type": "sms_code"}||"access_token":||access_token----"access_token":"(\S+?)"||post
register||{"mobile": "138${unique_num1}", "codeIdentify": 0, "platform": 2}||"code":0||sms_code----"code":"(\w+)"||post
login||{"phone": "138${num1}", "sms_code": "%{sms_code}", "client_id": "c4", "client_secret": "secret", "grant_type": "sms_code"}||"access_token":||access_token----"access_token":"(\S+?)"||post
login||{"phone": "138${num1}", "sms_code": "", "client_id": "c4", "client_secret": "secret", "grant_type": "sms_code"}||"access_token":||access_token----"access_token":"(\S+?)"||post
login||{"phone": "138${num1}", "sms_code": "%{sms_code}", "client_id": "c4", "client_secret": "", "grant_type": "sms_code"}||"access_token":||access_token----"access_token":"(\S+?)"||post
Pre_data_handler.py 文件:
在 Pre_data_handler.py 文件里面将我们做数据和配置的处理,Pre_data_handler.py 文件包含函数如下:
1. pre_data_handler:处理数据文件 test_data.txt 里面的${}参数部分,将其替换为对应的参数值
2. test_data_post_handler:获取返回结果的关联数据存入global_values 里面
3. after_data_handler:处理数据文件 test_data.txt 里面的%{}参数部分,将其替换为关联值
4. get_test_data:获取 test_data.py 中的测试数据,将获取的每一行数据分隔成 interface(请求接口地址),test_data(请求数据),result_key(验证结果关键字),var_regx(关联处理表达式)和 request_type(请求接口类型)等五部分,然后调用 pre_data_handler 函数处理 test_data 数据里面的参数,最后将这五部分内容,作为测试用例(元组)返回到一个列表中,返回格式为
[(interface,test_data,result_key,var_regx,request_type)…],列表里面的一个元素(元组)就是一个测试用例
注意:test_data.txt 里面的接口必须事先在 server_info.py 定义好
Pre_data_hander.py 代码如下:
# encoding = utf-8
import re
from Server_info import *
from Util import *
global_values = {}
def pre_data_handler(data):
global global_values
if re.search(r"\$\{unique_\w+\}", data): # 匹配用户名参数,即"${www}"的
var_name = re.search(r"\$\{(unique_\w+)\}", data).group(1)
var_name = var_name.split("_")[1]
unique_num = str(get_uniquenumber()) # 获取唯一数
data = re.sub(r"\$\{unique_\w+\}", unique_num, data)
global_values[var_name] = str(unique_num)
if re.search(r"\$\{(\w+)\}", data): # 将注册的用户名的唯一数取出用于后续用户名拼接
replaceVarList = re.findall(r"\$\{(\w+)\}", data)
# print("需要替换的数据: %s" % replaceVarList)
for var_name in replaceVarList:
# print("替换前 data:", data)
data = re.sub(r"\$\{%s\}" % var_name, str(global_values[var_name]), data)
# print("替换后 data:", data)
return data
def test_data_post_handler(data, regx):
# 添加关联
# print("data: %s" % data)
global global_values
if regx.lower().find("None") >= 0:
return
var_name = regx.split("----")[0]
print("var_name: %s" % var_name)
regx_exp = regx.split("----")[1]
print("regx_exp: %s" % regx_exp)
if re.search(regx_exp, data):
global_values[var_name] = re.search(regx_exp, data).group(1)
# print("re.search(regx_exp,data).groups(): %s" % re.search(regx_exp,data).groups())
print("global_values: %s" % global_values)
return
return data
def get_test_data(data_file):
'''读取测试数据文件获取测试数据'''
test_cases = []
with open(data_file, 'r') as fp:
for line in fp:
line = line.strip()
interface_name = eval(line.split("||")[0].strip())
test_data = pre_data_handler(line.split("||")[1].strip())
result_key = line.split("||")[2].strip()
var_regx = line.split("||")[3].strip()
request_type = line.split("||")[4].strip()
test_cases.append((interface_name, test_data, result_key, var_regx, request_type))
# print(test_cases)
return test_cases
def after_data_handler(data):
'''处理关联数据'''
global global_values
# print("global_values: %s" % global_values)
while re.search(r"\%\{\w+\}", data):
var_name = re.search(r"\%\{(\w+)\}", data).group(1)
# print("var_name: %s" % var_name)
# print("关联替换前 data:", data)
data = re.sub(r"\%\{(\w+)\}", global_values[var_name], data,
1) # 把 data 中匹配到的参数用 global_values[var_name]替换掉,替换次数是 1
# print("关联替换后 data:", data)
return data
Html_report.py 文件:
该文件主要是实现网页格式输出的测试报告,实现的时候使用了 bottle 的第三方模块’ template’来渲染测试报告,生成最终的 html 代码,文件内容如下
Html_report.py:
#encoding=utf-8
from bottle import template
def report_html(data,html_name):
template_demo = """
<!-- CSS goes in the document HEAD or added to your external stylesheet -->
<style type="text/css">
table.hovertable {
font-family: verdana,arial,sans-serif;
font-size:11px;
color:#333553;
border-width: 1px;
border-color: #999999;
border-collapse: collapse;
}
table.hovertable th {
background-color:#00BFFF;
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #a9c6c9;
}
table.hovertable tr {
background-color:#d4e3e5;
}
table.hovertable td {
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #a9c6c9;
}
</style>
<!-- Table goes in the document BODY -->
<head>
<meta http-equiv="content-type" content="txt/html; charset=utf-8" />
</head>
<table class="hovertable">
<tr>
<th>接口 URL</th>
<th>请求数据</th>
<th>接口响应数据</th>
<th>接口调用耗时(单位:ms)</th>
<th>断言词</th>
<th>测试结果</th>
</tr>
% for url,request_data,response_data,test_time,assert_word,result,total_test_case, success_test_case,failed_test_case in items:
<tr onmouseover="this.style.backgroundColor='#ffff66';"
onmouseout="this.style.backgroundColor='#d4e3e5';">
<td>{{url}}</td>
<td>{{request_data}}</td>
<td>{{response_data}}</td>
<td>{{test_time}}</td>
<td>{{assert_word}}</td>
<td>
% if result == '失败':
<font color=red>
% end
{{result}}</td>
</tr>
% end
<tr>
<th>测试总数</th>
<th>测试成功</th>
<th>测试失败</th>
</tr>
<tr>
<td>{{total_test_case}}</td>
<td>{{success_test_case}}</td>
<td>{{failed_test_case}}</td>
</tr>
</table>
"""
html = template(template_demo, items=data)
with open(html_name+".html", 'wb') as f:
f.write(html.encode('utf-8'))
Send_mail.py 文件:
在该文件中,我们使用 python 的发送邮件的包(smtplib),实现了自动发送测试报告邮件
的功能
提示:
使用 python 程序发送邮件时,需要对所使用的邮箱开启一下 POP3/SMTP/IMAP 服务并得到
授权码,方便通过客户端程序连接服务器,服务开启开启方式是在邮箱的设置页面,设置的
时候需要短信验证获取授权码,之后在程序中连接邮箱服务器时用的密码就是所收到的授权
码。各个邮箱服务器设置略有不同,设置的时候网上搜索一下就知道具体的步骤了。
打开 qq 邮箱-设置-账户
send_mail.py:
import os
import smtplib
from email import encoders
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.utils import formataddr
def send_mail():
mail_host="smtp.qq.com" #设置服务器
mail_user="#你的发送邮箱" #用户名
mail_pass="#你的发送邮箱口令" #授权码口令
sender = '#你的发送邮箱'
receivers = ['#你的接收邮箱'] # 接收邮件,可设置为你的 QQ 邮箱或者其他邮箱
# 创建一个带附件的实例
message = MIMEMultipart()
message['From'] = formataddr(["#发送人", "#你的发送邮箱"])
message['To'] = ','.join(receivers)
subject = '接口自动化测试执行报告'
message['Subject'] = Header(subject, 'utf-8')
message["Accept-Language"]="zh-CN"
message["Accept-Charset"]="ISO-8859-1,utf-8,gbk"
# 邮件正文内容
message.attach(MIMEText('最新执行的接口自动化测试报告,请参阅附件内容!', 'plain',
'utf-8'))
# 构造附件 1,传送测试结果的 html 文件
att = MIMEBase('application', 'octet-stream')
att.set_payload(open("接口测试报告.html", 'rb').read())
att.add_header('Content-Disposition', 'attachment', filename=('utf-8', '', "接口测试报告.html"))
encoders.encode_base64(att)
message.attach(att)
try:
smtpObj = smtplib.SMTP(mail_host)
smtpObj.login(mail_user, mail_pass)
smtpObj.sendmail(sender, receivers, message.as_string())
print("邮件发送成功")
except smtplib.SMTPException as e:
print("Error: 无法发送邮件", e)
if __name__ == '__main__':
send_mail()
Test_main.py 文件:
主函数文件中调用 get_test_data()方法获取测试用例数据,之后依次遍历所有用例信息,进
行发送请求、验证返回结果等处理,在发送请之前,进行 data 数据处理,如果 data 里面包
含了“%”关联参数关键字,则调用 after_data_handler()方法替换关联参数。
接着处理测试结果、报告,发送邮件。
# encoding = utf-8
from Pre_data_handler import *
from Html_report import report_html
import time
from Send_mail import send_mail
total_test_case = 0 # 记录总共测试用例数
success_test_case = 0 # 记录成功用例数
failed_test_case = 0 # 记录失败用例数
test_cases = get_test_data("test_data.txt")
test_results = []
# 逐一执行用例
for test_cases in test_cases:
url = test_cases[0]
data = test_cases[1]
request_type=test_cases[4]
test_case_result = ""
if "%" in data: # 如果数据中有关联数据
data = eval(after_data_handler(data)) # 处理关联数据
result_key = test_cases[2]
if "getVertificationCode" in url: # 请求地址是注册,就是json格式,加请求头
register_headers = {'Content-Type': 'application/json;charset=UTF-8'}
print(data)
start_time = time.time()
re = send_request(url, data, register_headers,request_type)
end_time = time.time()
test_time = int((end_time - start_time) * 1000)
register_headers = None
total_test_case += 1
print(re.text)
if assert_result(re, result_key):
success_test_case += 1
test_case_result = "成功"
print("断言成功")
else:
failed_test_case += 1
test_case_result = "失败"
print("断言失败")
try:
test_data_post_handler(re.text, test_cases[3])
print("获取关联变量成功")
except:
print("从请求结果%s 中,尝试获取关联变量失败,表达式%s" % (re.text, test_cases[3]))
test_results.append((re.url, data, re.text, test_time, test_cases[2], test_case_result,total_test_case, success_test_case,failed_test_case))
html_name = '接口测试报告'
report_html(test_results, html_name)
send_mail()