1 接口測試介紹
接口測試是對系統(tǒng)或組件之間的接口進(jìn)行測試完丽,主要是校驗(yàn)數(shù)據(jù)的交換躲惰,傳遞和控制管理過程,以及相互邏輯依賴關(guān)系智袭。
接口自動化相對于UI自動化來說奔缠,屬于更底層的測試,這樣帶來的好處就是測試收益更大吼野,且維護(hù)成本相對來說較低校哎,是我們進(jìn)行自動化測試的首選;
2 框架選型
目前接口自動化的框架比較多箫锤,比如jmeter贬蛙,就可以集接口自動化和性能測試于一體雨女,該工具編寫用例效率不高谚攒;還有我們常用的postman,結(jié)合newman也可以實(shí)現(xiàn)接口自動化氛堕;Python+unittest+requests+HTMLTestRunner是目前比較主流的測試框架馏臭,對python有一定的編碼要求;
本期我們選擇robotframework(文中后續(xù)統(tǒng)一簡稱為RF)這一個(gè)比較老牌的測試框架進(jìn)行介紹讼稚,RF是一個(gè)完全基于關(guān)鍵字測試驅(qū)動的框架括儒,它即能夠基于它的一定規(guī)則,導(dǎo)入你需要的測試庫(例如:其集成了selenium的測試庫锐想,即可以理解為操作控件的測試底層庫)帮寻,然后基于這些測試庫,你能應(yīng)用TXT形式編寫自己的關(guān)鍵字(支持python和java語言赠摇,這些關(guān)鍵字即你的庫組成)固逗,之后,再編寫(測試用例由測試關(guān)鍵字組成)進(jìn)行測試藕帜;他支持移動端烫罩、UI自動化和接口自動化的測試;
3 環(huán)境搭建
python的安裝:目前選取的python3以上的版本洽故,RF的運(yùn)行依賴python
robotframework:參考http://www.reibang.com/p/9dcb4242b8f2
jenkins:用于調(diào)度RF的用例執(zhí)行環(huán)境
gitlab:代碼倉庫
4 需求
4.1 需求內(nèi)容
接口內(nèi)容:實(shí)現(xiàn)一個(gè)下單贝攒,并檢查訂單狀態(tài)是否正常的場景;該需求涉及到如下三個(gè)接口
下單接口
訂單結(jié)果查詢接口
下單必須帶上認(rèn)證標(biāo)識时甚,生成token的接口
環(huán)境覆蓋:需要支持能在多套環(huán)境運(yùn)行隘弊,比如測試和預(yù)發(fā)布環(huán)境
系統(tǒng)集成:需要能夠集成在CICD中哈踱,實(shí)現(xiàn)版本更新后的自動檢測
4.2 用例設(shè)計(jì)
4.2.1 用例設(shè)計(jì),根據(jù)業(yè)務(wù)場景設(shè)計(jì)測試用例梨熙,方便后續(xù)實(shí)現(xiàn)
4.2.2 測試數(shù)據(jù)構(gòu)造嚣鄙,預(yù)置不同環(huán)境的測試數(shù)據(jù),供實(shí)現(xiàn)調(diào)用
5 整體實(shí)現(xiàn)架構(gòu)
接口測試實(shí)現(xiàn)層:在RF串结,通過引用默認(rèn)關(guān)鍵字RequestsLibrary(實(shí)現(xiàn)http請求)和通過python自定義關(guān)鍵字來完成用例實(shí)現(xiàn)的需求哑子;
jenkins調(diào)度:在jenkins上配置一個(gè)job,設(shè)置好RF用例執(zhí)行的服務(wù)器和發(fā)送給服務(wù)器相關(guān)的RF執(zhí)行的指令肌割,并且在jenkins中配置好測試報(bào)告模板卧蜓,這樣用例便可以通過jenkins完成執(zhí)行并發(fā)送測試結(jié)果給項(xiàng)目干系人;
生成用例執(zhí)行的API:上圖中藍(lán)色部分把敞,就是為了將jenkins的job生成一個(gè)可訪問api接口弥奸,方便被測項(xiàng)目的CICD集成;
集成到被測系統(tǒng)CICD流程:將上面步驟中封裝的API配置在被測應(yīng)用的gitlab-ci.yml中奋早,完成整個(gè)接口自動化的閉環(huán)
6 RF用例實(shí)現(xiàn)
6.1 引用的內(nèi)置關(guān)鍵字
RequestsLibrary 構(gòu)造http的請求盛霎,get|post等請求
getRequests
# get請求的入?yún)?[Arguments]${url_domain}${getbody}${geturl}${getToken}
Create session postmain${url_domain}
# 定義header的內(nèi)容
${head}createdictionary content-type=application/json Authorization=${getToken}MerchantId=${s_merchant_id}
# get請求
${addr}getRequest postmain${geturl}params=${getbody}headers=${head}
# 請求狀態(tài)碼斷言
Should BeEqualAs Strings${addr.status_code}200
${response_get_data}To Json${addr.content}
# 返回http_get請求結(jié)果
SetTest Variable${response_get_data}
Delete All Sessions
6.2 自定義關(guān)鍵字
getEnvDomain 用于從自定義的configs.ini文件獲取對應(yīng)環(huán)境的微服務(wù)的請求域名
configs.ini的內(nèi)容
獲取configs.ini的內(nèi)容
import configparser
def getEnv(path,env):
config = configparser.ConfigParser()
config.read(path)
passport = config[env]['passport']
stock=config[env]['stock']
finance=config[env]['finance']
SUP= config[env]['SUP']
publicApi = config[env]['publicApi']
publicOrder = config[env]['publicOrder']
data_dict={'passport':passport,'stock':stock,'finance':finance,'SUP':SUP,'publicApi':publicApi,'publicOrder':publicOrder}
return data_dict
excelTodict 用戶將excel中的內(nèi)容作為字典返回
import xlrd
'''
通用獲取excel數(shù)據(jù)
@:param path excel文件路徑
@:param sheet_name excel文件里面sheet的名稱 如:Sheet1
@:env 環(huán)境,是IT還是PRE
'''
def getExcelDate(path, sheet_name,env):
bk = xlrd.open_workbook(path)
sh = bk.sheet_by_name(sheet_name)
row_num = sh.nrows
data_list = []
for i in range(1, row_num):
row_data = sh.row_values(i)
data = {}
for index, key in enumerate(sh.row_values(0)):
data[key] = row_data[index]
data_list.append(data)
data_list1 = []
for x in data_list:
#print('這是'+str(x))
if(x.get('env')==env):
data_list1.append(x)
return data_list1
getToken 提供接口下單的授權(quán)token
*** Keywords ***
# 根據(jù)傳入的clientid耽装、secret生成對應(yīng)的token
getToken
[Arguments] ${client_id} ${client_secret} ${url_domain}
Create session postmain ${url_domain}
${auth} createdictionary grant_type=client_credentials client_id=${client_id} client_secret=${client_secret}
${header} createdictionary content-type=application/x-www-form-urlencoded
${addr} postRequest postmain /oauth/token data=${auth} headers=${header}
Should Be Equal As Strings ${addr.status_code} 200
${responsedata} To Json ${addr.content}
${access} Get From Dictionary ${responsedata} access_token
${token} set variable bearer ${access}
Set Test Variable ${token}
Delete All Sessions
getAllDate 獲取該用例下的所有數(shù)據(jù)
getAllData
[Arguments] ${row_no}
getEnvDomain
getBalance ${row_no}
getStockNum ${row_no}
getSupProPrice ${row_no}
getProPrice ${row_no}
Set Test Variable ${publicOrderUrl}
Set Test Variable ${FPbalance}
Set Test Variable ${Pbalance}
Set Test Variable ${Sbalance}
Set Test Variable ${Jbalance}
Set Test Variable ${Cardnum}
Set Test Variable ${sprice}
Set Test Variable ${price}
Set Test Variable ${j_merchant_id}
Set Test Variable ${s_merchant_id}
Set Test Variable ${stock_id}
Set Test Variable ${p_product_id}
Set Test Variable ${s_product_id}
實(shí)現(xiàn)demo
*** Settings ***
Test Template
Resource 引用所有資源.txt
*** Test Cases ***
*** Settings ***
Test Template
Resource 引用所有資源.txt
*** Test Cases ***
01 下單卡密直儲商品
[Tags] order
LOG ---------------------獲取下單前的數(shù)量愤炸、余額------------------------------------------
getAllData 0
${Cardnum1} set variable ${Cardnum}
${FPbalance1} set variable ${FPbalance}
${Pbalance1} set variable ${Pbalance}
${Sbalance1} set variable ${Sbalance}
${Jbalance1} set variable ${Jbalance}
${CustomerOrderNo1} Evaluate random.randint(1000000, 9999999) random
${Time} Get Time
log ------------------------下單操作-------------------------------------------------------
getToken 100xxxx 295dab07a9xxxx9780be0eb95xxxx ${casUrl}
${input_cs} create dictionary memberId=${j_merchant_id} clientId=1xxx079 userId=string shopType=string customerOrderNo=${CustomerOrderNo1}
... productId=${p_product_id} buyNum=1 chargeAccount=otest888888 notifyUrl=string chargeIp=string chargePassword=string
... chargeGameName=string chargeGameRole=string chargeGameRegion=string chargeGameSrv=string chargeType=string remainingNumber=0
... contactTel=string contactQQ=string customerPrice=0 poundage=0 batchNumber= originalOrderId=string
... shopName=string appointSupProductId=0 stemFromSubOrderId=123456 externalBizId=456789
postRequests ${publicOrderUrl} ${input_cs} /api/Order ${token}
${data} get from dictionary ${responsedata} data
${orderid} get from dictionary ${data} id
sleep 6
${getdata} create dictionary Id=${orderid} PageIndex=1 PageSize=1
getRequests ${publicOrderUrl} ${getdata} /api/Order/GetList ${token}
${datalist} get from dictionary ${response_get_data} data
${data} get from dictionary ${datalist} list
${dict} set variable ${data}[0]
${orderOuterStatus} get from dictionary ${dict} orderOuterStatus
LOG ---------------------獲取下單后的數(shù)量、余額----------------------------------------------
getAllData 0
${Cardnum2} set variable ${Cardnum}
${FPbalance2} set variable ${FPbalance}
${Pbalance2} set variable ${Pbalance}
${Sbalance2} set variable ${Sbalance}
${Jbalance2} set variable ${Jbalance}
${sprice} set variable ${sprice}
${price} set variable ${price}
log ------------------斷言-----------------------------------------------------------------
${Cardnum3} Evaluate ${Cardnum1}
${Jbalance3} Evaluate ${Jbalance1}
${Sbalance3} Evaluate ${Sbalance1}
${Pbalance3} Evaluate ${Pbalance1}
should be true ${orderOuterStatus}==90
should be true ${Cardnum3}==${Cardnum2}
should be true ${Jbalance3}==${Jbalance2}
should be true ${Sbalance3}==${Sbalance2}
should be true ${Pbalance3}==${Pbalance2}