背景
在《聊聊接口測試》中我提到了使用Jmeter的問題和局限性垢揩。
這里其實是有一個問題的骗炉。Jmeter的學(xué)習成本其實挺大的卵佛,基礎(chǔ)的發(fā)請求斷言這類功能當然是很簡單,再往后茧痕,很多細節(jié)上的處理問題野来,解決起來就非常非常困難,網(wǎng)絡(luò)上很難找到類似的問題和解決方法踪旷,即使是自己去翻官方文檔曼氛,也不一定就能很快的找到。
那自己寫一個接口測試就迫在眉睫了令野,本著自己驅(qū)動自己的想法舀患,我直接把所有內(nèi)容寫在代碼中,自己維護起來也很快彩掐。
環(huán)境
版本相關(guān)
操作系統(tǒng):Mac OS X EI Caption
Python版本:3.6
IDE:PyCharm
第三方依賴庫:requests
前端:Bootstrap
可視化:Echarts
思路
這部分主要參考Jmeter的方法构舟。先執(zhí)行接口測試,然后收集執(zhí)行結(jié)果堵幽,寫到一個結(jié)果文件中,再用腳本去讀這個結(jié)果文件弹澎,生成結(jié)果報告朴下。Jmeter是使用xml的方式生成一個jmx的結(jié)果,我對JSON熟悉一些苦蒿,就使用JSON來生成結(jié)果文件殴胧。
測試報告
整體架構(gòu)
|____Common.py
|____Debug.py
|____NewLive.py
|____outReport.py
|____report.html
|____reportData.json
|____Run.py
Common.py
封裝了一些通用的方法,為以后拓展多個項目做準備。
Debug.py
是用于編寫單條接口測試用例的文件团滥,基于Pycharm對unittest
的友好支持竿屹,調(diào)試起來非常方便。
NewLive.py
是我的接口測試文件灸姊,里面放了所有的接口測試用例拱燃、執(zhí)行方法和生成結(jié)果文件的方法。
outReport.py
是讀取結(jié)果文件生成HTML測試報告的腳本
report.html
是測試報告力惯。
reportData.json
是接口測試文件生成的結(jié)果文件碗誉。
Run.py
是啟動器,運行后就會批量執(zhí)行接口測試父晶。
注意:本工程只適用于單個接口測試項目哮缺,如果有多個接口測試項目,則需要增加一些遍歷的方法甲喝。
接口測試文件
這個文件是每一個接口測試項目的核心文件尝苇,整個項目的所有接口測試用例和執(zhí)行方法都在這里面。
項目的每一個接口埠胖,都寫一個類茎匠。這個接口的測試用例,在這個類中都以test開頭押袍。
使用unittest
作為框架本是最方便的方法诵冒,無奈unittest
方法對于結(jié)果文件的寫入不方便,我又懶得去翻官方文檔谊惭,于是簡單的自己寫一個啟動方法汽馋。
run方法
def run(classInstance):
"""
執(zhí)行類中的所有以test開頭的方法,前提是初始化的內(nèi)容要與類中的name屬性一致
:param classInstance:類
:return:None
"""
funcs = []
for x in dir(classInstance):
if x.startswith('test'):
funcs.append(x)
for x in funcs:
eval(classInstance.name+'.'+x+'()')
這個方法是啟動測試的實現(xiàn)方法,run()
方法需要傳入一個類作為參數(shù)圈盔,方法中需要獲取這個類中的name屬性用來啟動類中的測試用例豹芯,因此需要在類中專門定義這個name屬性,并且實例化的時候需要與這個name屬性一致驱敲。
接口類
class StartLive:
def __init__(self):
self.classes = []
self.name = 'startlive'
InterFaceInfo = {
'InterFaceName': 'StartLive',
'FuncNo': 'xxx',
'Desc': 'xxx'
}
self.classes.append(InterFaceInfo)
接口類的初始化需要定制一些內(nèi)容铁蹈。
self.classes
列表用于收集這個接口的測試情況。
self.name
需要與實例化的名稱一致众眨,用于啟動測試握牧。
InterFaceInfo
說明接口的描述,方便測試報告展示娩梨。
測試用例
def test001_StartLiveCommon(self):
"""正常開始直播"""
payload = {
"funcNo": "XXX",
"roomId": "XXX",
"userId": "XXX",
"broadIssue": time.strftime("%H%m%d") + "直播開始",
"broadNotice": time.strftime("%H%m%d") + "直播開始",
}
r = requests.post(url, data=payload)
result = r.json()
try:
assert result['error_no'] == '0'
assert result['error_info'] == '創(chuàng)建直播并發(fā)布直播公告成功'
consequence = "success"
except Exception:
consequence = 'error'
rst_data = {
"Url": url,
"desc": "正常開始直播",
"sendData": payload,
"rspData": result,
"result": consequence
}
self.classes.append(rst_data)
由于之前的run()
方法是遍歷以test開頭的方法沿腰,因此用例的方法的命名必須以test開頭。
第一部分的payload是請求的參數(shù)狈定,第二部分是請求的方法颂龙,可以根據(jù)自己的需求使用get
或者post
方法习蓬。第三部分是斷言部分,斷言成功則給一個成功的標記措嵌,斷言失敗則給一個失敗的標記躲叼。第四部分是測試結(jié)果的收集,信息包括url企巢、案例描述枫慷、發(fā)送數(shù)據(jù)、接受數(shù)據(jù)和測試結(jié)果包斑,用于最終報告的展示流礁。第五部分就是把這個結(jié)果放到接口類初始化時候的容器中。
清理方法
def testTearClass(self):
reportElement.append(self.classes)
在執(zhí)行完畢之后需要做一下數(shù)據(jù)收集罗丰,因此把初始化中的容器self.classes
列表的內(nèi)容放到reportElement
這個大容器中神帅。
注意:這個方法必須放在類的最后,確保這個方法是最后一個被執(zhí)行的萌抵,也就確保了所有的測試結(jié)果數(shù)據(jù)都能被收集找御,當然,由于要被run()方法執(zhí)行到绍填,因此命名也必須以test開頭
寫結(jié)果文件
if __name__ == '__main__':
startlive = StartLive()
run(startlive)
with codecs.open('reportData.json', 'w', 'utf-8') as f:
data = json.dumps(reportElement, sort_keys=True, indent=4)
f.write(data)
最終文件的執(zhí)行需要將類初始化霎桅,然后在run()
方法中傳入這個初始化的類,run()
方法就會自動執(zhí)行所有的測試用例讨永,將結(jié)果全部歸集到reportElement
這個容器中滔驶。
再調(diào)用寫json文件的方法把結(jié)果文件寫出來。
生成測試報告
生成測試報告的核心就是去遍歷這個JSON文件的內(nèi)容卿闹。
def exportReport(jsonName):
"""
根據(jù)結(jié)果報告的JSON文件生成HTML報告
:param jsonName:{str}JSON文件名
:return:None
"""
tableHead = []
trs = []
table = []
global interFaceName
with open(jsonName, 'r') as f:
jsonData = json.loads(f.read())
for x in jsonData: # x表示每個接口的數(shù)據(jù)
trbody = []
for i, a in enumerate(x):
if i == 0:
interFaceName = a['InterFaceName']
tableHead.append(exportInterfaceTableHead(
"接口名稱: {0}, 接口描述: {1}, 功能號: {2}".format(a['InterFaceName'], a['Desc'], a['FuncNo'])))
else:
trbody.append(
exportTableTr(interFaceName + str(i), a['desc'], a['result'], a['sendData'], a['rspData'],
a['Url']))
trs.append(''.join(trbody))
for x, y in zip(tableHead, removeEmptyInList(trs)):
table.append(x + y + exportBottom())
interFaceTable = ''.join(table)
html = htmlHead('直播接口測試', dashBoardTable(exportDashBoardTable(jsonName))) + interFaceTable + htmlFoot(jsonName)
with codecs.open('report.html', 'w', 'utf-8') as f:
f.write(html)
生成HTML結(jié)果的最優(yōu)方式揭糕,肯定是用Django來做一個小后臺,這樣可以用模板引擎來來處理HTML锻霎,更加快速和靈活著角。不過為了懶得折騰后臺,在整個生成結(jié)果的方法中旋恼,我用的是硬編碼的方式吏口,也就是說我把html的內(nèi)容全部以字符串的形式放在代碼中。然后用format
方法把一些遍歷的結(jié)果放到字符串中冰更,最終把所有的字符串全部拼接到一起产徊,直接寫到文件中,就生成了最終的測試報告冬殃。
大部分前端展示的內(nèi)容囚痴,都是使用Bootstrap來處理,比較簡單直觀审葬。餅狀圖使用的是百度Echarts。
最后
這只是一個初步的結(jié)果,后期在項目增加時涣觉,需要做一些改造痴荐,比如測試報告的歸檔,測試用例的歸檔等內(nèi)容官册,執(zhí)行方法的優(yōu)化等等。