轉載請註明出處:http://www.javashuo.com/article/p-amamsfcw-c.htmlhtml
前言:上篇文章python3+requests+unittest:接口自動化測試(一):http://www.javashuo.com/article/p-yiygkcyw-db.html ,已經介紹了基於unittest框架的實現接口自動化,可是也存在一些問題,好比最明顯的測試數據和業務沒有區分開,接口用例不便於管理等,因此又對此修改完善。接下來主要是介紹該套接口自動化框架的設計到實現,參考代碼的git地址:https://github.com/zhangying123456/python3_interfacepython
1.代碼框架展現git
(1)case:存放測試用例數據的,好比請求類型get/post、請求url、請求header、請求數據等;github
(2)data:獲取excel文件中相應數據的方法封裝,獲取excel中對應表格內的數據,excel的行列數據等:get_data.py;判斷用例之間是否存在依賴關係並獲取依賴數據:dependent_data.py;初始化excel文件:data_config.py;json
(3)dataconfig:存放請求中涉及到的header、data、cookies等數據;服務器
(4)log:存放測試完成以後生成的日誌文件,能夠查看日誌定位問題;cookie
(5)main:腳本執行的主函數run_test.pysession
(6)util:通用方法的封裝,各類不一樣斷言方式common_assert.py;對excel文件的讀寫操做operation_excel.py;從請求返回數據中拿取數據做爲下一個接口的請求header數據operation_header.py;從json文件中拿取想要的數據operation_json.py;將接口自動化過程當中的相關日誌輸出到log.txt中print_log.py;根據請求類型的不一樣執行對應的get/post方法runmethod.py;將測試結果以郵件形式發送給相關人員send_mail.py。app
2.代碼實現說明框架
(1)首先看下用例數據
說明:該用例只是用來覆蓋一些接口場景而測試使用的,有興趣的能夠參考源碼用本身項目的真實數據來實現
先判斷是否執行:若是yes,執行該條用例;若是no,直接跳過該條用例。
執行用例:獲取用例的url、請求類型、請求頭header、請求數據,request.get/post執行該條接口用例。
在執行用例過程當中,會存在特殊狀況:(1)好比test_04依賴於test_03,test_04中的請求字段supplier的參數數據來源於test_03的response中value[0].biz字段的數據,因此在執行接口過程當中須要判斷是否存在依賴關係;(2)好比test_06請求數據須要test_05的response中的cookies數據,因此這種類型接口也要特殊處理。
執行完成後:寫入實際結果,與預期結果作對比,進行斷言。
(2)看了用例excel後,對基本的流程有個大概瞭解,如今的問題就是如何拿取對應的數據執行接口獲得運行結果
if is_run: url = self.data.get_request_url(i) method = self.data.get_request_method(i) #獲取請求參數 data = self.data.get_data_value(i) # 獲取excel文件中header關鍵字 header_key = self.data.get_request_header(i) # 獲取json文件中header_key對應的頭文件數據 header = self.data.get_header_value(i) expect = self.data.get_expect_data(i) depend_case = self.data.is_depend(i)
舉例說明1:請求url數據是存放在excel中,咱們經過操做excel文件到特定單元格拿到url數據
#獲取url def get_request_url(self,row): col = int(data_config.get_url()) url = self.oper_excel.get_cell_value(row,col) return url
舉例說明2:請求頭header或者請求數據中有的數據爲空,因此咱們在拿取數據過程當中要作判斷
#獲取請求數據 def get_request_data(self,row): col = int(data_config.get_data()) data = self.oper_excel.get_cell_value(row,col) if data == '': return None return data
首先拿取excel中表格中的關鍵字,再經過關鍵字去對應json文件拿取具體的請求數據。好比先拿取excel中請求數據中的hotwords,再根據此關鍵字去json文件讀取hotwords的鍵值數據
"hotwords": { "bizName": "globalSearchClient", "sign": "8c8bc3ee9d6c4b7b8a390ae298cb6db5", "timeMills": "1524906299999" }
#經過獲取請求關鍵字拿到data數據 def get_data_value(self,row): oper_json = OperationJson('../dataconfig/request_data.json') request_data = oper_json.get_data(self.get_request_data(row)) return request_data
#根據關鍵字獲取數據 ''' dict['key']只能獲取存在的值,若是不存在則觸發KeyError dict.get(key, default=None),返回指定鍵的值,若是值不在字典中返回默認值None excel文件中請求數據有可能爲空,因此用get方法獲取 ''' def get_data(self,key): # return self.data[key] return self.data.get(key)
(3)通常的接口都是單接口,便是單獨請求,沒有上下依賴關係的,針對這種只要模擬請求拿到數據進行斷言就能夠了。可是實際項目中會存在特殊場景,好比test_03和test04
說明:test_04中,請求數據qqmusic_more中的supplier字段依賴於test_03中的返回數據value[0].biz的值
"qqmusic_more": { "bizName": "globalSearchClient", "appLan": "zh_CN", "musicLimit": "20", "imei": "864044030085594", "keyword": "fly", "timeMills": "1527134461256", "page": "0", "sign": "17daa7e3e84bd4dfbe9a1bd9a1bd7e62", "mac": "90f05205d7b7", "sessionId": "43e605b914874cd99b47ac997e19c1a1", "network": "1", "supplier": "", "language": "zh_CN", }
先執行test_03,獲取依賴的返回數據value[0].biz的值
#執行依賴測試,獲取test_03返回結果 def run_dependent(self): row_num = self.oper_excel.get_row_num(self.case_id) request_data = self.data.get_data_value(row_num) header = self.data.get_request_header(row_num) method = self.data.get_request_method(row_num) url = self.data.get_request_url(row_num) res = self.method.run_main(method,url,request_data,header,params=request_data) return res #獲取依賴字段的響應數據:經過執行依賴測試case來獲取響應數據,響應中某個字段數據做爲依賴key的value def get_value_for_key(self,row): #獲取依賴的返回數據key depend_data = self.data.get_depend_key(row) print(depend_data) #depend_data打印數據:value[0].biz #執行依賴case返回結果 response_data = self.run_dependent() # print(depend_data) # print(response_data) return [match.value for match in parse(depend_data).find(response_data)][0]
再將value[0].biz值放入test_04請求數據qqmusic_more中的supplier字段中
if depend_case != None: self.depend_data = DependentData(depend_case) #獲取依賴字段的響應數據 depend_response_data = self.depend_data.get_value_for_key(i) #獲取請求依賴的key depend_key = self.data.get_depend_field(i) #將依賴case的響應返回中某個字段的value賦值給該接口請求中某個參數 data[depend_key] = depend_response_data
(4)拿到請求相關數據後,執行該條case,獲取response;而後實際結果與預期結果進行斷言
res = self.run_method.run_main(method,url,data,header,params=data) ''' get請求參數是params:request.get(url='',params={}),post請求數據是data:request.post(url='',data={}) excel文件中沒有區分直接用請求數據表示,則data = self.data.get_data_value(i)拿到的數據,post請求就是data=data,get請就是params=data '''
根據get、post類型區分
class RunMethod: def post_main(self,url,data,header=None): res = None if header != None: res = requests.post(url=url,data=data,headers=header) else: res = requests.post(url=url,data=data) return res.json() def get_main(self,url,params=None,header=None): res = None if header != None: res = requests.get(url=url, params=params, headers=header) else: res = requests.get(url=url, params=params) return res.json() def run_main(self,method,url,data=None,header=None,params=None): res = None if method == 'post': res = self.post_main(url,data,header) else: res = self.get_main(url,params,header) return res
(5)執行接口case過程當中,可能存在某條case異常報錯,致使下面的case沒法運行,因此咱們既要將異常日誌存放在特定文件中方便後續排查,也要保證下面的case可以不受影響繼續執行完
try:... except Exception as e: # 將報錯寫入指定路徑的日誌文件裏 with open(log_file,'a',encoding='utf-8') as f: f.write("\n第%s條用例報錯:\n" % i) initLogging(log_file,e) fail_count.append(i)
抓取日誌的方法可使用python內置模塊logging,具體用法能夠參考:http://www.javashuo.com/article/p-khatqxur-dn.html
import logging def initLogging(logFilename,e): logging.basicConfig( level = logging.INFO, format ='%(asctime)s-%(levelname)s-%(message)s', datefmt = '%y-%m-%d %H:%M', filename = logFilename, filemode = 'a') fh = logging.FileHandler(logFilename,encoding='utf-8') logging.getLogger().addHandler(fh) log = logging.exception(e) return log
日誌文件log.txt結果:直接定位問題出在哪兒
第5條用例報錯: 18-06-19 10:27-ERROR-string indices must be integers Traceback (most recent call last): File "C:/Users/xxx/Documents/GitHub/python3_interface/main/run_test.py", line 70, in go_on_run op_header.write_cookie() File "C:\Users\xxx\Documents\GitHub\python3_interface\util\operation_header.py", line 30, in write_cookie cookie = requests.utils.dict_from_cookiejar(self.get_cookie()) File "C:\Users\zhangying1\Documents\GitHub\python3_interface\util\operation_header.py", line 25, in get_cookie url = self.get_response_url()+"&callback=jQuery21008240514814031887_1508666806688&_=1508666806689" File "C:\Users\xxx\Documents\GitHub\python3_interface\util\operation_header.py", line 18, in get_response_url url = self.response['data']['url'][0] TypeError: string indices must be integers
(6)接口自動化測試執行完成後,須要將測試結果發送給項目組相關人員,郵件發送實現方法參考:http://www.javashuo.com/article/p-rmamgiew-ec.html
self.send_mail.send_main(pass_count,fail_count,no_run_count)
#coding:utf-8 import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart import datetime class SendEmail: global send_user global email_host global password password = "lunkbrgwqxhfjgxx" email_host = "smtp.qq.com" send_user = "xxx@qq.com" def send_mail(self,user_list,sub,content): user = "shape" + "<" + send_user + ">" # 建立一個帶附件的實例 message = MIMEMultipart() message['Subject'] = sub message['From'] = user message['To'] = ";".join(user_list) # 郵件正文內容 message.attach(MIMEText(content, 'plain', 'utf-8')) # 構造附件(附件爲txt格式的文本) filename = '../log/log.txt' time = datetime.date.today() att = MIMEText(open(filename, 'rb').read(), 'base64', 'utf-8') att["Content-Type"] = 'application/octet-stream' att["Content-Disposition"] = 'attachment; filename="%s_Log.txt"'% time message.attach(att) server = smtplib.SMTP_SSL() server.connect(email_host,465)# 啓用SSL發信, 端口通常是465 # server.set_debuglevel(1)# 打印出和SMTP服務器交互的全部信息 server.login(send_user,password) server.sendmail(user,user_list,message.as_string()) server.close() def send_main(self,pass_list,fail_list,no_run_list): pass_num = len(pass_list) fail_num = len(fail_list) #未執行的用例 no_run_num = len(no_run_list) count_num = pass_num + fail_num + no_run_num #成功率、失敗率 ''' 用%對字符串進行格式化 %d 格式化整數 %f 格式化小數;想保留兩位小數,須要在f前面加上條件:%.2f;用%%來表示一個% 若是你不太肯定應該用什麼,%s永遠起做用,它會把任何數據類型轉換爲字符串 ''' pass_result = "%.2f%%" % (pass_num/count_num*100) fail_result = "%.2f%%" % (fail_num/count_num*100) no_run_result = "%.2f%%" % (no_run_num/count_num*100) user_list = ['xxx@qq.com'] sub = "接口自動化測試報告" content = "接口自動化測試結果:\n經過個數%s個,失敗個數%s個,未執行個數%s個:經過率爲%s,失敗率爲%s,未執行率爲%s\n日誌見附件" % (pass_num,fail_num,no_run_num,pass_result,fail_result,no_run_result) self.send_mail(user_list,sub,content)
到此,就基本完成。
說明:
1.只是大概整理了接口自動化實現的設計流程,須要源碼參考的可查看:https://github.com/zhangying123456/python3_interface
2.這套接口框架中還有不少須要完善的地方,好比斷言方法不夠豐富,好比測試報告展現須要完善,等等。各位有興趣的能夠不斷完善改進