使用python+requests+pytest實現(xiàn)接口自動化
主要思路:
- 對 requests 進行二次封裝,做到定制化效果
- 使用 excel 存放接口請求數(shù)據(jù),作為數(shù)據(jù)驅(qū)動
- 里面有一些功能模仿了 jmeter,比如用戶參數(shù)定義、jsonpath 提取
- 用 pytest 進行測試用例管理
一、環(huán)境
- python==3.8.0
- requests==2.31.0
- pytest==7.4
- 還有一些其他第三方庫例如 allure 報告、jsonpath 等
這塊不過多介紹,安裝 python 配置環(huán)境變量,pip install 命令安裝所需插件即可
二、項目目錄結(jié)構(gòu)
1、config用來存放項目配置文件,包括接口基礎(chǔ)配置,數(shù)據(jù)庫信息等,使用的是 configparser 進行讀取
2、Outputs 層存放了日志和測試報告,這里報告用的是 allure
3、resources 存放的是測試數(shù)據(jù) (這里用的 excel 作為接口數(shù)據(jù)驅(qū)動) 以及其他的一些測試文件等
4、Testcases 存放測試類
5、utils:工具類,包含了讀取配置、記錄日志、http 請求、接口數(shù)據(jù)處理等功能
三、重點功能介紹
1、讀取 excel
實現(xiàn)代碼如下:
from openpyxl import load_workbookclass DoExcel: def __init__(self, file_name): # 打開文件 self.filepath = os.path.join(resources,'case_datas', f'{file_name}.xlsx') # 獲取工作表 self.wb = load_workbook(self.filepath) # 獲取當(dāng)前sheet self.ws = self.wb.active def get_data_from_excel(self): '''從excel獲取測試數(shù)據(jù)''' datas = [] for row in range(1, self.ws.max_row + 1): row_data = {} if row > 1: # 將行數(shù)據(jù)轉(zhuǎn)為字典數(shù)據(jù)格式 for column in range(1, self.ws.max_column + 1): row_data[f"{self.ws.cell(1, column).value}"] = self.ws.cell(row, column).value # 將json_path轉(zhuǎn)字典 if row_data["json_path"]: row_data["json_path"] = {k: v for item in row_data["json_path"].split("&") for k, v in[item.split("=")]} datas.append(row_data) self.wb.close() return datas def close(self): # 關(guān)閉文件流 self.wb.close()
數(shù)據(jù)文檔格式:
(這里目前只支持單 sheet 頁讀取,后續(xù)將擴展成 sheet 名稱傳參獲取方式)
2、requests 請求部分
import requestsclass HttpRequest: def __init__(self): self.session = requests.sessions.session() self.global_headers = {"X-Request-Sign": "xxxxxxxxx"} def set_headers(self, path, content_type, headers): # 判斷接口是登錄時,將Authorization請求頭刪除 if path in ["oauth/oauth/userlogin", "/oauth/oauth/login"] and "Authorization" in self.global_headers.keys(): self.global_headers.pop("Authorization") # 判斷content_type為json時,添加請求頭content_type if content_type == "json": self.global_headers["Content-Type"] = "application/json;charset=UTF-8" # 判斷content_type不為json時,刪除請求頭content_type elif content_type != "json" and "Content-Type" in self.global_headers.keys(): self.global_headers.pop("Content-Type") # 將接口信息中的請求頭更新到global_headers中 if headers: self.global_headers.update(headers) def set_token(self, path, res_code, res_json): # 請求接口為登錄并且返回成功時,將token附加到headers里 if path == "/oauth/oauth/login" and res_code == 200: token = json_path(res_json, "$.data.access_token") self.global_headers["Authorization"] = f"Bearer {token}" def clear_headers(self, headers): # 清除無用的請求頭 if headers: for i in headers.keys(): if i in self.global_headers.keys(): self.global_headers.pop(i) def http_request(self, datas, file=None): log_info.log_info('--------------------') log_info.log_info('↓↓↓↓↓接口請求開始↓↓↓↓↓') # 拼接url base從配置文件獲取+參數(shù)傳遞接口路徑 urls = config.get_strValue('api', 'base_url') + datas["path"] self.set_headers(path=datas["path"], content_type=datas["content_type"], headers=datas["headers"]) log_info.log_info(f'接口名稱:{datas["desc"]}') log_info.log_info(f"請求地址:{urls}") log_info.log_info(f"請求頭:{self.global_headers}") log_info.log_info(f'請求參數(shù):{datas["params"]}') print((f"請求地址:{urls}")) res = None # 請求封裝,這里用的session會話保持,https請求需要將verify設(shè)置為False try: if datas["method"].lower() == 'get': res = self.session.request(datas["method"], urls, params=datas["params"], headers=self.global_headers, verify=False) elif datas["method"].lower() == 'post': if datas["content_type"] in ["form", "upload"]: res = self.session.request(datas["method"], urls, data=datas["params"], headers=self.global_headers, files=file,verify=False) elif datas["content_type"] == "json": res = self.session.request(datas["method"], urls, json=datas["params"], headers=self.global_headers, verify=False) log_info.log_info(f"響應(yīng)信息:{res.text}") except Exception as e: log_info.log_info(e) raise e self.set_token(path=datas["path"], res_code=res.status_code, res_json=res.json()) self.clear_headers(headers=datas["headers"]) log_info.log_info('↑↑↑↑↑接口請求結(jié)束↑↑↑↑↑') log_info.log_info('--------------------') return res def close(self): self.session.close()
在這里我根據(jù)項目情況,進行了請求頭的特殊處理;
- 實例化對象的時候添加了全局請求頭
- 根據(jù)不同類型的請求和傳參格式對 content-type 做了相應(yīng)處理
- token 的處理
- 最后請求后把當(dāng)前無用請求頭部分做了清理
3、接口數(shù)據(jù)處理
class DataHandle: def datas_init(self,datas,params_list): self.datas = datas self.params_list = params_list def headers_handle(self): # 將請求頭進行參數(shù)化 if self.datas["headers"]: self.datas["headers"] = re_replace(self.datas["headers"], self.params_list) # 請求頭轉(zhuǎn)字典格式 if self.datas["headers"]: self.datas["headers"] = {k: v for item in self.datas["headers"].split("&") for k, v in [item.split("=", 1)]} return self.datas["headers"] def params_handle(self): # 將請求參數(shù)進行參數(shù)化 if self.datas["params"]: self.datas["params"] = re_replace(self.datas["params"], self.params_list) # 這里把請求參數(shù)格式化,轉(zhuǎn)成不同方法需要的參數(shù)格式 # 將form表單格式的參數(shù)轉(zhuǎn)化成字典 if self.datas["content_type"] in ["form","upload"] and self.datas["params"]: self.datas["params"] = {k: v for item in self.datas["params"].split("&") for k, v in [item.split("=")]} # 將json字符串解包為字典 elif self.datas["content_type"] == "json": self.datas["params"] = eval(self.datas["params"]) return self.datas["params"] def user_var_handle(self,res): # 獲取響應(yīng)信息中下文接口用到的參數(shù) if self.datas["json_path"]: for i in self.datas["json_path"].keys(): self.params_list[i] = json_path(res.json(), self.datas["json_path"][i]) return self.params_list def assert_handle(self,res): # 將預(yù)期結(jié)果處理 if self.datas["except"]: # 參數(shù)化替換 self.datas["except"] = re_replace(self.datas["except"], self.params_list) # 轉(zhuǎn)成字符串列表 self.datas["except"] = self.datas["except"].split("&") # 斷言 for i in self.datas["except"]: print(f"斷言:{i}") assert i in res.text def send_request(self,datas,file=None): res = http_request.http_request(datas,file) return res
這里主要做了幾點處理:
- 將請求頭部分做了參數(shù)化替換并進行了數(shù)據(jù)格式轉(zhuǎn)換,轉(zhuǎn)為字典格式
- 請求 body 進行參數(shù)化,并根據(jù)請求方式和 content-type 做了相應(yīng)處理
- 用戶參數(shù)處理,用 jsonpath 獲取響應(yīng)信息字段值存放到測試類屬性中
- 預(yù)期結(jié)果斷言處理(這里統(tǒng)一用的自帶的 assert 方法,還沒進行二次封裝)
- 將更新后的 data 發(fā)送請求
4、測試類,測試方法
class TestDemo(DataHandle): params_list = {'custName': unique_string(), "custCertiNo": random_string(), "custLegalPersonCertiNo": random_string(18), "custBankAccount": random_string(18), "managerStamp":{"file": ("testpicture.png", open(upload_file + "\\" + "testpicture.png", "rb"), "image/png")} } @ pytest.mark.usefixtures("login_guanliren") @ pytest.mark.parametrize("datas", DoExcel("demo").get_data_from_excel()) def test_case(self, datas): # 數(shù)據(jù)初始化 self.datas_init(datas, self.params_list) # 請求頭處理 datas["headers"] = self.headers_handle() # 請求參數(shù)處理 datas["params"] = self.params_handle() # 獲取文件名 filename = datas["filename"] # 發(fā)送接口請求 res = self.send_request(datas=datas,file=self.params_list[filename] if datas["content_type"] == "upload" else None) # 參數(shù)列表處理 self.params_list = self.user_var_handle(res) # 預(yù)期結(jié)果及斷言 self.assert_handle(res)
這里測試類繼承了上面的 DataHandle 類,測試方法中可以調(diào)用父類方法進行測試數(shù)據(jù)的處理 params_list 是用字典存放的參數(shù)化的數(shù)據(jù),我這里是通過正則替換把請求參數(shù)里面的需要被替換的部分找到 params_list 里的 key,將 value 進行替換
5、runner 執(zhí)行測試用例
import pytestfrom project.utils.base_dir import *if __name__ == '__main__': pytest.main(['-vs', f'{test_class}', f'--alluredir={allure_dir} ', '--clean-alluredir']) os.system(f"allure generate {allure_dir} -o {allure_html} --clean")
調(diào)用 pytest 的 main 方法進行命令行參數(shù)執(zhí)行,添加了 allure 生成測試報告
6、測試報告展示
7、未完成功能
- excel 讀?。含F(xiàn)在只支持當(dāng)前 sheet 頁讀取,后續(xù)擴展成傳 sheet 名稱指定獲取
- 數(shù)據(jù)庫斷言:excel 添加一列存放 sql 語句,發(fā)送接口請求后查詢數(shù)據(jù)庫,將這部分加進斷言里
- http_requests 目前只有 get 和 post 方法,后續(xù)將其他方法也封裝進去
- 斷言方式:目前沒有對斷言封裝,用的自帶的 assert 函數(shù),后面可以進行二次封裝或用第三方庫實現(xiàn)
到此這篇關(guān)于使用python+requests+pytest實現(xiàn)接口自動化的文章就介紹到這了,更多相關(guān)python實現(xiàn)接口自動化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解java調(diào)用python的幾種用法(看這篇就夠了)
這篇文章主要介紹了詳解java調(diào)用python的幾種用法(看這篇就夠了),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12python爬蟲模擬瀏覽器訪問-User-Agent過程解析
這篇文章主要介紹了python爬蟲模擬瀏覽器訪問-User-Agent過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12Sublime Text3最新激活注冊碼分享適用2020最新版 親測可用
這篇文章主要介紹了Sublime Text3最新激活注冊碼分享親測3211可用2020-11-11Python實現(xiàn)隱馬爾可夫模型的前向后向算法的示例代碼
這篇文章主要介紹了Python實現(xiàn)隱馬爾可夫模型的前向后向算法,本文通過實例代碼給大家講解的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-12-12Python爬蟲分析微博熱搜關(guān)鍵詞的實現(xiàn)代碼
這篇文章主要介紹了Python爬蟲分析微博熱搜關(guān)鍵詞的實現(xiàn)代碼,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02Pytorch反向求導(dǎo)更新網(wǎng)絡(luò)參數(shù)的方法
今天小編就為大家分享一篇Pytorch反向求導(dǎo)更新網(wǎng)絡(luò)參數(shù)的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08