一、说明
该接口自动化测试时基于python+requests库+excel进行实现的,没有选用python+requests+uniitest+htmltestrunner或者python+requests+pytest+htmltestrunner框架:这两个框架的好处整个框架结构全部都封装好了,只需要往里面添加业务逻辑即接口测试py文件即可,但是有个致命的缺陷,就是测试用例的易维护性不是很好,当接口发生变化,就要去直接修改业务逻辑的py文件,而且测试数据都是写死在每一个函数中,或者持久化在数据库中,需要直接修改py文件,所以就没有才这两个框架,只选取了其中必要的组成部分:python+requests,然后自己完成后续业务逻辑;
二、接口测试框架的拆解
现有框架是根据HTTP请求的请求、响应的生命周期来进行规划框架的目录结构的;
1、HTTP请求的生命周期
1.1、建立连接;
1.2、接收请求;
1.3、处理请求;
1.4、访问资源;
1.5、构建响应;
1.6、发送响应;
1.7、记录事务处理过程;
2、框架目录结构
2.1、base包:封装请求方法
base包下new_test.py文件,主要用于封装常规的请求方法;
http请求方式中主要有8种常见的请求方式:GET,POST,HEAD,OPTIONS,PUT,DELETE,TRACE,CONNECT方法,这几种请求方式详细使用方式请自行百度;
我只封装了其中最常见的四种方式:GET,POST,DELETE,PUT方式,因为项目中只用到了这四种方式;
new_test.py:
1 #!/usr/bin/env python
2 #-*- coding:utf-8
3 importrequests,json4 ##原主程序:将data及headers的为空的情况考虑进去,就会报错404
5 classRunmethod:6 ###get方法不会有data,查询参数都是跟在url后面的;需要后再进行优化;
7 def getmain(self,url,data=None,headers=None):8 res =None9 if headers !=None: ####正常的get请求;
10 if data ==None:11 res = requests.get(url=url, headers=headers)12 else:13 res = requests.get(url=url, data=data, headers=headers)14
15 else: ##非常规的get请求,如本身是get请求,为了保证数据的安全性直接使用get方法,将原本卸载url中的参数全部写在data里面;
16 if data ==None:17 res = requests.get(url=url)18 else:19 res = requests.get(url=url, data=data)20 returnres21
22 def postmain(self,url,data=None,headers=None):23 res =None24 if headers !=None: ####正常的post请求;
25 if data ==None:26 res = requests.post(url=url,headers=headers)27 else:28 res = requests.post(url=url, data=data, headers=headers)29 else: ##非常规的post请求,如本身是get请求,为了保证数据的安全性直接使用post方法,将原本卸载url中的参数全部写在data里面;
30 if data ==None:31 res = requests.post(url=url)32 else:33 res = requests.post(url=url,data=data)34 returnres35 def runmain(self,method,url,data=None,headers=None):36 res =None37 if method == "GET":38 res = self.getmain(url,data=None,headers=None)39 elif method == "POST":40 res = self.postmain(url,data=None,headers=None)41 returnres42 ##测试类:只考虑正常情况,不考虑空值,
43 classRunmethod2:44 defgetmain(self,url,data,headers):45 res = requests.get(url=url, data=data, headers=headers)46 returnres47 defpostmain(self,url,data,headers):48 res = requests.post(url=url, data=data, headers=headers)49 returnres50 defrunmain(self,method,url,data,headers):51 res =None52 if method == "GET":53 res =self.getmain(url, data, headers)54 elif method == "POST":55 res =self.postmain(url, data, headers)56 returnres57
58 ##只考虑data是否是未空,前两种方式在调试过程中有问题;
59 classRunmethod3:60 ###get请求方式
61 def getmain(self,url,headers =None):62 res =None63 if headers !=None:64 res = requests.get(url=url,headers =headers)65 else:66 res = requests.get(url=url)67 returnres.json()68 ##post请求方式
69 def postmain(self,url,data=None,headers=None):70 res =None71 if headers !=None:72 res = requests.post(url=url,data=data, headers=headers)73 else:74 res = requests.post(url=url,data=data)75 returnres.json()76 def putmain(self,url,data=None,headers =None):77 res =None78 if headers !=None:79 res = requests.post(url=url, data=data, headers=headers)80 else:81 res = requests.post(url=url, data=data)82 returnres.json()83 def deletemain(self,url,data=None,headers =None):84 res =None85 if headers !=None:86 res = requests.delete(url=url,data = data, headers=headers)87 else:88 res = requests.delete(url=url, data=data)89 returnres.json()90
91 def runmain(self,method,url,data=None,headers=None):92 res =None93 if method == "GET":94 res = self.getmain(url,headers =headers)95 elif method == "POST":96 res = self.postmain(url, data=data, headers=headers)97 #return json.dumps(res,indent=2,ensure_ascii=False)
98 elif method == "PUT":99 res = self.putmain(url,data=data,headers=headers)100 print(type(res))101 elif method == "DELETE":102 res = self.deletemain(url,data=data,headers=headers)103 return json.dumps(res,indent=2,ensure_ascii=False)
View Code
2.2、main包:主程序执行入口
根据读取到的测试用例excle表中相关字段的信息进行判断进行相关的处理;
2.2.1、获取到整个测试用例excel表,取到每行数据即每一条测试用例,取到每条测试用例中的每个字段,然后根据每个字段的相关信息执行每条用例;
2.2.2、主程序执行:判断是否执行-判断是否有依赖-判断依赖类型,根据依赖类型执行不同的方法-获取到正确的依赖数据后执行用例-获取响应报文与与预期结果对比,回写测试执行结果-最后检查是否需要清除测试数据-发送测试报告给对应人员;
#!/usr/bin/env python#-*- coding:utf-8###下面添加系统路径是因为代码部署在服务器上、在windows cmd命令行下运行报错提示找不到models添加的;
importos,sys
base_dir= os.path.dirname(os.path.abspath(__file__))
base_dir_file=os.path.dirname(base_dir)
sys.path.append(base_dir)
sys.path.append(base_dir_file)
sys.path.append(os.path.abspath(__file__))#print(sys.path)
print(base_dir,base_dir_file)#import sys#import os#
#curPath = os.path.abspath(os.path.dirname(__file__))#rootPath = os.path.split(curPath)[0]#PathProject = os.path.split(rootPath)[0]#sys.path.append(rootPath)#sys.path.append(PathProject)
"""# 导入绝对路径"""
#sys.path.append("D:/untitled/autotest")#sys.path.append("D:/untitled/autotest/utils")
print(sys.path)from base.new_test importRunmethod,Runmethod2,Runmethod3from data.get_data importgetdataimportjsonfrom utils.common import *
from utils.op_excel importOper_excelfrom data.data_config importglobal_varfrom data.depend_data importdepentdatafrom utils.send_email importsendemail1,sendemail2from utils.op_database importdatabase_handlerfrom utils.op_file importop_file_handle###按照此时的写法post是通了,但是get报错了,get返回的是html文件,而程序是用json去解析的
classruntest:def __init__(self):
self.run_method=Runmethod3()
self.data=getdata()
self.obj_iscomtent=commonutil()
self.obj_op_excel=Oper_excel()
self.obj_sendemail=sendemail1()
self.obj_sendemail2=sendemail2()
self.obj_file_handle=op_file_handle()#print("========================================>>>>这是op_excel实例化打印的路径:%s" %self.obj_op_excel)
##主程序执行
defrun(self):
result=[]
total_run=0
pass_count=[]
fail_count=[]#统计接口测试用例数量
print("===================>>>主程序开始执行")
row_counts=self.data.get_case_Lines()print("================>>>>测试用例总计:%s <<<<======================" %(row_counts-1))###for循环下直接return是有坑的,在第一次循环的时候就直接返回了,没有进行后续的循环;
for i in range(1,row_counts):
url=self.data.get_url(i)
mehtod=self.data.get_request_method(i)
data=self.data.get_request_datajson(i)#print("=====================???",data)
#if data != None:
#data = json.dumps(data)
#data = self.data.get_request_datajson(i)
headers =self.data.header_is_run(i)###判断是否运行
is_run =self.data.get_is_run(i)
expect=self.data.get_expect_data(i)
depend_type=self.data.get_depeed_type(i)print("执行%s条用例" %i,url,mehtod,data,is_run)#print(headers)
###判断接口用例是否执行
ifis_run:
total_run+=1
###判断执行接口用例是否需要依赖数据
#depend_case_id = self.data.is_depend(i)
depend_case_id =self.data.get_depend_id(i)print("=========!!!!!!!依赖id",depend_case_id)if depend_case_id !=None:##获取依赖的响应数据
#try:
#self.obj_dependdata = depentdata(self.data.get_depend_id(i))
#depend_response = self.obj_dependdata.get_dependentdata(i)
#except Exception as e:
#print("========>>>执行第%s条依赖用例失败,请检查依赖用例\r\n %s" %(i,e))
###获取数据依赖的key
self.obj_dependdata =depentdata(self.data.get_depend_id(i))
depend_response=self.obj_dependdata.get_dependentdata(i)print("============>>我正在打印依赖用例的响应报文", depend_response)
depend_key=self.data.get_depend_field(i)###根据依赖类型进行依赖数据处理
"""依赖数据可能会存在以下几种方式:1、header中依赖,如token;2、请求body中的参数依赖;
现在写的逻辑是直接认为是body依赖,没有考虑header依赖,我现在座的就是header依赖"""
if depend_response !=None:
access_type= "Bearer"
###header_depend需要依赖登录接口,获取token
if depend_type == "header_depend":####header依赖数据的处理逻辑,因为我的系统header依赖为token,所以我就直接做的token依赖处理
token = access_type +depend_responseprint("===============>>>这是token",token)
headers[depend_key]=token###将登录获取到的token写入到cfg.py文件中
self.obj_file_handle.write_file_token_handler(token)elif depend_type == "body_depend":pass
elif depend_case_id ==None:if depend_type == "header_depend_token":#headers[depend_key] = self.obj_file_handle.read_file_token_handler()
headers[depend_key] = self.obj_file_handle.read_file_handler("Authorization")print(headers[depend_key])print("++++++++++++++header_depend_token分支的这是处理依赖后的headers:",headers)#res = self.run_method.runmain(mehtod,url,data,headers)
###判断请求方式,根据请求方式执行请求想
#if mehtod == "GET":
#res = self.run_method.runmain(mehtod,url)
#elif mehtod == "POST":
#res = self.run_method.runmain(mehtod, url, json.dumps(data), headers)
res = self.run_method.runmain(mehtod,url, data = json.dumps(data), headers =headers)print("==========>>>>:这是返回的报文类型:",type(res))ifself.obj_iscomtent.iscomtent(expect,res):#print("测试通过")
self.data.write_exact_res(i,'pass')
pass_count.append(i)print("********************这是在清理测试数据是否执行sql语句", self.data.get_clean_testdata(i))if self.data.get_clean_testdata(i) == "YES":
clean_sql=self.data.get_clean_sql(i)print("================>>>>这是需要执行的sql语句",clean_sql,type(clean_sql))##执行sql语句
if clean_sql != "None":try:
obj_database_hendler=database_handler()
sql_excute_res=obj_database_hendler.cleantestdata(clean_sql)print("=====================>>>>这是执行的sql的结果:",sql_excute_res)###写入清除数据sql执行结果
self.data.write_sql_excute_res(i, sql_excute_res)exceptException as e:
self.data.write_sql_excute_res(i, e)else:#print("测试失败")
self.data.write_exact_res(i,res)
fail_count.append(i)
result.append(res)#print(res.text)
###清除测试数据
###获取是否需要清除测试数据
#return json.dumps(res.json(),ensure_ascii=False,indent=2)
print("========测试通过的测试用例数量:%s" %len(pass_count))print("========测试失败的测试用例数量:%s" %len(fail_count))print("========总计执行的测试用例数量:%s" %total_run)print("========总计未执行测试用例数量:%s" %(row_counts-1-total_run))print("========总计测试用例数量:%s" %(row_counts-1))#print(pass_count,fail_count)
try:
self.obj_sendemail2.send_main(pass_count,fail_count)exceptException as e:print("========================》》》发送测试邮件失败,请检查网络配置:%s" %e)returnresultif __name__ == "__main__":print("===============================>>>>>>当前执行路径:%s" %os.path.abspath(__file__))print("=======================?????执行shell命令:%s" %os.popen("pwd").read())print("=======================?????执行shell命令:%s" %os.popen("cd ..").read())print("=======================?????执行shell命令:%s" %os.popen("pwd").read())
obj_run=runtest()
res=obj_run.run()#for i in res:
#print(i)
run_autotest.py
2.3、utils包:工具包,用于存放操作类方法
2.3.1、common.py主要是用于判断预期结果与实际结果是否一致及根据业务需要编写特定逻辑类;
1 #!/usr/bin/env python
2 #-*- coding:utf-8
3 ###检查接口是否正确,并检查响应报文是否正确
4 classcommonutil:5 defiscomtent(self,str_one,str_two):6 """
7 判断一个参数是否在另一个参数中8 str_one:查找参数9 str_two:被查找参数10 """
11 flag =None12 ##可以对str_one进行数据格式的验证
13 if str_one instr_two:14 flag =True15 else:16 flag =False17 returnflag18
19 if __name__ == "__main__":20 a = "a"
21 b = "adb"
22 res =commonutil().iscomtent(a,b)23 print(res)
common.py
2.3.2、op_database.py主要是用于数据库操作用于检查测试数据是否正确,以及清除测试数据;
该类直接使用的原生sql语句
1 ####主要用户数据库操作
2 importpymysql3 ###清除测试数据
4 classdatabase_handler:5 ###实例化就连接数据库并获取游标当前位置
6 def __init__(self):7 self.conn = pymysql.connect(host='*****', port=3306, user='****', passwd='*****..', db='*****')8 ###游标,当前位置
9 self.cursor =self.conn.cursor()10 ###清除测试数据
11 #def cleantestdata(self,table,feild,data):
12 #try:
13 #sql = "delete from %s where %s = '%s'" %(table,feild,data)
14 ## sql = "delete from tbl_user where user_name = 'autotest002'"
15 #print(sql)
16 #res=self.cursor.execute(sql)
17 #self.conn.commit()
18 #print(res)
19 #except Exception as e:
20 #print(e)
21 #self.conn.close()
22
23 defcleantestdata(self, sql_list):24 res =""
25 try:26 #sql = "delete from %s where %s = '%s'" % (table, feild, data)
27 #sql = "delete from tbl_user where user_name = 'autotest002'"
28 for sql ineval(sql_list):29 result =self.cursor.execute(sql)30 print("========>>>这是SQL语句的详细情况",sql)31 self.conn.commit()32 res = "====================>>清除测试数据成功:%s" %result33 exceptException as e:34 res =e35 self.conn.close()36 returnres37
38 if __name__ == "__main__":39 #sql = ["update tbl_user set `enable`= 1 where id = 585"]
40 sql = ["delete from tbl_robot where robot_id = '12345678901234567890'","delete from tbl_user_robot where robot_id = '12345678901234567890';"]41
42 print(type(sql))43 obj=database_handler()44 print(obj.cleantestdata(sql))
op_database.py
2.3.3、op_excel.py主要用于操作excel表-用例表,读取数据以及写入测试数据
1 #!/usr/bin/env python
2 #-*- coding:utf-8
3 importxlrd4 from xlutils.copy importcopy5 from data.data_config import *
6 classOper_excel:7 def __init__(self,data_path=None):8 ifdata_path:9 self.data_path =data_path10 self.data_sheet =011 else:12 self.data_path = "../data_file/auto_test_case.xls"
13 self.data_sheet =014 self.data =self.get_excel_data()15 #读取excel表的数据
16 defget_excel_data(self):17 #print(self.data_path,self.data_sheet)
18 book =xlrd.open_workbook(self.data_path)19 #sheet = book.sheet_by_index(self.data_sheet)
20 sheet =book.sheet_by_index(self.data_sheet)21 #for i in sheet.get_rows():
22 #print(i)
23 returnsheet24 ###获取单元格行数
25 defget_line(self):26 tables =self.data27 returntables.nrows28 #return tables.ncols
29 ###获取某个单元格的内容
30 defget_cell_value(self, row, col):31 returnself.data.cell_value(row, col)32 ###写入实际结果:就是修改excel表格
33 defupdate_data(self,row,col,value):34 """
35 写入excel表数据36 :param row:37 :param col:38 :param value:39 :return:40 """
41 book =xlrd.open_workbook(self.data_path)42 #sheet = book.sheet_by_index(self.data_sheet)
43 pre_data =copy(book)44 sheet_data =pre_data.get_sheet(self.data_sheet)45 sheet_data.write(row,col,value)46 pre_data.save(self.data_path)47 ###根据caseid查找对应行的内容
48 defget_row_data(self,caseid):49 row_num =self.get_row_num(caseid)50 returnself.get_row_values(row_num)51 ###根据对应的caseid找到对应的行号
52 defget_row_num(self,caseid):53 num =054 colsdata =self.get_col_data()55 #print(colsdata)
56 for coldata incolsdata:57 if caseid incoldata:58 returnnum59 num+=1
60 ##获取某一列的内容
61 def get_col_data(self,colid=None):62 if colid !=None:63 cols =self.data.col_values(colid)64 else:65 cols =self.data.col_values(0)66 returncols67
68 ##根据行号找到该行的内容
69 defget_row_values(self,row):70 tables =self.data71 row_data =tables.row_values(row)72 returnrow_data73
74 #excel表写入数据
75 #print(Oper_excel)
76 if __name__ == "__main__":77 data =Oper_excel()78 print(data.get_line(),type(data.get_line()))79 for i inrange(data.get_line()):80 print(i,data.get_cell_value(i,get_case_expect()))81 print(data.get_row_data("web_api_002"))
op_excel.py
2.3.4、op_file.py主要是用户操作文本文件,对配置文件的操作:读取和写入(对读取到的数据进行处理)
####处理配置文件
classop_file_handle:def __init__(self,data_path=None,para=None):ifdata_path:
self.data_path=data_pathelse:
self.data_path= "../data/"
ifpara:
self.para=paraelse:
self.para= "cfg.text"
defwrite_file_token_handler(self,token):###将配置文件读取出来
src_f = open(self.data_path+self.para,'r',encoding="utf-8")
comtent=src_f.readlines()
src_f.close()###修改配置文件
dst_f = open(self.data_path+self.para,'w',encoding="utf-8",newline="")for data incomtent:if data.startswith("Authorization"):#dst_f.write(re.sub("Authorization = \d","Authorization ="+token,data,0))
data_split = data.split("=")
data_split[1] =tokenprint("========>>>这是写入的token",data_split[1])
dst_f.write("%s = %s\n" %(data_split[0].strip(),data_split[1].strip()))else:
dst_f.write(data)
dst_f.close()###读取token配置文件,对token的处理
defread_file_token_handler(self):
with open(self.data_path+self.para,'r',encoding="utf-8") as f:for data inf.readlines():if data.startswith("Authorization"):
data_split= data.split("=")print(data_split[1].strip())return data_split[1].strip()###依次读取配置文件,处理配置文件信息
defread_file_handler(self,para):
with open(self.data_path+self.para,'r',encoding="utf-8") as f:for data inf.readlines():ifdata.startswith(para):
data_split= data.split("=")#print(data_split[0].strip(),data_split[0].strip())
return data_split[1].strip()###读取邮件要发送的附件;
classop_excel_handle:def __init__(self, data_path=None, para=None):ifdata_path:
self.data_path=data_pathelse:
self.data_path= "../data_file/"
ifpara:
self.para=paraelse:
self.para= "auto_test_case.xls"
###读取邮件需要发送的附件
defread_file_email_handler(self):
with open(self.data_path+self.para,'rb') as f:
cont=f.read()returncontif __name__ == "__main__":
obj_op_file_handle=op_file_handle()#obj_op_file_handle.write_file_handler("测试写入")
obj_op_file_handle.read_file_token_handler()
op_excel.py
2.3.5、op_json.py:主要是用于操作json文件;json文件主要是存放请求body地方,存放格式是json格式
1 #!/usr/bin/env python
2 #-*- coding:utf-8
3 importjson4 classdata_read_handle:5 def __init__(self,data_path=None,para=None):6 ifdata_path:7 self.data_path =data_path8 else:9 self.data_path = "../data_file/"
10 ifpara:11 self.para =para12 else:13 self.para = "usermanagement.json"
14 self.data =self.json_data_read()15 defjson_data_read(self):16 f = open(self.data_path+self.para,encoding='utf-8')17 data =f.read()18 data =json.loads(data)19 #print(type(data))
20 f.close()21 #f = open(self.data_path+self.para)
22 #data = json.load(f)
23 returndata24 ###重写json文件
25 #def json_data_reload(self,data):
26 #f = open(self.data_path+self.para,"w",encoding="utf-8")
27 #json.dump(data,f,ensure_ascii=False,indent=4)
28 deftaskmanagement_data_read(self):29 pass
30 ##根据参数获取对应的body
31 defget_data_read(self,para):32 if para ==None:33 returnNone34 returnself.data[para]35
36 if __name__ == "__main__":37 data_read_handler=data_read_handle()38 print(data_read_handler.data["login"])
op_json.py
2.3.6、send_mail.py:smtplib以及exchangelib库,根据公司邮箱的协议选择不同的库,用于发送测试报告给相关的人员;
1 #!/usr/bin/env python
2 #-*- coding:utf-8
3 """
4 邮件发送中的坑:qq可以向qq及163邮箱发送邮件;5 163向qq发送邮件会被邮件服务器退回,可能原因是qq邮箱在接收邮件的时候按照某种规则进行校验处理了的;6 而且一定要小心过期教程的坑;7 后期可对邮件发送的类进行重新整合,将两个类合并为一个类,做兼容性的处理;8 """
9 import smtplib ###发送邮件的库
10 from email.mime.text import MIMEText ###邮件格式的库
11 importtime12 from exchangelib importDELEGATE, Account, Credentials, Message, Mailbox, HTMLBody,FileAttachment13 importos14 """
15 1、sendemail1:使用的是smtplib库,可用于常规邮箱如qq、163邮箱的邮件发送;16 2、sendemail2:使用的是exchangelib库,可以用来发送小众邮箱如公司级的邮箱的邮件发送;17 """
18 from utils.op_file importop_file_handle,op_excel_handle19 classsendemail1:20 def __init__(self):21 pass
22 ##发送邮件
23 globalsend_user,email_host,password24 send_user = "[email protected]"
25 #email_host = "smtp.countrygarden.com.cn"
26 email_host = "smtp-mail.outlook.com"
27 ###授权密码
28 password = "654#@!qaz1"
29 def send_mail(self,user_list,sub,content): ###接收邮件的用户list;主题;内容
30 user = "我有才2号"+""
31 message = MIMEText(content,_subtype='plain',_charset='utf-8')32 message['Subject'] =sub33 message['From'] =user34 message['To'] = ";".join(user_list)35 ##连接邮件服务器
36 try:37 server =smtplib.SMTP()38 server.connect(email_host)39 server.login(send_user,password)40 server.sendmail(user,user_list,message.as_string())41 server.close()42 print("发送邮件成功")43 exceptsmtplib.SMTPException as e:44 print(e)45 defsend_main(self,pass_list,fail_list):46 now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())47 pass_num =len(pass_list)48 fail_num =len(fail_list)49 count_num = pass_num+fail_num50 pass_rate = "%.2f%%" %(pass_num/count_num*100)51 fail_rate = "%.2f%%" %(fail_num/count_num*100)52 #user_list = ["[email protected]", "[email protected]"]
53 user_list = ["[email protected]"]54 sub = "这是我司测试部关于线上环境接口监测情况报告"
55 content ="%s 此次运行接口测试用例总计:%s,通过用例:%s,失败用例:%s,通过率为:%s,失败率为:%s" %(now,count_num,pass_num,fail_num,pass_rate,fail_rate)56 self.send_mail(user_list,sub,content)57 classsendemail2:58 def __init__(self):59 self.obj_op_file_handle =op_file_handle()60 self.obj_op_excel_handle =op_excel_handle()61
62 ###发送邮件
63 defsend_email(self, to, subject, body):64 creds =Credentials(65 #username='[email protected]',
66 #password='654#@!qaz1'
67 username=self.obj_op_file_handle.read_file_handler("username"),68 password = self.obj_op_file_handle.read_file_handler("password")69 )70 account =Account(71 #primary_smtp_address='[email protected]',
72 primary_smtp_address = self.obj_op_file_handle.read_file_handler("sender"),73 credentials=creds,74 autodiscover=True,75 access_type=DELEGATE76 )77 m =Message(78 account=account,79 subject=subject,80 body=HTMLBody(body),81 to_recipients=[Mailbox(email_address=to)]82 )83 ###邮件附件
84 #with open(os.path.abspath(r"../data_file/auto_test_case.xls"), "rb") as f:
85 #with open(r"../data_file/auto_test_case.xls", "rb") as f:
86 cont =self.obj_op_excel_handle.read_file_email_handler()87 attch_file = FileAttachment(name = "auto_test_case.xls",content=cont)88 m.attach(attch_file)89 m.send()90 ##生成发送内容并发送邮件
91 defsend_main(self, pass_list, fail_list):92 now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())93 pass_num =len(pass_list)94 fail_num =len(fail_list)95 count_num = pass_num +fail_num96 pass_rate = "%.2f%%" % (pass_num / count_num * 100)97 fail_rate = "%.2f%%" % (fail_num / count_num * 100)98 #user_list = ["[email protected]", "[email protected]"]
99 #send_list = ["[email protected]", "[email protected]",
100 #"[email protected]"]
101 #"[email protected]",
102 send_list = eval(self.obj_op_file_handle.read_file_handler("recv_list"))103 subject = "这是我司测试部关于线上环境接口监测情况报告"
104 body = "%s 此次运行接口测试用例总计:%s:\r\n通过用例:%s;\r\n失败用例:%s;\r\n通过率为:%s;\r\n失败率为:%s;"\105 %(now, count_num, pass_num, fail_num, pass_rate, fail_rate)106 for send_to insend_list:107 self.send_email(send_to, subject, body)108
109
110 if __name__ == "__main__":111 sen =sendemail2()112 sen.send_main([1,2,3,4],[8,5,7,8])
send_mail.py
2.4、data包:对excel用例的操作类方法及配置文件cfg
2.4.1、data_config.py:主要用于存在excel表测试用例每个字段的对应关系,以及获取每个字段的方法;
1 #!/usr/bin/env python
2 #-*- coding:utf-8
3 from utils.op_json importdata_read_handle4 classglobal_var:5 case_id = 0 ##用例编号
6 case_model = 1 ##测试模块
7 case_pre = 2 ##请求前置条件
8 case_urlpath = 3 ##请求URL
9 case_method = 4 ##请求方式
10 case_run = 5 ##接口是否运行
11 case_msg = 6 ##接口备注说明
12 case_header = 7 ##请求头数
13 case_depend_type = 8
14 case_dependid = 9 ##依赖caseID
15 case_dependdata = 10 ##依赖数据
16 case_dependfileld = 11 ##依赖数据所属字段
17 case_dataid = 12 ##请求数据
18 case_expect = 13 ##预期结果
19 case_exact = 14 ##实际结果
20 case_clean_testdata = 15 ### 是否清除测试数据 ###由于系统做的都是逻辑删除;
21 case_clean_sql = 16
22 case_is_sql_success = 17 ###获取执行结果
23 ###获取每一条接口测试用例的每个字段
24 defget_case_id():25 returnglobal_var.case_id26 defget_case_model():27 returnglobal_var.case_model28 defget_case_pre():29 returnglobal_var.case_pre30 defget_case_urlpath():31 returnglobal_var.case_urlpath32 defget_case_method():33 returnglobal_var.case_method34 defget_case_run():35 returnglobal_var.case_run36 defget_case_msg():37 returnglobal_var.case_msg38 defget_case_header():39 returnglobal_var.case_header40 defget_case_depend_type():41 returnglobal_var.case_depend_type42 defget_case_dependid():43 returnglobal_var.case_dependid44 defget_case_dependdata():45 returnglobal_var.case_dependdata46 defget_case_dependfileld():47 returnglobal_var.case_dependfileld48 defget_case_dataid():49 returnglobal_var.case_dataid50 defget_case_expect():51 returnglobal_var.case_expect52 defget_case_exact():53 returnglobal_var.case_exact54 defget_case_headers_data():55 #data_headers = {
56 #"Host": "10.8.202.148:10084",
57 #"Connection": "keep-alive",
58 ## "Content-Length": "81",
59 #"Accept": "*/*",
60 #"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36",
61 #"Content-Type": "application/json",
62 ## "Origin": "http://10.8.202.148:10000",
63 ## "Referer": "http://10.8.202.148:10000/robot-management/login.html",
64 #"Accept-Encoding": "gzip, deflate",
65 #"Accept-Language": "zh-CN,zh;q=0.9",
66 ## "Authorization": "Bearer 22db77cb-9647-448c-923f-8b3c7e8e576e"
67 #}
68 obj_op_json = data_read_handle(para="header.json")69 print(obj_op_json.json_data_read())70 data_headers = obj_op_json.get_data_read(para="normal_headers")71 returndata_headers72 defget_case_clean_testdata():73 returnglobal_var.case_clean_testdata74 defget_case_clean_sql():75 returnglobal_var.case_clean_sql76 defget_is_sql_success():77 returnglobal_var.case_is_sql_success78 if __name__ == "__main__":79 print(get_case_headers_data())
data_config.py
2.4.2、depend_data.py:主要用处理测试依赖数据,测试依赖数据:依赖类型、依赖ID(依赖哪一条用例)、依赖数据(匹配的模式)、依赖数据所属字段(是哪个字段需要依赖数据);该py文件主要用于执行依赖用例,然后对依赖用例的响应报文进行解析获取依赖数据;将依赖的内容获取到拼接到依赖的地方;
1 #!/usr/bin/env python
2 #-*- coding:utf-8
3 from utils.op_excel importOper_excel4 from data.get_data importgetdata5 importjson6 from base.new_test importRunmethod37 from jsonpath_rw importjsonpath,parse8 from utils.common importcommonutil9 classdepentdata:10 """
11 通过caseid去获取整航数据12 """
13 def __init__(self,caseid):14 self.caseid =caseid15 self.opexcel =Oper_excel()16 self.data =getdata()17 self.run =Runmethod3()18
19 ###根据caseid去获取该caseid的整行数据
20 defget_case_line_data(self):21 returnself.opexcel.get_row_data(self.caseid)22 ###执行依赖数据的测试用例
23 defrun_dependent(self):24 row =self.opexcel.get_row_num(self.caseid)25 url =self.data.get_url(row)26 mehtod =self.data.get_request_method(row)27 data =self.data.get_request_datajson(row)28 if data !=None:29 data =json.dumps(data)30 headers =self.data.header_is_run(row)31 res =self.run.runmain(mehtod, url, data, headers)32 print("正在执行依赖用例:%s" %res)33 returnres34 ###根据规则key提取依赖数据的响应报文中的特定的数据,然后返回
35 defget_dependentdata(self,row):36 res =self.get_dependentdata_excute(row)37 returnres38 defget_dependentdata_excute(self,row):39 depend_data =self.data.get_depend_key(row)40 print("============>>依赖用例的数据", depend_data)41 ###依赖用例执行的响应报文
42 response_data =self.run_dependent()43 print("============>>依赖用例的响应报文", response_data)44 ###依赖测试的预期结果
45 expect_data =self.data.get_expect_data(row)46 print(expect_data)47 obj_commonutil =commonutil()48 print("判断预期实际结果",obj_commonutil.iscomtent(expect_data,response_data))49 if obj_commonutil.iscomtent(expect_data,response_data) ==True:50 print("++++++++++++,判断预期响应报文包含在实际响应报文中")51 print(depend_data)52 print(response_data)53 try:54 json_exe =parse(depend_data)55 madle = json_exe.find(json.loads(response_data)["data"])56 res = [math.value for math inmadle][0]57 print(res)58 returnres59 exceptException as e:60 returne61 else:62 print("预期的响应报文不包含在实际报文中")63 returnNone64
65
66
67
68
69 if __name__ == "__main__":70 ###excel中的提取规则运行有问题,需要后续检查
71 obj_depentdata = depentdata("web_api_002")72 print(obj_depentdata.get_dependentdata(3))
depend_data.py
2.4.3、get_data.py:主要是用于获取excel用例表每一个字段的内容;
1 #!/usr/bin/env python
2 #-*- coding:utf-8
3 from utils.op_excel importOper_excel4 from data.data_config import *
5 from utils.op_json import *
6 from utils.op_file import *
7
8 classgetdata:9 def __init__(self):10 self.oper_excel =Oper_excel()11 ##去获取excel表里面的case行数
12 defget_case_Lines(self):13 returnself.oper_excel.get_line()14 ###获取程序是否运行
15 defget_is_run(self,row):16 flag =None17 col =get_case_run()18 run_model =self.oper_excel.get_cell_value(row,col)19 if run_model == "YES":20 flag =True21 else:22 flag =False23 returnflag24 ###是否携带header
25 defheader_is_run(self,row):26 col =get_case_header()27 header =self.oper_excel.get_cell_value(row,col)28 if header == "YES":29 returnget_case_headers_data()30 else:31 returnNone32 ###获取请求方式
33 defget_request_method(self,row):34 col =get_case_method()35 request_method =self.oper_excel.get_cell_value(row,col)36 returnrequest_method37 ###获取URL
38 defget_urlpath(self,row):39 col =get_case_urlpath()40 url =self.oper_excel.get_cell_value(row,col)41 returnurl42 ###获取请求数据关键字
43 defget_request_dataid(self,row):44 col =get_case_dataid()45 dataid =self.oper_excel.get_cell_value(row,col)46 if dataid == '':47 dataid =None48 returndataid49 ###通过关键字获取请求数据
50 defget_request_datajson(self,row):51 obj_op_json =data_read_handle()52 data =obj_op_json.get_data_read(self.get_request_dataid(row))53 #data = obj_op_json.data
54 returndata55 ###获取预期结果
56 defget_expect_data(self,row):57 col =get_case_expect()58 data =self.oper_excel.get_cell_value(row,col)59 if data == '':60 data =None61 returndata62 ###写入实际结果
63 defwrite_exact_res(self,row,value):64 col =get_case_exact()65 self.oper_excel.update_data(row,col,value)66 ###获取依赖数据key
67 defget_depend_key(self,row):68 col =get_case_dependdata()69 data =self.oper_excel.get_cell_value(row,col)70 if data == '':71 data =None72 returndata73 ###判断是否有数据依赖
74 defis_depend(self,row):75 col =get_case_dependdata()76 data =self.oper_excel.get_cell_value(row, col)77 if data == '':78 data =None79 returndata80 ####判断数据依赖类型:header_depend:header依赖;body_depend:body依赖
81 defget_depeed_type(self,row):82 col =get_case_depend_type()83 data =self.oper_excel.get_cell_value(row,col)84 if data == '':85 data =None86 returndata87 ###获取数据依赖字段
88 defget_depend_field(self,row):89 col =get_case_dependfileld()90 data =self.oper_excel.get_cell_value(row, col)91 if data == '':92 data =None93 returndata94 ###获取依赖ID
95 defget_depend_id(self,row):96 col =get_case_dependid()97 data =self.oper_excel.get_cell_value(row, col)98 if data == '':99 data =None100 returndata101 ###获取配置文件中的url_ip并且将绝对URL拼接出来
102 defget_url(self,row):103 obj_op_file_handle =op_file_handle()104 url_ip = obj_op_file_handle.read_file_handler("url_ip")105 """
106 协议版本:http/https这儿暂不做处理,后期有需要在进行处理107 """
108 url_path =self.get_urlpath(row)109 url = "http://"+eval(url_ip)+url_path110 returnurl111 ###获取是否清除测试数据
112 defget_clean_testdata(self,row):113 col =get_case_clean_testdata()114 data =self.oper_excel.get_cell_value(row, col)115 if data == 'NO':116 data =None117 elif data == 'YES':118 returndata119 ###获取清除数据sql
120 defget_clean_sql(self,row):121 col =get_case_clean_sql()122 data =self.oper_excel.get_cell_value(row, col)123 if data == '':124 data =None125 returndata126 ###写入清除数据sql执行结果
127 defwrite_sql_excute_res(self,row,value):128 col =get_is_sql_success()129 print(col)130 self.oper_excel.update_data(row,col,value)131 if __name__ == "__main__":132 obj_getdata =getdata()133 #print(obj_getdata.get_case_Lines())
134 ####根据请求id到json文件中拿到请求数据
135 #for i in range(1,obj_getdata.get_case_Lines()):
136 #print(obj_getdata.get_request_datajson(i))
137 #print(obj_getdata.get_depend_key(3))
138 print(obj_getdata.get_request_datajson(3))
get_data.py
2.4.4、cfg.text:配置文件,存放常用的变量,以及可以对特定的变量进行修改;
1 ###服务器主机名
2 url_ip = "****" ###127.0.0.1:8080
3 ###token 获取到token持久化,后续token依赖都到配置文件中取值;
4 ###token刷新或者过期重新持久化
5 Authorization = Bearer2e4c4b2e-93d1-45fd-9201-8d778e4f841c6 ###测试报告邮件登录账号
7 username =**** ##[email protected]
8 ###测试报告邮件登录密码
9 password = ****
10 ###测试报告邮件发送人地址
11 sender =**** ##[email protected]
12 ###测试报告邮件接收人地址
13 recv_list = ["****", "****","****"]
cfg.text
2.5、data_file包:用于存放测试用例及json文件
2.5.1、auto_test_case.xls:存放接口测试用例;
接口测试用例表每个字段备注:
auto_test_case.xls:存放接口测试用例;
id:接口测试用例id;
模块名称:接口所属模块
前提条件:接口依赖的前提条件
接口地址:只放置了接口path,接口的url可以通过读取配置文件获取;
请求方式:http协议的常规请求方式
是否执行:通过读取该字段可以控制用例是否执行;
接口说明:描述该接口是干嘛的;
header:是否需要在发送请求的时候传递headers
依赖类型:依赖的类型:属于headers依赖还是数据body依赖;
依赖id:依赖于哪一条接口测试用例;
依赖数据:对响应报文解析的模式,读取模式对响应报文进行解析,获取依赖的值;
依赖数据所属字段:是哪一个字段需要依赖数据,获取到依赖的值之后对依赖数据进行拼接然后发起请求
请求数据:存放请求body的key,然后根据key值到指定的json文件中读取请求body;
预期结果:存放预期结果的key,然后根据key值到指定的json文件中读取预期结果;
实际结果:存放实际结果,在请求完成后获取到结果然后写入到对应的接口测试用例行中;
清除测试数据:是否需要清除测试数据,
执行sql语句:如果需要清除数据,然后调用数据库操作即可
执行结果:将sql语句的执行结果吸入到改表格内
备注
2.5.2、header.json:主要用于存在常规通过的headers
2.5.3、usermanagement.json:用于存放请求body,json格式,可以根据不同的模块创建不同的json文件;
三、项目部署
1、python3环境的部署
python3在Linux系统上的在线部署网上到处有,今天主要记录一下离线安装;
1.1、离线安装python解释器
a、python官网上:https://www.python.org/downloads/source/ 上下载tar.gz压缩包;
b、通过smtp上传到服务;
c、解压;
d、编译:
1 cd Python-3.7.2 #进入python3安装包目录
2 ./configure --prefix=/usr/local/python3 #将python3安装在这个目录
3 make && make install #编译和安装
e、创建软连接
1 ln -s /usr/local/python3/bin/python3 /usr/bin/python3 #创建python3软连接
2 ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3 #创建pip3的软连接
备注:为何要创建这样的软连接:
Linux系统中编译安装python3创建软连接其实和windows上添加环境python3的环境变量是一样作用的,Linux在/usr/bin/ 目录下创建软连接就类似于将python3添加到环境变量中;
不信,你可以使用echo打印一下当前环境变量:echo $PATH,当前用户的环境变量如下;
1.2、离线安装python3插件
a、运行python程序,找到报错原因,确定报错原因是否是缺少插件造成的;
b、复制报错中的插件,进入官网:https://www.python.org/ 搜索对应的插件;或者搜索引擎中直接输入python3+包名;
c、下载成功上传到服务器上,下载下来的插件有几种格式:tar.gz/zip/whl
d、安装对应的插件,这是我离线下载的python插件,报错提示缺什么就对应下载什么插件;
对于不同格式的插件安装方式有所不同;
d.1、tar.gz、zip包直接解压,进入目录,对于这类插件,目录里都有setup.py文件,直接python3 setup.py install即可安装成功,直接将插件安装到虚拟运行环境sites-packages目录下;
d.2、whl格式的插件安装,直接 pip3 install 对应的whl文件即可,也是直接将插件安装到虚拟运行环境sites-packages目录下;
到目前为止只遇到这两个方式安装python3的插件;
2、jenkins部署
2.1、jenkins安装
到目前为止接触到两种方式:war包放在tomcat下运行;jar包直接运行,需要依赖java;
2.2、jenkins构建工程;
中间过程中的jenkins插件安装及配置就不做说明了,网上大把资源等着你去搜索;主要详细记录一下jenkins工程构建过程;
环境配置说明:因为公司已经使用堡垒机登录,不允许直接通过终端程序登录远程服务器,所以jenkins,原计划是jenkins在A服务器上,接口测试项目在B服务器上,因此就直接将jenkins服务和接口自动化的项目放在同一服务器上;
本次部署的构建工程非常简单:
a、源码管理,因为代码通过公司自搭建的git管理,配置git参数后,可以直接将配置仓库的指定分支代码拉到jenkins的工作目录下;
b、构建、直接选择执行shell;代码拉到jenkins的工作目录下,没有进行打包解压编译、清除文件、记录日志等相关操作,所以直接在工作目录下直接运行的程序;
就这样完成了初级的jenkins拉取远程仓库的指定分支的代码,然后在服务器上运行的操作;
3、接口自动化测试项目部署
其实接口自动化测试项目在服务器上的部署就是前面python环境的部署已经jenkins构建工程的相关操作,哈哈!