每天進(jìn)步一點(diǎn)點(diǎn),關(guān)注我們哦,每天分享測(cè)試技術(shù)文章
本文章出自【碼同學(xué)軟件測(cè)試】
碼同學(xué)公眾號(hào):自動(dòng)化軟件測(cè)試
碼同學(xué)抖音號(hào):小碼哥聊軟件測(cè)試
1.數(shù)據(jù)驅(qū)動(dòng)框架設(shè)計(jì)
1.框架結(jié)構(gòu)
- common: 這是一個(gè)package,主要用來存儲(chǔ)所有的底層代碼封裝
- logs: 這是一個(gè)目錄,主要用來存放日志文件
- report: 這是一個(gè)目錄,里邊的data表示測(cè)試結(jié)果數(shù)據(jù),里邊的html表示測(cè)試報(bào)告,注意這兩個(gè)目錄都是每次執(zhí)行測(cè)試時(shí)自動(dòng)生成的
- testcases: 這是一個(gè)目錄,主要用來存儲(chǔ)excel文件,excel文件里是接口測(cè)試的相關(guān)數(shù)據(jù)
- conftest.py: 重寫pytest自帶的一個(gè)內(nèi)置函數(shù)的,統(tǒng)一管理自定義fixture的
- pytest.ini: pytest相關(guān)的配置參數(shù)
- run.py: 是整個(gè)框架執(zhí)行的入口
2.excel數(shù)據(jù)規(guī)則設(shè)計(jì)
按照一定的維度進(jìn)行分類,每個(gè)分類可以當(dāng)做一個(gè)sheet工作表
- 全局變量
- 主要用來管理我們的公共數(shù)據(jù)
變量名稱 | 變量值 |
host | http://82.xxx74.xx:xxxx |
username | 18866668888 |
password | 123456 |
- 接口默認(rèn)參數(shù)
- 通常在一個(gè)項(xiàng)目中,參數(shù)如果很多的時(shí)候,我們針對(duì)測(cè)試用例去傳遞數(shù)據(jù)就會(huì)很麻煩,所以我們針對(duì)每個(gè)接口的默認(rèn)參數(shù)數(shù)據(jù)進(jìn)行單獨(dú)管理,在測(cè)試時(shí)只需要針對(duì)當(dāng)前測(cè)試用例傳遞你要測(cè)試的某個(gè)字段值即可,其他字段統(tǒng)統(tǒng)來自于默認(rèn)參數(shù)
- 填寫參數(shù)的規(guī)則:對(duì)于接口參數(shù)可能會(huì)有多種類型,表單的,查詢的,json的,文件的等等
表單類型時(shí):
{ “data”:{ “xxx”:”xxjsdhdh” }}
查詢參數(shù):
{ “params”:{ “xxx”:”xxjsdhdh” }}
json參數(shù):
{ “json”:{ “xxx”:”xxjsdhdh” }}
混合參數(shù),比如既有表單又有查詢:
{ “params”:{ “xxx”:”xxjsdhdh” }, “data”:{ “ddd”:”ddff” }}
接口名稱 | 默認(rèn)參數(shù) |
登錄 | { “data”:{ “username”:”${username}”, “password”:”${password}” } } |
新增客戶 | { “json”:{ “entity”: { “customer_name”: “沙陌001”, “mobile”: “18729399607”, “telephone”: “01028375678”, “website”: “http://mtongxue.com/”, “next_time”: “2022-05-12 00:00:00”, “remark”: “這是備注”, “address”: “北京市,北京城區(qū),昌平區(qū)”, “detailAddress”: “霍營(yíng)地鐵口”, “location”: “”, “lng”: “”, “lat”: “” } } } |
新建聯(lián)系人 | { “json”:{ “entity”: { “name”: “沙陌001聯(lián)系人”, “customer_id”:”${customerId}”, “mobile”: “18729399607”, “telephone”: “01028378782”, “email”: “sdsdd@qq.com”, “post”: “采購部員工”, “address”: “這是地址”, “next_time”: “2022-05-10 00:00:00”, “remark”: “這是備注” } } } |
新建產(chǎn)品 | { “json”:{ “entity”: { “name”: “python全棧自動(dòng)化”, “category_id”: 23, “num”: “98888”, “price”: “6980”, “description”: “接口/web/app/持續(xù)集成” } } } |
- 測(cè)試集合管理
- 測(cè)試集合管理 主要是為了控制要執(zhí)行哪些測(cè)試集合,以及測(cè)試集合執(zhí)行的順序
- 測(cè)試集合名稱:對(duì)應(yīng)的就是某個(gè)測(cè)試集合的sheet工作表名稱
- 是否執(zhí)行:只有值是y時(shí)才會(huì)被執(zhí)行,其他值不會(huì)被執(zhí)行
- 測(cè)試集合名稱是否執(zhí)行新增客戶接口測(cè)試集合y新建聯(lián)系人接口測(cè)試集合y新建產(chǎn)品接口測(cè)試集合y
- 測(cè)試集合
- 每個(gè)測(cè)試集合在excel里是一個(gè)單獨(dú)的sheet工作表,他負(fù)責(zé)某個(gè)模塊或者某個(gè)接口相關(guān)的測(cè)試用例數(shù)據(jù)管理,一個(gè)測(cè)試集合中是可以存在多個(gè)測(cè)試用例的
- 序號(hào):僅僅只是個(gè)標(biāo)識(shí),沒啥作用
- 用例名稱:一個(gè)用例可能會(huì)有多個(gè)接口的先后調(diào)用,在excel里一行數(shù)據(jù)就是針對(duì)一個(gè)接口的調(diào)用,多行數(shù)據(jù)就是多個(gè)接口的調(diào)用,如果一個(gè)用例需要用多行數(shù)據(jù),那么這幾行的用例名稱保持一致
- 接口名稱:該列主要是為了和接口默認(rèn)參數(shù)中的接口名稱進(jìn)行關(guān)聯(lián),通過接口名稱得到該接口對(duì)應(yīng)的默認(rèn)參數(shù),然后再根據(jù)測(cè)試數(shù)據(jù)來決定參數(shù)是什么
- 接口地址:表示接口地址,在接口地址里域名幾乎都是相同的,或者說是公共的,所以我們將域名作為了公共變量進(jìn)行存儲(chǔ),那么在這里需要調(diào)用公共變量域名,調(diào)用方式${host},host就是公共變量中的一個(gè)變量
- 請(qǐng)求方式:get/post/put/delete
- 接口頭信息:指的就是headers,對(duì)于一個(gè)接口來說不一定有特殊的頭信息,那么就不填,如果有需要按照如下格式進(jìn)行填寫,以json格式字符串的方式:
- token是要從登錄接口的返回值中提取的,提取之后保存到一個(gè)變量中,咱們這里保存的變量名稱叫做token,所以在這里引用了變量token,引用方式就是${token}
- {“Admin-Token”:”${token}”}
- 假如特殊的頭信息有多個(gè),寫法就是在json字符串中繼續(xù)追加鍵值對(duì),比如:
- {“Admin-Token”:”${token}”,”Content-Type”:”application/json”}
- 測(cè)試數(shù)據(jù):指的是在接口發(fā)起調(diào)用時(shí)傳遞的你要測(cè)試的某個(gè)數(shù)據(jù),對(duì)于一個(gè)接口來說參數(shù)有很多,但是我們每次測(cè)試時(shí),可能只是針對(duì)一兩個(gè)參數(shù)進(jìn)行測(cè)試,可以借助之前所學(xué)的通過jsonpath去匹配某些參數(shù),并且替換他們的值
- 設(shè)計(jì)思路:
- 一個(gè)json格式的字符串,其中參數(shù)類型分為data、json、params、files
- 其中的key是你要替換的目標(biāo)參數(shù)對(duì)應(yīng)的jsonpath,value就是該參數(shù)對(duì)應(yīng)的新值,也就是測(cè)試數(shù)據(jù)
- { “json”:{ “$.entity.customer_id”:999999999, }}
- 如果要替換多個(gè)參數(shù):
- { “json”:{ “$.entity.name”:”自動(dòng)化${{cur_timestamp()}}”, “$.entity.num”:”${timestamp}}” }}
- 響應(yīng)提取:響應(yīng)提取是為了從當(dāng)前接口的返回值中提取某些信息,保存在變量中,以便后續(xù)接口要使用時(shí)進(jìn)行變量引用,這也是我們常說的關(guān)聯(lián)
- 比如每個(gè)接口都要用到token,token是登錄接口產(chǎn)生的,所以登錄的響應(yīng)提取里要寫提取內(nèi)容,規(guī)則是以json格式的字符串作為標(biāo)準(zhǔn)格式,其中key是要保存的變量名稱,value是要提取的參數(shù)對(duì)應(yīng)的jsonpath表達(dá)式
- { “token”:”$.Admin-Token”}
- 期望響應(yīng)狀態(tài)碼:http響應(yīng)狀態(tài)碼期望值
- 期望響應(yīng)信息:表示我們要針對(duì)接口的響應(yīng)信息中的某些參數(shù)進(jìn)行斷言,設(shè)計(jì)規(guī)則如下:
- 依然是json格式的字符串,最外層是一個(gè)列表,里邊套的是多個(gè)字典,一個(gè)字典就是一個(gè)參數(shù)的斷言。
- 每個(gè)字典的格式是必須包括兩個(gè)鍵值對(duì),一個(gè)actual表示實(shí)際值的key,實(shí)際值的value是參數(shù)對(duì)應(yīng)的jsonpath表達(dá)式,一個(gè)expect表示期望值的key,期望值的value是期望內(nèi)容
- [ { “actual”:”$.code”, “expect”:500, }, { “actual”:”$.msg”, “expect”:”產(chǎn)品編號(hào)已存在,請(qǐng)校對(duì)后再添加!”, }]
2.數(shù)據(jù)驅(qū)動(dòng)框架底層代碼實(shí)現(xiàn)
1.創(chuàng)建項(xiàng)目
依賴于設(shè)計(jì)去創(chuàng)建項(xiàng)目結(jié)構(gòu)
2.excel數(shù)據(jù)讀取
在common這個(gè)package下創(chuàng)建一個(gè)python文件,叫做testcase_util.py
# !/usr/bin python3 # encoding: utf-8 -*- # @file : testcase_util.py # @author : 沙陌 Matongxue_2# @Time : 2022-05-10 11:27# @Copyright: 北京碼同學(xué)import openpyxl# 讀取全局變量sheet工作表def get_variables(wb): sheet_data = wb[‘全局變量’] variables = {} # 用來存儲(chǔ)讀到的變量,名稱是key,值是value lines_count = sheet_data.max_row # 獲取總行數(shù) for l in range(2,lines_count+1): key = sheet_data.cell(l,1).value value = sheet_data.cell(l,2).value variables[key] = value return variablesdef get_api_default_params(wb): sheet_data = wb[‘接口默認(rèn)參數(shù)’] api_default_params = {} # 用來存儲(chǔ)讀到的變量,名稱是key,值是value lines_count = sheet_data.max_row # 獲取總行數(shù) for l in range(2,lines_count+1): key = sheet_data.cell(l,1).value value = sheet_data.cell(l,2).value api_default_params[key] = value return api_default_params# 獲取要執(zhí)行的測(cè)試集合名稱def get_casesuitename(wb): sheet_data = wb[‘測(cè)試集合管理’] lines_count = sheet_data.max_row # 獲取總行數(shù) cases_suite_name = [] # 用來存儲(chǔ)要執(zhí)行的測(cè)試集合名稱 for l in range(2,lines_count+1): flag = sheet_data.cell(l,2).value if flag == ‘y’: suite_name = sheet_data.cell(l,1).value cases_suite_name.append(suite_name) return cases_suite_name# 需要根據(jù)要執(zhí)行的測(cè)試集合名稱來讀取對(duì)應(yīng)的測(cè)試用例數(shù)據(jù)def read_testcases(wb,suite_name): sheet_data = wb[suite_name] lines_count = sheet_data.max_row # 獲取總行數(shù) cols_count = sheet_data.max_column # 獲取總列數(shù) “”” 規(guī)定讀出來的測(cè)試數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)如下: { “新增客戶正確”:[ [‘apiname’,’接口地址’,’請(qǐng)求方式’,’頭信息’,….], [‘apiname’,’接口地址’,’請(qǐng)求方式’,’頭信息’,….], ], “新增客戶失敗-用戶名為空”:[ [‘apiname’,’接口地址’,’請(qǐng)求方式’,’頭信息’,….] ], “新增客戶失敗-手機(jī)號(hào)格式不正確”:[ [‘apiname’,’接口地址’,’請(qǐng)求方式’,’頭信息’,….] ] } “”” cases_info = {} #用來存儲(chǔ)當(dāng)前測(cè)試集合中的所有用例信息的 for l in range(2,lines_count+1): case_name = sheet_data.cell(l,2).value # 測(cè)試用例名稱 lines = [] # 用來存儲(chǔ)當(dāng)前行測(cè)試數(shù)據(jù)的 for c in range(3,cols_count+1): cell = sheet_data.cell(l,c).value # 當(dāng)前單元格數(shù)據(jù) if cell == None: # 處理空單元格 cell = ” lines.append(cell) # 判斷當(dāng)前用例名稱是否已存在于cases_info中 # 如果不存在,那就是直接賦值 # 否則就是在原來的基礎(chǔ)上追加 if case_name not in cases_info: cases_info[case_name] = [lines] else: cases_info[case_name].append(lines) return cases_info# 整合所有要執(zhí)行的測(cè)試用例數(shù)據(jù),將其轉(zhuǎn)成pytest參數(shù)化需要的數(shù)據(jù)結(jié)構(gòu)格式def get_all_testcases(wb): “”” 整合后的數(shù)據(jù)結(jié)構(gòu)是 [ [‘新增客戶接口測(cè)試集合’,’新增客戶正確’,[[],[]]], [‘新增客戶接口測(cè)試集合’,’新增客戶失敗-用戶名為空’,[[],[]]], [‘新增客戶接口測(cè)試集合’,’新增客戶失敗-手機(jī)號(hào)格式不正確’,[[],[]]], [‘新建產(chǎn)品接口測(cè)試集合’,’新建產(chǎn)品正確’,[[],[]]], [‘新建產(chǎn)品接口測(cè)試集合’,’新建產(chǎn)品失敗-產(chǎn)品編碼重復(fù)’,[[],[]]], ] :param wb: :return: “”” test_data = [] # 用來存儲(chǔ)所有測(cè)試數(shù)據(jù) # 獲取所有要執(zhí)行的測(cè)試集合名稱 cases_suite_name = get_casesuitename(wb) for suite_name in cases_suite_name: # 遍歷讀取每個(gè)要執(zhí)行的測(cè)試集合sheet工作表中的測(cè)試用例數(shù)據(jù) cur_cases_info = read_testcases(wb,suite_name) # 是個(gè)字典 for key,value in cur_cases_info.items(): # key實(shí)際上就是測(cè)試用例名稱,value實(shí)際上測(cè)試用例多行數(shù)據(jù)信息 case_info = [suite_name,key,value] test_data.append(case_info) return test_dataif __name__ == ‘__main__’: wb = openpyxl.load_workbook(‘../testcases/CRM系統(tǒng)接口測(cè)試用例.xlsx’) # print(get_variables(wb)) # print(get_api_default_params(wb)) # print(get_casesuitename(wb)) # print(read_testcases(wb,’新增客戶接口測(cè)試集合’)) print(get_all_testcases(wb))
3.接口調(diào)用底層方法封裝
在common目錄下創(chuàng)建一個(gè)client.py,寫上如下代碼
# !/usr/bin python3 # encoding: utf-8 -*- # @file : client.py # @author : 沙陌 Matongxue_2# @Time : 2022-05-11 10:01# @Copyright: 北京碼同學(xué)import jsonpathimport requestssession = requests.session()class RequestsClient: def send(self,url,method,**kwargs): try: self.resp = session.request(url=url,method=method,**kwargs) except BaseException as e: raise BaseException(f’接口發(fā)起異常:{e}’) return self.resp # 針對(duì)jsonpath的數(shù)據(jù)提取封裝一個(gè)方法 # 第一個(gè)參數(shù)指的是你要匹配的數(shù)據(jù)的jsonpath表達(dá)式 # 第二個(gè)指的是你想返回匹配到的第幾個(gè),默認(rèn)是0返回第一個(gè) def extract_resp(self,json_path,index=0): # 注意有的接口是沒有返回信息的,返回信息是空的 text = self.resp.text # 獲取返回信息的字符串形式 if text != ”: resp_json = self.resp.json() # 獲取響應(yīng)信息的json格式 # 如果能匹配到值,那么res就是個(gè)列表 # 如果匹配不到res就是個(gè)False res = jsonpath.jsonpath(resp_json,json_path) if res: if index < 0: # 如果index小于0 ,我認(rèn)為你要匹配到的所有結(jié)果 return res else: return res[index] else: print('沒有匹配到任何東西') else: raise BaseException('接口返回信息為空,無法提取')if __name__ == '__main__': client = RequestsClient() client.send(url= 'http://82.156.74.26:9099/login', method='post', data={'username':'18866668888','password':'123456'}) print(client.extract_resp('Admin-Token'))
4.輔助函數(shù)封裝及引用定義
在我們測(cè)試時(shí),有的參數(shù)并不能夠?qū)懰?,所以這個(gè)時(shí)候我們希望某個(gè)參數(shù)在每次執(zhí)行時(shí)都是動(dòng)態(tài)變化的,那么就需要我們封裝一些輔助隨機(jī)函數(shù)來幫我們完成數(shù)據(jù)的動(dòng)態(tài)變化
在common目錄下建一個(gè)util_func.py的文件,在其中寫上我們需要用到的輔助函數(shù)
隨機(jī)數(shù)生成我們可以用一個(gè)第三方庫faker
# !/usr/bin python3 # encoding: utf-8 -*- # @file : run.py # @author : 沙陌 Matongxue_2# @Time : 2022-05-10 11:24# @Copyright: 北京碼同學(xué)import hashlibimport timefrom faker import Fakerfake = Faker(locale=’zh_CN’)def rdm_phone_number(): return fake.phone_number()def cur_timestamp():#到毫秒級(jí)的時(shí)間戳 return int(time.time() * 1000)def cur_date():# 2021-12-25 return fake.date_between_dates()def cur_date_time():# 2021-12-25 10:07:33 return fake.date_time_between_dates()def rdm_date(pattern=’%Y-%m-%d’): return fake.date(pattern=pattern)def rdm_date_time(): return fake.date_time()def rdm_future_date_time(end_date): return fake.future_datetime(end_date=end_date)def md5(data): data = str(data) return hashlib.md5(data.encode(‘UTF-8’)).hexdigest()if __name__ == ‘__main__’: print(rdm_phone_number()) print(rdm_date()) print(rdm_date_time()) print(cur_date()) print(cur_timestamp()) print(cur_date_time()) print(rdm_future_date_time(‘+60d’)) print(md5(‘123456’))
在excel中需要用到動(dòng)態(tài)函數(shù)時(shí),調(diào)用規(guī)則是${{md5(123456)}} 再比如${{rdm_future_date_time(+60d)}}
免費(fèi)領(lǐng)取 碼同學(xué)軟件測(cè)試 課程筆記+超多學(xué)習(xí)資料+完整視頻+最新面試題,可以轉(zhuǎn)發(fā)文章 + 私信「碼同學(xué)666」獲取資料哦
5.excel中動(dòng)態(tài)數(shù)據(jù)的正則替換
- 正則表達(dá)式基本規(guī)則
- 基本規(guī)則: https://baike.baidu.com/item/正則表達(dá)式/1700215
- 在線正則調(diào)試:https://tool.oschina.net/regex
- 代碼封裝
- 在testcase_util.py文件中增加如下代碼:
- # 該方法是針對(duì)excel中數(shù)據(jù)中存在動(dòng)態(tài)變量時(shí),進(jìn)行變量識(shí)別以及替換的def regx_sub(string,vars_dict): res = re.findall(r’${([A-Za-z_]+?)}’,string) for var_name in res: # print(var_name) value = vars_dict[var_name] # print(value) # 得到變量對(duì)應(yīng)的值value,然后用字符串替換的方法替換 string = string.replace(f’${{{var_name}}}’,str(value)) return string# 針對(duì)excel數(shù)據(jù)中存在動(dòng)態(tài)函數(shù)調(diào)用時(shí),使用正則匹配并執(zhí)行函數(shù)完成數(shù)據(jù)替換def regx_func_exec(string): res = re.findall(r’${{(.+?)((.*?))}}’,string) for func_method in res: print(func_method) func_name = func_method[0] # 函數(shù)名稱 func_params = func_method[1] # 函數(shù)參數(shù) # 使用python中的反射機(jī)制來實(shí)現(xiàn)函數(shù)執(zhí)行 if hasattr(util_func,func_name): # 如果該函數(shù)在util_func這個(gè)文件中,那么我就去得到該函數(shù)對(duì)象 f_method = getattr(util_func,func_name) if func_params == ”: value = f_method() else: value =f_method(func_params) string = string.replace(f’${{{{{func_name}({func_params})}}}}’,str(value)) else: raise BaseException(f'{func_name}不存在’) return string# 統(tǒng)一的針對(duì)數(shù)據(jù)做動(dòng)態(tài)變量和動(dòng)態(tài)函數(shù)的替換def regx_sub_data(string,vars_dict): string = regx_sub(string,vars_dict) string = regx_func_exec(string) return string
6.統(tǒng)一測(cè)試方法封裝
針對(duì)框架去封裝一個(gè)執(zhí)行測(cè)試的入口,這個(gè)入口是一個(gè)基于pytest參數(shù)化的測(cè)試用例,在run.py中實(shí)現(xiàn)
- 補(bǔ)充json數(shù)據(jù)替換的方法
在testcases_util.py中增加如下方法:
def update_value_to_json(json_object,json_path,new_value): json_path_expr = parse(json_path) for match in json_path_expr.find(json_object): path = match.path # 這是獲取到匹配結(jié)果的路徑 if isinstance(path,Index): match.context.value[match.path.index] = new_value elif isinstance(path,Fields): match.context.value[match.path.fields[0]] = new_value return json_object
- 補(bǔ)充內(nèi)置變量timestamp
在testcases_util.py中修改下述方法
def get_variables(wb): sheet_data = wb[‘全局變量’] variables = {} # 用來存儲(chǔ)讀到的變量,名稱是key,值是value lines_count = sheet_data.max_row # 獲取總行數(shù) for l in range(2,lines_count+1): key = sheet_data.cell(l,1).value value = sheet_data.cell(l,2).value variables[key] = value # 增加一個(gè)內(nèi)置變量,叫時(shí)間戳,注意這個(gè)時(shí)間戳是當(dāng)前測(cè)試一運(yùn)行就會(huì)產(chǎn)生,產(chǎn)生之后在當(dāng)前測(cè)試未完成之前不管調(diào)用 # 多少次,都是一致的 variables[‘timestamp’] = cur_timestamp() return variables
- run.py里的代碼
# !/usr/bin python3 # encoding: utf-8 -*- # @file : run.py # @author : 沙陌 Matongxue_2# @Time : 2022-05-10 11:24# @Copyright: 北京碼同學(xué)import openpyxlimport pytestfrom common.client import RequestsClientfrom common.testcase_util import get_all_testcases, get_variables, get_api_default_params, regx_sub_data, update_value_to_jsonwb = openpyxl.load_workbook(‘testcases/CRM系統(tǒng)接口測(cè)試用例.xlsx’)# 獲取所有的測(cè)試用例數(shù)據(jù)test_data = get_all_testcases(wb)variables = get_variables(wb) # 獲取所有的公共變量,也用來存儲(chǔ)測(cè)試過程中產(chǎn)生的動(dòng)態(tài)變量api_default_params = get_api_default_params(wb) # 獲取所有接口的默認(rèn)參數(shù)數(shù)據(jù)@pytest.mark.parametrize(‘suite_name,case_name,case_info_list’,test_data)def test_run(suite_name,case_name,case_info_list): # 創(chuàng)建一個(gè)接口調(diào)用的對(duì)象 client = RequestsClient() # case_info_list 是多個(gè)接口的數(shù)據(jù),是一個(gè)列表 for case_info in case_info_list: # case_info 其實(shí)也是一個(gè)列表,表示excel某一行的測(cè)試數(shù)據(jù),從接口名稱開始往后 # [‘登錄’, ‘${host}/login’, ‘post’, ”, ”, ‘{“token”:”$.Admin-Token”}’, 200, ‘[{“actual”:”$.code”,”expect”:0}]’] kwargs = {‘verify’:False} #verify表示忽略https的證書 api_name = case_info[0] # 接口名稱 url = case_info[1] # 接口名稱 url = regx_sub_data(url,variables) # 處理url中的動(dòng)態(tài)變量及動(dòng)態(tài)函數(shù)調(diào)用 method = case_info[2] # 接口請(qǐng)求方式 headers = case_info[3] # 接口頭信息 if headers!=”: headers = regx_sub_data(headers,variables) headers = eval(headers) # 將json格式的字符串轉(zhuǎn)換成字典 kwargs[‘headers’] = headers # 測(cè)試數(shù)據(jù)并不是接口發(fā)起時(shí)真正的全部參數(shù),需要根據(jù)用戶填入的要測(cè)試的數(shù)據(jù)和該接口對(duì)應(yīng)的默認(rèn)數(shù)據(jù)進(jìn)行替換以及組合來達(dá)到 # 請(qǐng)求數(shù)據(jù) api_default_param = api_default_params[api_name] # 獲取當(dāng)前行的接口對(duì)應(yīng)的默認(rèn)數(shù)據(jù) if api_default_param != ”: api_default_param = regx_sub_data(api_default_param,variables) api_default_param = eval(api_default_param) test_params = case_info[4] # 測(cè)試數(shù)據(jù) if test_params != ”: test_params = regx_sub_data(test_params,variables) test_params = eval(test_params) # 解析測(cè)試數(shù)據(jù),通過jsonpath去替換默認(rèn)參數(shù)中的數(shù)據(jù) # 邏輯是遍歷測(cè)試數(shù)據(jù),判斷測(cè)試數(shù)據(jù)中是哪種參數(shù)類型(data/params/json/files),根據(jù)參數(shù)類型去替換默認(rèn)數(shù)據(jù)的對(duì)應(yīng)的部分 if ‘json’ in test_params: “”” { “$.entity.name”:”聯(lián)系人${{cur_timestamp()}}”, } “”” for json_path,new_value in test_params[‘json’].items(): api_default_param[‘json’] = update_value_to_json(api_default_param[‘json’],json_path,new_value) if ‘data’ in test_params: for json_path,new_value in test_params[‘data’].items(): api_default_param[‘data’] = update_value_to_json(api_default_param[‘data’],json_path,new_value) if ‘params’ in test_params: for json_path,new_value in test_params[‘params’].items(): api_default_param[‘params’] = update_value_to_json(api_default_param[‘params’],json_path,new_value) if ‘files’ in test_params: for json_path,new_value in test_params[‘files’].items(): api_default_param[‘files’] = update_value_to_json(api_default_param[‘files’],json_path,new_value) test_params = api_default_param # 整合完成測(cè)試數(shù)據(jù)和默認(rèn)數(shù)據(jù)之后,將他們分別存儲(chǔ)kwargs中 if ‘json’ in test_params: kwargs[‘json’] = test_params[‘json’] if ‘data’ in test_params: kwargs[‘data’] = test_params[‘data’] if ‘params’ in test_params: kwargs[‘params’] = test_params[‘params’] if ‘files’ in test_params: kwargs[‘files’] = test_params[‘files’] resp = client.send(url=url,method=method,**kwargs) # 發(fā)起請(qǐng)求 expect_status = case_info[6] # 期望的響應(yīng)狀態(tài)碼 assert resp.status_code == expect_status # print(resp.text) extract_resp = case_info[5] # 響應(yīng)提取 if extract_resp != ”: extract_resp = eval(extract_resp) “”” { “token”:”$.Admin-Token” } “”” for key,value in extract_resp.items(): # key就是提取后要保存的變量名稱 # value是你要提取的目標(biāo)字段對(duì)應(yīng)的jsonpath表達(dá)式 res = client.extract_resp(value) variables[key] = res expect_resp = case_info[7] # 期望的響應(yīng)信息 if expect_resp != ”: expect_resp = regx_sub_data(expect_resp,variables) expect_resp = eval(expect_resp) “”” [ { “actual”:”$.code”, “expect”:500, }, { “actual”:”$.msg”, “expect”:”產(chǎn)品編號(hào)已存在,請(qǐng)校對(duì)后再添加!”, } ] “”” for expect_info in expect_resp: json_path = expect_info[‘actual’] actual_res = client.extract_resp(json_path) expect_res = expect_info[‘expect’] pytest.assume(actual_res==expect_res,f’期望是{expect_res},實(shí)際是{actual_res}’)if __name__ == ‘__main__’: pytest.main() # 該方法會(huì)自動(dòng)掃描當(dāng)前項(xiàng)目中的pytest.ini,根據(jù)其中的配置進(jìn)行執(zhí)行
7.集成日志收集
日志收集的目的是在我們用例有失敗時(shí),可以幫助我們?nèi)プ匪輪栴}產(chǎn)生的原因。日志都要收集哪些信息呢?
主要收集接口發(fā)起以及接口響應(yīng)的各項(xiàng)信息,在什么地方去集成日志可以收集到這些信息?
- 修改client.py中的代碼如下:
- # !/usr/bin python3 # encoding: utf-8 -*- # @file : client.py # @author : 沙陌 Matongxue_2# @Time : 2022-05-11 10:01# @Copyright: 北京碼同學(xué)import loggingimport jsonpathimport requestssession = requests.session()class RequestsClient: def __init__(self): self.logger = logging.getLogger(__class__.__name__) def send(self,url,method,**kwargs): self.logger.info(f’接口地址是:{url}’) self.logger.info(f’接口請(qǐng)求方式是:{method}’) for key,value in kwargs.items(): self.logger.info(f’接口的{key}是:{value}’) try: self.resp = session.request(url=url,method=method,**kwargs) self.logger.info(f’接口響應(yīng)狀態(tài)碼是:{self.resp.status_code}’) self.logger.info(f’接口響應(yīng)信息是:{self.resp.text}’) except BaseException as e: self.logger.exception(‘接口發(fā)起異常’) raise BaseException(f’接口發(fā)起異常:{e}’) return self.resp # 針對(duì)jsonpath的數(shù)據(jù)提取封裝一個(gè)方法 # 第一個(gè)參數(shù)指的是你要匹配的數(shù)據(jù)的jsonpath表達(dá)式 # 第二個(gè)指的是你想返回匹配到的第幾個(gè),默認(rèn)是0返回第一個(gè) def extract_resp(self,json_path,index=0): # 注意有的接口是沒有返回信息的,返回信息是空的 text = self.resp.text # 獲取返回信息的字符串形式 if text != ”: resp_json = self.resp.json() # 獲取響應(yīng)信息的json格式 # 如果能匹配到值,那么res就是個(gè)列表 # 如果匹配不到res就是個(gè)False res = jsonpath.jsonpath(resp_json,json_path) if res: if index < 0: # 如果index小于0 ,我認(rèn)為你要匹配到的所有結(jié)果 self.logger.info(f'通過{json_path}提取到的結(jié)果是:{res}') return res else: self.logger.info(f'通過{json_path}提取到的結(jié)果是:{res[index]}') return res[index] else: self.logger.warning(f'通過{json_path}沒有提取到結(jié)果') return res else: self.logger.exception('接口返回信息為空,無法提取') raise BaseException('接口返回信息為空,無法提取')if __name__ == '__main__': client = RequestsClient() client.send(url= 'http://82.156.74.26:9099/login', method='post', data={'username':'18866668888','password':'123456'}) print(client.extract_resp('Admin-Token'))
- 配置pytest.ini文件
- 在pytest.ini文件中追加如下內(nèi)容:
- ;critical > error > warning > info > debuglog_cli = truelog_cli_level = INFOlog_format = %(asctime) s [%(filename) s:%(lineno) s] [%(levelname) s] %(message) slog_date_format=%Y-%m-%d %H:%M:%Slog_file = logs/test.log
- log_cli = true: 表示日志要輸出到控制臺(tái)上
- log_cli_level=INFO: 表示要收集的日志等級(jí),日志分為critical > error > warning > info > debug
- log_format = xxx:表示日志的格式
- log_date_format = xxx: 表示日志中的時(shí)間格式
- log_file = logs/test.log: 日志收集存儲(chǔ)的文件,要注意他的路徑
8.allure測(cè)試報(bào)告集成
- 收集pytest執(zhí)行的測(cè)試結(jié)果數(shù)據(jù)
需要用到一個(gè)python的第三方庫,叫做allure-pytest,所以先安裝
在pytest.ini中追加allure結(jié)果數(shù)據(jù)收集的命令參數(shù)
addopts = -sv –alluredir ./report/data –clean-alluredir
–alluredir ./report/data :表示收集到的測(cè)試結(jié)果存放在report/data目錄中
–clean-alluredir :表示每次執(zhí)行收集結(jié)果前都先清除上一次的結(jié)果
- 生成html報(bào)告
需要用到allure的命令行工具,命令行工具下載地址:
https://github.com/allure-framework/allure2/releases
如果無法訪問,那么就下載我提供的allure-2.11.0.zip
下載之后解壓即可,解壓以后去配環(huán)境變量path,配如下路徑
配完以后,在命令行中輸入allure –version能看到版本號(hào),就說明配置好了
記得重啟pycharm,在pycharm進(jìn)入終端輸入如下命令:
allure generate ./report/data -o ./report/html
報(bào)告打開:
每次命令行輸入命令比較麻煩,可以直接將生成命令集成在代碼中,修改run.py中的main里代碼如下:
if __name__ == ‘__main__’: pytest.main() # 該方法會(huì)自動(dòng)掃描當(dāng)前項(xiàng)目中的pytest.ini,根據(jù)其中的配置進(jìn)行執(zhí)行 os.system(‘allure generate ./report/data -o ./report/html –clean’)
- 優(yōu)化allure測(cè)試報(bào)告展示
增加測(cè)試用例的層級(jí)劃分
修改run.py中的代碼如下:
# !/usr/bin python3 # encoding: utf-8 -*- # @file : run.py # @author : 沙陌 Matongxue_2# @Time : 2022-05-10 11:24# @Copyright: 北京碼同學(xué)import osimport allureimport openpyxlimport pytestfrom common.client import RequestsClientfrom common.testcase_util import get_all_testcases, get_variables, get_api_default_params, regx_sub_data, update_value_to_jsonwb = openpyxl.load_workbook(‘testcases/CRM系統(tǒng)接口測(cè)試用例.xlsx’)# 獲取所有的測(cè)試用例數(shù)據(jù)test_data = get_all_testcases(wb)variables = get_variables(wb) # 獲取所有的公共變量,也用來存儲(chǔ)測(cè)試過程中產(chǎn)生的動(dòng)態(tài)變量api_default_params = get_api_default_params(wb) # 獲取所有接口的默認(rèn)參數(shù)數(shù)據(jù)@pytest.mark.parametrize(‘suite_name,case_name,case_info_list’,test_data)def test_run(suite_name,case_name,case_info_list): # 創(chuàng)建一個(gè)接口調(diào)用的對(duì)象 client = RequestsClient() allure.dynamic.feature(suite_name) # 測(cè)試報(bào)告上會(huì)高于測(cè)試用例的層級(jí)展示 allure.dynamic.title(case_name) # 測(cè)試報(bào)告上表示測(cè)試用例的名稱 # case_info_list 是多個(gè)接口的數(shù)據(jù),是一個(gè)列表 for case_info in case_info_list: # case_info 其實(shí)也是一個(gè)列表,表示excel某一行的測(cè)試數(shù)據(jù),從接口名稱開始往后 # [‘登錄’, ‘${host}/login’, ‘post’, ”, ”, ‘{“token”:”$.Admin-Token”}’, 200, ‘[{“actual”:”$.code”,”expect”:0}]’] kwargs = {‘verify’:False} #verify表示忽略https的證書 api_name = case_info[0] # 接口名稱 url = case_info[1] # 接口名稱 url = regx_sub_data(url,variables) # 處理url中的動(dòng)態(tài)變量及動(dòng)態(tài)函數(shù)調(diào)用 method = case_info[2] # 接口請(qǐng)求方式 headers = case_info[3] # 接口頭信息 if headers!=”: headers = regx_sub_data(headers,variables) headers = eval(headers) # 將json格式的字符串轉(zhuǎn)換成字典 kwargs[‘headers’] = headers # 測(cè)試數(shù)據(jù)并不是接口發(fā)起時(shí)真正的全部參數(shù),需要根據(jù)用戶填入的要測(cè)試的數(shù)據(jù)和該接口對(duì)應(yīng)的默認(rèn)數(shù)據(jù)進(jìn)行替換以及組合來達(dá)到 # 請(qǐng)求數(shù)據(jù) api_default_param = api_default_params[api_name] # 獲取當(dāng)前行的接口對(duì)應(yīng)的默認(rèn)數(shù)據(jù) if api_default_param != ”: api_default_param = regx_sub_data(api_default_param,variables) api_default_param = eval(api_default_param) test_params = case_info[4] # 測(cè)試數(shù)據(jù) if test_params != ”: test_params = regx_sub_data(test_params,variables) test_params = eval(test_params) # 解析測(cè)試數(shù)據(jù),通過jsonpath去替換默認(rèn)參數(shù)中的數(shù)據(jù) # 邏輯是遍歷測(cè)試數(shù)據(jù),判斷測(cè)試數(shù)據(jù)中是哪種參數(shù)類型(data/params/json/files),根據(jù)參數(shù)類型去替換默認(rèn)數(shù)據(jù)的對(duì)應(yīng)的部分 if ‘json’ in test_params: “”” { “$.entity.name”:”聯(lián)系人${{cur_timestamp()}}”, } “”” for json_path,new_value in test_params[‘json’].items(): api_default_param[‘json’] = update_value_to_json(api_default_param[‘json’],json_path,new_value) if ‘data’ in test_params: for json_path,new_value in test_params[‘data’].items(): api_default_param[‘data’] = update_value_to_json(api_default_param[‘data’],json_path,new_value) if ‘params’ in test_params: for json_path,new_value in test_params[‘params’].items(): api_default_param[‘params’] = update_value_to_json(api_default_param[‘params’],json_path,new_value) if ‘files’ in test_params: for json_path,new_value in test_params[‘files’].items(): api_default_param[‘files’] = update_value_to_json(api_default_param[‘files’],json_path,new_value) test_params = api_default_param # 整合完成測(cè)試數(shù)據(jù)和默認(rèn)數(shù)據(jù)之后,將他們分別存儲(chǔ)kwargs中 if ‘json’ in test_params: kwargs[‘json’] = test_params[‘json’] if ‘data’ in test_params: kwargs[‘data’] = test_params[‘data’] if ‘params’ in test_params: kwargs[‘params’] = test_params[‘params’] if ‘files’ in test_params: kwargs[‘files’] = test_params[‘files’] resp = client.send(url=url,method=method,**kwargs) # 發(fā)起請(qǐng)求 expect_status = case_info[6] # 期望的響應(yīng)狀態(tài)碼 assert resp.status_code == expect_status # print(resp.text) extract_resp = case_info[5] # 響應(yīng)提取 if extract_resp != ”: extract_resp = eval(extract_resp) “”” { “token”:”$.Admin-Token” } “”” for key,value in extract_resp.items(): # key就是提取后要保存的變量名稱 # value是你要提取的目標(biāo)字段對(duì)應(yīng)的jsonpath表達(dá)式 res = client.extract_resp(value) variables[key] = res expect_resp = case_info[7] # 期望的響應(yīng)信息 if expect_resp != ”: expect_resp = regx_sub_data(expect_resp,variables) expect_resp = eval(expect_resp) “”” [ { “actual”:”$.code”, “expect”:500, }, { “actual”:”$.msg”, “expect”:”產(chǎn)品編號(hào)已存在,請(qǐng)校對(duì)后再添加!”, } ] “”” for expect_info in expect_resp: json_path = expect_info[‘actual’] actual_res = client.extract_resp(json_path) expect_res = expect_info[‘expect’] pytest.assume(actual_res==expect_res,f’期望是{expect_res},實(shí)際是{actual_res}’)if __name__ == ‘__main__’: pytest.main() # 該方法會(huì)自動(dòng)掃描當(dāng)前項(xiàng)目中的pytest.ini,根據(jù)其中的配置進(jìn)行執(zhí)行 os.system(‘allure generate ./report/data -o ./report/html –clean’)
免費(fèi)領(lǐng)取碼同學(xué)軟件測(cè)試課程筆記+超多學(xué)習(xí)資料+學(xué)習(xí)完整視頻,可以關(guān)注我們公眾號(hào)哦:自動(dòng)化軟件測(cè)試
本文著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。