背景:數據庫
某項目有多個接口,以前使用的unittest框架來管理測試用例,將每一個接口的用例封裝成一個py文件,接口有數據或者字段變更後,須要去每一個py文件中找出變更的接口測試用例,維護起來不方便,爲了便於接口變更後維護,使用excel來管理測試用例,接口有變更不須要修改代碼,只須要維護excel便可。json
思路:api
爲了方便維護測試用例,一個接口的測試用例使用一個excel文件來管理,每一個excel文件中有兩個sheet頁,第一個sheet頁是接口的基本信息,包括接口名稱,地址和請求方式,第二個sheet頁爲接口的測試用例,以下圖所示cookie
第一個sheet頁session
第二個sheet頁app
接口請求的數據類型爲X-WWW-FORM-URLENCODED,在測試用例中每一個字段爲一列,每條用例爲一行,倒數第二列爲預期結果,倒數第三列爲該條用例的描述。框架
接口自動化框架結構:post
common目錄存放公共的方法,例如寫日誌,連數據庫測試
config目錄存放配置文件和讀取配置文件內容的方法,cfg.ini包括髮送郵件的配置信息和接口的ip和端口字體
data目錄存放接口的測試用例
logs目錄存放用例執行的日誌
report目錄存放測試報告
run_main.py爲用例執行的入口
源碼:
api_test.py封裝讀取測試用例的數據,執行測試用例和校驗測試結果
#coding:utf-8 import xlrd,os import requests from datetime import datetime from xlrd import xldate_as_tuple from config import readConfig from common.logger import Log ''' 獲取測試用例data所在的目錄 ''' d = os.path.dirname(__file__) #返回當前文件所在目錄(common文件夾路徑) parent_path = os.path.dirname(d) #返回common的父級目錄 data_path = os.path.join(parent_path,'data') #返回data所在目錄 data_path1 = os.listdir(data_path) #返回data目錄下全部的文件 log = Log() def api_data(): for filename in data_path1: book = xlrd.open_workbook(os.path.join(data_path,filename)) ''' 獲取excel文件中接口信息 ''' table = book.sheet_by_index(0) #經過索引,獲取相應的列表,這裏表示獲取excel的第一個列表 inf_name = table.row_values(1)[0] #返回接口名稱 inf_address = table.row_values(1)[1] #返回接口地址 inf_mode = table.row_values(1)[2] #返回請求方式 ''' 獲取excel文件中測試用例信息 ''' sheet = book.sheet_by_index(1) #經過索引,獲取相應的列表,這裏表示獲取excel的第二個列表 nrows = sheet.nrows #獲取全部行數 filed = sheet.row_values(0) # print(filed) for i in range(1,nrows): d1 = {} for j in range(0,len(filed)-2): ctype = sheet.cell(i, j).ctype # 表格的數據類型 cell = sheet.cell_value(i, j) d = {} if ctype == 2 and cell % 1 == 0: # 若是是整形 cell = int(cell) elif ctype == 3: # 轉成datetime對象 date = datetime(*xldate_as_tuple(cell, 0)) cell = date.strftime('%Y/%m/%d') elif ctype == 4: cell = True if cell == 1 else False # print(cell) d.update({filed[j]:cell}) # print(d) d1.update(d) # print(d1) ''' 獲取excel文件中測試用例預期結果和描述 ''' a = [] for k in range(len(filed)-2,len(filed)): ctype = sheet.cell(i, k).ctype # 表格的數據類型 cell = sheet.cell_value(i, k) if ctype == 2 and cell % 1 == 0: # 若是是整形 cell = int(cell) elif ctype == 3: # 轉成datetime對象 date = datetime(*xldate_as_tuple(cell, 0)) cell = date.strftime('%Y/%m/%d') elif ctype == 4: cell = True if cell == 1 else False a.append(cell) # print(a[0]) # print(type(a[0])) ''' 獲取cfg.ini配置文件中接口公共信息(ip和port) ''' ip = readConfig.ip # 獲取配置文件中接口ip i_port = readConfig.i_port # 獲取配置文件中接口port url = "http://" + ip + ":" + i_port + inf_address headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0", "X-Requested-With": "XMLHttpRequest", "Connection": "keep-alive" } par = d1 ''' 判斷請求方式是GET仍是POST,而且判斷測試用例預期結果與實際響應一致 ''' if inf_mode == 'GET': r = requests.get(url, params=par) result = r.json() # log.info("---編號%s,接口名稱%s---")%(i,inf_name) print(inf_name, str(result).replace('None','null'), a[1]) if str(result).replace('None','null') == a[0]: log.info("pass") log.info("--------") else: log.info("false") log.info("--------") elif inf_mode == 'POST': r = requests.post(url, data=par, headers=headers) result = r.json() print(inf_name, str(result).replace('None', 'null'), a[1]) if str(result).replace('None', 'null') == a[0]: print('pass') print('--------') else: print('false') print('--------') api_data()
配置文件cfg.ini(主要配置郵箱和接口ip+port等經常使用數據信息)
[email] smtp_server = smtp.163.com port = 465 sender = xxx;psw是QQ郵箱的受權碼 psw = xxx ;收件人多個時,中間用逗號隔開,如'a@xx.com,b@xx.com' receiver = xxx[interface] ip = xxx ;接口ip port = xxx ;接口端口
readConfig.py讀取配置文件中數據
# coding:utf-8 import os import configparser cur_path = os.path.dirname(os.path.realpath(__file__)) configPath = os.path.join(cur_path, "cfg.ini") conf = configparser.ConfigParser() conf.read(configPath,encoding='utf-8') smtp_server = conf.get("email", "smtp_server") sender = conf.get("email", "sender") psw = conf.get("email", "psw") receiver = conf.get("email", "receiver") port = conf.get("email", "port") ip = conf.get("interface","ip") i_port = conf.get("interface","port")
優化:
部分接口訪問時,響應未知用戶,須要用session關聯接口,先調用登陸接口,把登陸接口的調用封裝成了一個實例方法,實現了複用,登陸以後,登陸接口的http響應會把session以 cookie的形式set到客戶端,以後的接口都會使用此session去請求
封裝登陸接口user_login.py
#coding:utf-8 import requests from common.logger import Log class Login(): log = Log() def __init__(self,s): self.s = s def login(self,code,passwd): url = "http://192.168.20.100:8081/backend/system/user/login" headers = {"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8", "User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36", "X-Requested-With":"XMLHttpRequest", "Cookie":"JSESSIONID=92D7FB4C7FB917B7D2E8DC429A63443F", "Connection":"keep-alive" } d = {"code":code,"passwd":passwd} res = self.s.post(url,headers=headers,data=d) result1 = res.text #字節輸出 self.log.info(u"調用登陸方法,獲取結果:%s"%result1) return res.json()
優化api_test.py中部分代碼(紅色部分爲優化的代碼)
1.在請求接口前首先調用登陸接口
2.加入執行用例的編號(p),每循環一次自增1
#coding:utf-8 import xlrd,os import requests from datetime import datetime from xlrd import xldate_as_tuple from config import readConfig from common.logger import Log from case.user_login import Login ''' 獲取測試用例data所在的目錄 ''' d = os.path.dirname(__file__) #返回當前文件所在目錄(common文件夾路徑) parent_path = os.path.dirname(d) #返回common的父級目錄 data_path = os.path.join(parent_path,'data') #返回data所在目錄 data_path1 = os.listdir(data_path) #返回data目錄下全部的文件 s = requests.session() lon = Login(s) log = Log() def api_data(): p = 1 for filename in data_path1: book = xlrd.open_workbook(os.path.join(data_path,filename)) ''' 獲取excel文件中接口信息 ''' table = book.sheet_by_index(0) #經過索引,獲取相應的列表,這裏表示獲取excel的第一個列表 inf_name = table.row_values(1)[0] #返回接口名稱 inf_address = table.row_values(1)[1] #返回接口地址 inf_mode = table.row_values(1)[2] #返回請求方式 ''' 獲取excel文件中測試用例信息 ''' sheet = book.sheet_by_index(1) #經過索引,獲取相應的列表,這裏表示獲取excel的第二個列表 nrows = sheet.nrows #獲取全部行數 filed = sheet.row_values(0) # print(filed) for i in range(1,nrows): d1 = {} for j in range(0,len(filed)-2): ctype = sheet.cell(i, j).ctype # 表格的數據類型 cell = sheet.cell_value(i, j) d = {} if ctype == 2 and cell % 1 == 0: # 若是是整形 cell = int(cell) elif ctype == 3: # 轉成datetime對象 date = datetime(*xldate_as_tuple(cell, 0)) cell = date.strftime('%Y/%m/%d') elif ctype == 4: cell = True if cell == 1 else False # print(cell) d.update({filed[j]:cell}) # print(d) d1.update(d) # print(d1) ''' 獲取excel文件中測試用例預期結果和描述 ''' a = [] for k in range(len(filed)-2,len(filed)): ctype = sheet.cell(i, k).ctype # 表格的數據類型 cell = sheet.cell_value(i, k) if ctype == 2 and cell % 1 == 0: # 若是是整形 cell = int(cell) elif ctype == 3: # 轉成datetime對象 date = datetime(*xldate_as_tuple(cell, 0)) cell = date.strftime('%Y/%m/%d') elif ctype == 4: cell = True if cell == 1 else False a.append(cell) # print(a[0]) # print(type(a[0])) ''' 獲取cfg.ini配置文件中接口公共信息(ip和port) ''' ip = readConfig.ip # 獲取配置文件中接口ip i_port = readConfig.i_port # 獲取配置文件中接口port url = "http://" + ip + ":" + i_port + inf_address headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0", "X-Requested-With": "XMLHttpRequest", "Connection": "keep-alive" } par = d1 ''' 判斷請求方式是GET仍是POST,而且判斷測試用例預期結果與實際響應一致,全部接口請求前先調用登陸接口 ''' code = "xxx" #登陸接口用戶code passwd = "xxx" #登陸接口用戶passwd lon.login(code, passwd) if inf_mode == 'GET': r = s.get(url, params=par) result = r.json() log.info("編號:%s,接口名稱:%s,測試點:%s,響應:%s"%(p,inf_name,a[1],str(result).replace('None','null'))) # print(inf_name, str(result).replace('None','null'), a[1]) if str(result).replace('None','null') == a[0]: log.info("pass") log.info("--------") else: log.info("false") log.info("--------") elif inf_mode == 'POST': r = s.post(url, data=par, headers=headers) result = r.json() log.info("編號:%s,接口名稱:%s,測試點:%s,響應:%s" % (p, inf_name,a[1],str(result).replace('None', 'null'))) # print(inf_name, str(result).replace('None', 'null'), a[1]) if str(result).replace('None', 'null') == a[0]: log.info("pass") log.info("--------") else: log.info("false") log.info("--------") p=p+1 api_data()
執行結果:
優化二:
excel中添加結果列,將每條用例的執行結果寫入excel中,由於excel版本是2007以上,採用openpyxl模塊去修改excel單元格的值,執行經過用綠色字體標註pass,執行不經過的用例紅色字體標註false
優化api_test.py中部分代碼
#coding:utf-8 import xlrd,os import requests import openpyxl from openpyxl.styles import Font # from xlutils.copy import copy from datetime import datetime from xlrd import xldate_as_tuple from config import readConfig from common.logger import Log from case.user_login import Login ''' 獲取測試用例data所在的目錄 ''' d = os.path.dirname(__file__) #返回當前文件所在目錄(common文件夾路徑) parent_path = os.path.dirname(d) #返回common的父級目錄 data_path = os.path.join(parent_path,'data') #返回data所在目錄 data_path1 = os.listdir(data_path) #返回data目錄下全部的文件 s = requests.session() lon = Login(s) log = Log() def api_data(): p = 1 for filename in data_path1: book = xlrd.open_workbook(os.path.join(data_path,filename)) ''' 使用xlwt操做excel,xlwt只支持excel2007如下版本 ''' # wb = copy(book) # ws = wb.get_sheet(1) ''' 使用openpyxl操做excel,openpyxl支持excel2007以上版本 ''' wb = openpyxl.load_workbook(os.path.join(data_path,filename)) ws = wb.worksheets[1] font_green = Font(color="37b400") font_red = Font(color="ff0000") ''' 獲取excel文件中接口信息 ''' table = book.sheet_by_index(0) #經過索引,獲取相應的列表,這裏表示獲取excel的第一個列表 inf_name = table.row_values(1)[0] #返回接口名稱 inf_address = table.row_values(1)[1] #返回接口地址 inf_mode = table.row_values(1)[2] #返回請求方式 ''' 獲取excel文件中測試用例信息 ''' sheet = book.sheet_by_index(1) #經過索引,獲取相應的列表,這裏表示獲取excel的第二個列表 nrows = sheet.nrows #獲取全部行數 ncols = sheet.ncols #獲取全部列數 filed = sheet.row_values(0) # print(filed) for i in range(1,nrows): d1 = {} for j in range(0,len(filed)-3): ctype = sheet.cell(i, j).ctype # 表格的數據類型 cell = sheet.cell_value(i, j) d = {} if ctype == 2 and cell % 1 == 0: # 若是是整形 cell = int(cell) elif ctype == 3: # 轉成datetime對象 date = datetime(*xldate_as_tuple(cell, 0)) cell = date.strftime('%Y/%m/%d') elif ctype == 4: cell = True if cell == 1 else False # print(cell) d.update({filed[j]:cell}) # print(d) d1.update(d) # print(d1) ''' 獲取excel文件中測試用例預期結果和描述 ''' a = [] for k in range(len(filed)-3,len(filed)-1): ctype = sheet.cell(i, k).ctype # 表格的數據類型 cell = sheet.cell_value(i, k) if ctype == 2 and cell % 1 == 0: # 若是是整形 cell = int(cell) elif ctype == 3: # 轉成datetime對象 date = datetime(*xldate_as_tuple(cell, 0)) cell = date.strftime('%Y/%m/%d') elif ctype == 4: cell = True if cell == 1 else False a.append(cell) # print(a[0]) # print(type(a[0])) ''' 獲取cfg.ini配置文件中接口公共信息(ip和port) ''' ip = readConfig.ip # 獲取配置文件中接口ip i_port = readConfig.i_port # 獲取配置文件中接口port url = "http://" + ip + ":" + i_port + inf_address headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0", "X-Requested-With": "XMLHttpRequest", "Connection": "keep-alive" } par = d1 ''' 判斷請求方式是GET仍是POST,而且判斷測試用例預期結果與實際響應一致,全部接口請求前先調用登陸接口 ''' code = "xuxingan" passwd = "admin" lon.login(code, passwd) if inf_mode == 'GET': r = s.get(url, params=par) result = r.json() log.info("編號:%s,接口名稱:%s,測試點:%s,響應:%s"%(p,inf_name,a[1],str(result).replace('None','null'))) # print(inf_name, str(result).replace('None','null'), a[1]) if str(result).replace('None','null') == a[0]: # ws.write(i,ncols-1,'pass'.encode('utf-8')) ws.cell(row=i+1,column=ncols,value='pass').font = font_green log.info("pass") log.info("--------") else: # ws.write(i,ncols-1,'false'.encode('utf-8')) ws.cell(row=i+1, column=ncols, value='false').font = font_red log.info("false") log.info("--------") elif inf_mode == 'POST': r = s.post(url, data=par, headers=headers) result = r.json() log.info("編號:%s,接口名稱:%s,測試點:%s,響應:%s" % (p, inf_name,a[1],str(result).replace('None', 'null'))) # print(inf_name, str(result).replace('None', 'null'), a[1]) if str(result).replace('None', 'null') == a[0]: # ws.write(i,ncols-1,'pass'.encode('utf-8')) ws.cell(row=i+1, column=ncols, value='pass').font = font_green log.info("pass") log.info("--------") else: # ws.write(i,ncols-1,'false'.encode('utf-8')) ws.cell(row=i+1, column=ncols, value='false').font = font_red log.info("false") log.info("--------") wb.save(os.path.join(data_path, filename)) p=p+1 log.info("總計%s條用例"%p) api_data()
執行結果
總結:
第一個版本有點粗糙,1.後續加入將每條用例的執行結果寫入測試用例excel文件 2.生成自動化測試報告