python+requests 實現介面自動化 [更新 提交原始碼至 GitHub]
python+requests實現介面自動化
1. 前言
今年2月調去支援專案介面測試,測試過程中使用過postman、jmeter工具,基本能滿足使用,但是部分情況下使用較為麻煩。
比如:部分欄位存在唯一性校驗或欄位間有業務性校驗,每次請求均需手工修改部分報文內容,使用工具難以滿足實際使用。
因此,萌生了使用python去實現介面自動化的想法。之前未接觸過介面測試,但有一點程式設計基礎,經過2個多月的磕磕碰碰,不斷完善,經歷2次重構之後,最後基本達成了目標。
2. 技術棧
- python語言
- requests庫
- unittest單元測試框架
- HTMLTestReportCN、BeautifulReport測試報告
3. 實現的功能概述
- 支援post、get等請求型別,xml、json格式的報文
- 支援使用excel編寫測試用例,測試用例支援涉及多介面的場景用例;支援按指令碼的形式編寫測試用例
- 支援測試結果儲存至資料庫,支援生成html報告,支援將生成的測試結果匯出到excel檔案
- 支援郵件傳送測試結果
- 支援多執行緒併發執行測試用例
4. 框架及專案結構
APIS_AutoTest
api: 主程式目錄
comm:公共函式,包括:介面請求基類、請求及相應資料操作基類等
intf_handle:介面操作層,包含:介面初始化、斷言等
business:業務實現部分
utils:工具類,包括:讀取檔案、傳送郵件、excel操作、資料庫操作、日期時間格式化等
config:配置檔案目錄,包含yaml配置檔案、以及路徑配置
data:測試資料目錄,用於存放測試資料
temp:臨時檔案目錄,用於存放臨時檔案
result:結果目錄
report:測試報告目錄,用於存放生成的html報告
details:測試結果詳情目錄,用於存放生成的測試用例執行結果excel檔案
log:日誌檔案目錄
test:測試用例、測試集相關目錄,啟動test_suite執行用例檔案存放在此
test_case:測試用例存放路徑
test_suite:測試模組集,按模組組裝用例
5. 測試用例執行流程
以指令碼形式編寫的測試用例執行流程圖:
以excel形式編寫的測試用例執行流程圖:
6. 核心方法設計
-
介面請求基類
RequestBase(request_type, url, header, body, data_type=None)
功能描述:
根據傳入的請求型別,請求地址,請求頭,請求體,傳送介面請求,獲得響應頭,響應體
- request_type: 請求型別,只能是'GET', 'POST', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE', 'CONNECT'中的一個,無大小寫要求
- url: 請求地址,完整的介面地址
- head: 請求頭
- body:請求體,xml格式請求體為字串;json格式請求體需傳入json格式
- data_type: 資料型別,未使用欄位,備用。可為:xml、json
支援方法
__send_request()
傳送請求,類私有方法,初始化時呼叫,呼叫方法返回:響應物件
get_respond()
獲取響應物件,呼叫方法返回:響應物件
get_respond_head()
獲取響應頭,呼叫方法返回:響應頭
get_respond_body()
獲取響應體,呼叫方法返回:響應體
-
請求或響應資料操作基類
RequestRespondHandle(data_type, body, fields, value_dict=None)
功能描述:
根據傳入的xml、json格式請求體或響應體,讀取欄位的值或更新欄位的值
引數描述:
- data_type: 資料型別,當前僅支援xml、json,不區分大小寫
- body: 請求體或響應體,json格式請求體支援傳入字典、json格式資料(自動轉換為字典)
- fields: 欄位名稱,支援多欄位傳入,支援資料型別:字串、元組、列表。多欄位傳入形式,字串:多個欄位名稱之間逗號隔開,如:'a1,b2';元組、列表正常傳入即可。json格式:需寫入完整節點路徑,如:body.base.name,對應list型別的需傳入索引位置,如:body.baselist[0].name
- value_dict: 欄位值字典,以欄位名稱及欄位值鍵值對的方式儲存資料,讀取欄位值時,一般不需傳入,也支援傳入(用於讀取excel形式流程前後傳值);更新欄位值時,需傳入,且字典中的key值需要與fields中的欄位名對應。
支援方法:
get_fields_value()
獲取欄位值,呼叫方法即可獲取到xml或json格式的請求體或響應體對應的欄位值,並返回:欄位值字典
update_fields_value()
更新欄位值,呼叫方法即可更新請求體中對應的欄位值,返回更新後的請求體。
Json格式請求體返回格式為字典。
不足與改進:
xml格式,讀取或更新欄位時,若存在多個相同名稱欄位,預設只選第一個;
json格式,讀取巢狀列表的時候,未支援按列表讀取,當前需精確位置單個讀取或更新;
-
請求體初始化-介面對映類
RequestMsgInitMapper(data_type, intf_code, request_body, **kwargs)
功能描述:
根據傳入的介面編號,對映到對應的介面請求報文的初始化方法,進行介面初始化
引數描述:
- data_type: 資料型別,執行xml、json,不區分大小寫
- intf_code: 介面編號,每個介面都要一個介面編號,根據介面編號可以唯一確定一個介面
- request_body: 請求體,json格式請求體支援傳入字典、json格式資料
- **kwargs: 可變關鍵字引數,每個介面初始化時,引數數量均不一致,所有需要使用可變引數,需要以a=value這種方式傳入。部分欄位(如id)可在方法內設定生成方法,一般可變引數設定都是用於前後介面欄位傳值。
支援方法:
start_Intf_init_mapper()
啟動 介面初始化對映,該方法通過判斷傳入資料型別、介面編號,執行對應的介面初始化方法。
呼叫方法後,進行對應介面的初始化,並返回初始化後的請求體。
Json格式的資料,返回json格式的請求體(不管傳入的是字典格式、還是json格式)。
適用場景說明:
介面對映類主要是針對不同介面初始化欄位涉及複雜業務判斷,需按介面單獨編寫的場景。
如果一個介面初始化涉及的欄位均不涉及業務相關的複雜判斷處理,可以直接統一使用通用介面初始化方法進行初始化。
通用介面初始化方法
intf_base_init(data_type, request_body, **kwargs)
功能描述:
根據可變關鍵字引數傳入的鍵值對,進行介面報文初始化,返回初始化後的請求體
引數描述:
- data_type: 資料型別,執行xml、json,不區分大小寫
- request_body: 請求體,json格式請求體支援傳入字典、json格式資料
- **kwargs:可變關鍵字引數,每個介面初始化時,引數數量均不一致,所有需要進行初始化的欄位,均需以鍵值對的方式傳入。
適用場景說明:
介面初始化欄位不涉及複雜邏輯判斷,直接傳值後更新即可;該方式也適用於介面請求頭的初始化。
7. 資料操作類
資料操作主要是資料讀取、寫入,主要分為如下幾類:
- 讀取txt、json等檔案內容(整個讀取)、寫入內容到檔案
- 讀取yaml檔案內容
- 讀取excel表格內容、匯出excel表格、讀取excel並作為模板
- 運算元據庫表中的資料(增刪改查)
讀取txt/json檔案內容,寫入內容到檔案
FileHandle(file_name, file_path)
功能描述:
讀取檔案所有內容
引數描述:
- file_name:讀取/寫入檔名稱,包含字尾,支援txt,json等
- file_path:讀取/寫入檔案所在的目錄
支援方法:
read_file_content()
讀取檔案內容,並以字串返回
write_to_file(content)
將內容寫入檔案
讀取yaml檔案內容
ReadYaml(file_path)
功能描述:
讀取yaml檔案內,支援按名稱讀取
引數描述:
- file_path:讀取檔案的完整路徑
支援方法:
get_yaml()
讀取yaml檔案所有內容,並以字典格式返回
get_value(level_name)
讀取yaml檔案欄位的值,並返回
level_name: 節點欄位名稱,如涉及多節點需傳入對應路徑,如:db.host
讀取excel表格內容、匯出excel表格、讀取excel並作為模板
ExcelHandle()
功能描述:
讀取excel表格內容、將資料匯出到excle表格中
支援方法:
read_excel_data(excel_path, sheet_name=None)
讀取excel表格內容,並以列表巢狀列表的方式返回
excel_path: 讀取excel檔案的完整路徑
sheet_name: 讀取excel的頁簽名稱,預設讀取第一個頁籤
export_to_excel(data, head, file_name, time_flag=None)
將資料匯出到excle表格中
data: 需要匯出到excel的內容,元組巢狀元組(對應資料庫中查詢返回的結果)
head: excel表頭,列表、元組巢狀元組(資料庫中查詢表頭、描述,支援多表頭)
file_name: excel檔名稱,包含字尾.xlsx,excel匯出路徑系統預設
time_flag: 時間戳標記,可傳入用例執行的報告號,使其對應
copy_excel_template(template_path, sheet_name=None)
複製excel表格模板,返回workbook, workssheet
template_path: 模板檔案完整路徑
sheet_name: 模板檔案頁簽名稱,預設第一個頁籤
運算元據庫資料
功能描述:
- 表資料讀取:根據情況讀取所需資料,返回資料格式元組巢狀元組。
- 插入/更新表資料:根據實際內容,寫入資料到對應的表中
備註:
資料庫部分封裝為資料庫操作類
8. 部分關鍵程式碼
介面請求基類
# create by: wyun
# create at: 2020/4/19 9:30
import json
import requests
class RequestBase:
def __init__(self, request_type, request_url, request_body, request_headers, intf_type=None):
"""
請求介面公共類
:param request_type: 請求型別:post, get
:param request_url: 請求url地址
:param request_headers: 請求頭
:param request_body: 請求體(xml型別傳入字串格式,json型別資料必須傳入json格式,不能傳入字典)
:param intf_type: 介面型別:webservice, api,分別對應介面資料格式:xml, json
"""
self.request_type = request_type
self.request_url = request_url
self.request_body = request_body
self.request_headers = request_headers
self.intf_type = intf_type
self.res = self.__send_request()
# 傳送請求
def __send_request(self):
if not isinstance(self.request_type, str):
print('請求型別格式錯誤。')
return None
if self.request_type.upper() not in ['GET', 'POST', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE', 'CONNECT']:
print('請求型別不存在。')
return None
return requests.request(method=self.request_type, url=self.request_url, data=self.request_body.encode('utf-8'),
headers=self.request_headers)
# 獲取響應
def get_respond(self):
return self.res
# 獲取響應頭
def get_respond_head(self):
return self.res.headers
# 獲取響應體
def get_respond_body(self):
if self.res.content:
return self.res.text
請求或響應資料操作基類
# create by: wyun
# create at: 2020/4/19 12:58
import json
import re
"""
對請求體或響應體進行處理:
1. 支援讀取請求體或響應體中欄位的值
2. 支援更新請求體中欄位的值
"""
# 遞迴呼叫,更新json中的欄位值
def update_json_step(node, json_str, value, i=0):
# 當前節點索引(負向)
node_index = -len(node) + i
# 如果包含[n]形式,說明節點為列表,需處理
if '[' in node[node_index] and ']' in node[node_index]:
list_node, list_index = node[node_index].split('[')
index = list_index.split(']')[0]
if index is None or index == '':
print('引數傳入錯誤,請指定列表[%s]索引' % list_node)
else:
index = int(index)
return update_json_step(node, json_str[list_node][index], value, i + 1)
# 判斷如果當前節點為最後一個節點,則更新value值
if node_index == -1:
json_str[node[-1]] = value
return json_str
return update_json_step(node, json_str[node[node_index]], value, i + 1)
class DataHandle:
def __init__(self, data_type, data_msg, fields, value_dict=None):
"""
處理請求體或響應體資料,讀取或更新欄位值
:param data_type: 資料型別:xml,json
:param data_msg: 請求體或響應體
:param fields: 欄位值,支援多字讀方式,欄位間逗號隔開;或傳入列表、元組
json格式:需寫入完整節點路徑,如:body.base.name,對應list型別的需傳入索引位置,如:body.baselist[0].name
:param value_dict: 以字典鍵值對儲存欄位值
"""
self.data_type = data_type
self.data = data_msg
# fields支援str,list方式,str自動轉換為list
if isinstance(fields, str):
self.fields = fields
self.fields_list = []
if ',' in fields:
self.fields_list = fields.strip().split(',')
else:
self.fields_list.append(fields.strip())
elif isinstance(fields, list):
self.fields_list = fields
else:
self.fields_list = list(fields)
# 初始化字典值
if value_dict is None:
self.value_dict = dict()
else:
self.value_dict = value_dict
def get_fields_value(self):
"""
獲取欄位值
1. 支援獲取多個欄位值,輸入字串或列表, 元組
"""
step_dict = self.value_dict
# 欄位列表迴圈獲取
for p in self.fields_list:
if p == '':
continue
# 處理xml格式報文
if self.data_type.upper() == 'XML':
pattern = '<' + p + '>.*</' + p + '>'
search_result = re.search(pattern, self.data)
if search_result is not None:
# 包括標籤和值都匹配上
field_and_value = search_result.group()
# 去除標籤獲取欄位值,並存入字典
step_dict[p] = (field_and_value.split('</')[0]).split('>')[-1]
else:
# print('引數[%s]提取失敗,無匹配值。' % p)
pass
# 處理json格式報文
elif self.data_type.upper() == 'JSON':
# 獲取層級(使用.隔開)
node = p.split('.')
# json轉換字典
if isinstance(self.data, dict):
temp = self.data
else:
temp = json.loads(self.data)
# 逐層讀取資料
for per_node in node:
if '[' in per_node and ']' in per_node:
per_node, index_str = per_node.split('[')
index = int(index_str.split(']')[0])
temp = temp.get(per_node)[index]
if temp is None:
break
else:
temp = temp.get(per_node)
if temp is None:
break
# 講讀取的結果寫入字典
step_dict[p] = temp
# 非 xml,json的資料格式,報錯退出
else:
print('ERROR: 不支援此資料型別[%s]' % self.data_type)
break
return step_dict
def update_fields_value(self):
"""
更新欄位值
支援更新單個欄位值,多個欄位值(欄位名 字串或列表、元組,欄位值 字典)
"""
req_body = self.data
# 欄位列表迴圈更新
for p in self.fields_list:
# 入參型別判斷
if isinstance(self.value_dict, dict):
# 引數在字典中不存在,則跳過
if p not in self.value_dict.keys():
print('ERROR: 字典中不存在引數[%s]。' % p)
continue
# 獲取字典中引數的值
value = self.value_dict[p]
elif isinstance(self.value_dict, str):
value = self.value_dict
else:
print('函式入參[%s]型別不支援,請傳入字串或字典。' % self.value_dict)
break
# 更新資料型別為 xml 的引數值
if self.data_type.upper() == 'XML':
# 優先尋找是否存在指定更新引數,若有,則按指定引數更新
if '${' + p + '}' in req_body:
req_body = req_body.replace('${' + p + '}', value)
# 其次尋找標籤引數值,若有,則按標籤更新值
else:
# 檢查標籤是否存在,若存在
if '<' + p + '>' in req_body and '</' + p + '>' in req_body:
# 正則匹配
pattern = '<' + p + '>.*</' + p + '>'
new_field_and_value = '<' + p + '>' + str(value) + '</' + p + '>'
search_result = re.search(pattern, req_body)
# 若正則匹配結果存在,則進行欄位值更新
if search_result is not None:
old_field_and_value = search_result.group()
req_body = req_body.replace(old_field_and_value, new_field_and_value)
else:
print('引數[%s]匹配標籤失敗,請檢查報文中標籤格式。' % p)
else:
# print('引數[%s]匹配標籤失敗,無此標籤。' % p)
continue
# 更新資料型別為 xml 的引數值
elif self.data_type.upper() == 'JSON':
# json轉換字典
if isinstance(self.data, dict):
req_body = self.data
else:
req_body = json.loads(self.data)
# 獲取層級
node = p.split('.')
# 呼叫遞迴函式更新欄位值
update_json_step(node, req_body, value)
# 非 xml,json的資料格式,報錯退出
else:
print('ERROR: 不支援此資料型別[%s]' % self.data_type)
break
return req_body
介面初始化通用類
# create by: wyun
# create at: 2020/4/23 22:42
from api.comm.data_handle import DataHandle
def intf_base_init(data_type, request_body, **kwargs):
"""
介面初始化通用類
:param data_type: 資料型別,如:xml,json
:param request_body: 請求體
:param kwargs: 可變關鍵字引數
:return: 請求體
"""
return DataHandle(data_type, request_body, kwargs.keys(), kwargs).update_fields_value()
相關文章
- 基於Python+requests搭建的自動化框架-實現流程化的介面串聯Python框架
- 介面自動化Python+requests踩坑記錄Python
- Spring Boot + JPA實現MySQL批量更新原始碼 - githubSpring BootMySql原始碼Github
- 利用Github Actions實現自動化部署Github
- GO 使用Webhook 實現github 自動化部署GoWebHookGithub
- jenkins + GitHub 實現專案自動化部署JenkinsGithub
- 基於RestAssured實現介面自動化REST
- 利用github提供的Webhooks實現自動化部署GithubWebHook
- gitee 和 GitHub 的 webhook 的使用,實現伺服器程式碼的自動更新。GiteeGithubWebHook伺服器
- postman實現介面的自動化測試Postman
- 使用GitHub的Webhooks實現程式碼的自動部署GithubWebHook
- 《Flink SQL任務自動生成與提交》後續:修改flink原始碼實現kafka connector BatchModeSQL原始碼KafkaBAT
- 利用 ACME 實現SSL證書自動化配置更新ACM
- 各位有開源介面自動化專案原始碼嗎(java)原始碼Java
- 試著使用 jmeter 實現介面自動化測試JMeter
- 介面未全部提測,怎麼實現介面指令碼之間的自動化關聯?指令碼
- JMeter 介面自動化測試(手工轉自動化指令碼)JMeter指令碼
- 基於 Springboot+layui 實現介面自動化平臺Spring BootUI
- Jmeter 介面自動化連載 (15) - beanshell 實現字串加密JMeterBean字串加密
- 介面自動化之實現日誌記錄封裝封裝
- 提交程式碼到githubGithub
- Selenium自動化實現web自動化-1Web
- 前端之路: 用github的webhooks實現專案自動化構建前端GithubWebHook
- 【Hexo】使用Hexo+github pages+travis ci 實現自動化部署HexoGithub
- Azure Terraform(九)GitHub Actions 實現 Infra 資源的自動化部署ORMGithub
- Vuepress + GitHub Actions 實現部落格自動部署!VueGithub
- Jenkins + Gitee 實現程式碼自動化構建JenkinsGitee
- 自動化驗證碼登入如何實現?
- sqlalchemy實現時間列自動更新SQL
- 介面自動化實戰之框架搭建框架
- 婚戀app原始碼開發,如何實現介面效能優化?APP原始碼優化
- 基於 Pytest+Requests+Allure 實現介面自動化測試
- 基於Python+Requests+Pytest+YAML+Allure實現介面自動化PythonYAML
- github提交程式碼步驟Github
- 向Github上提交程式碼Github
- Httpclient 介面自動化HTTPclient
- python 介面自動化Python
- 全自動化介面