1. 程式人生 > >自己驅動自己—Python程式碼寫介面測試(一)

自己驅動自己—Python程式碼寫介面測試(一)

20161021

背景

《聊聊介面測試》中我提到了使用Jmeter的問題和侷限性。

這裡其實是有一個問題的。Jmeter的學習成本其實挺大的,基礎的發請求斷言這類功能當然是很簡單,再往後,很多細節上的處理問題,解決起來就非常非常困難,網路上很難找到類似的問題和解決方法,即使是自己去翻官方文件,也不一定就能很快的找到。

那自己寫一個介面測試就迫在眉睫了,本著自己驅動自己的想法,我直接把所有內容寫在程式碼中,自己維護起來也很快。

環境

版本相關

作業系統:Mac OS X EI Caption

Python版本:3.6

IDE:PyCharm

第三方依賴庫:requests

前端:Bootstrap

視覺化:Echarts

思路

這部分主要參考Jmeter的方法。先執行介面測試,然後收集執行結果,寫到一個結果檔案中,再用指令碼去讀這個結果檔案,生成結果報告。Jmeter是使用xml的方式生成一個jmx的結果,我對JSON熟悉一些,就使用JSON來生成結果檔案。

測試報告

報告展示

報告展示2

整體架構

|____Common.py
|____Debug.py
|____NewLive.py
|____outReport.py
|____report.html
|____reportData.json
|____Run.py

Common.py

封裝了一些通用的方法,為以後拓展多個專案做準備。

Debug.py是用於編寫單條介面測試用例的檔案,基於Pycharm對unittest的友好支援,除錯起來非常方便。

NewLive.py是我的介面測試檔案,裡面放了所有的介面測試用例、執行方法和生成結果檔案的方法。

outReport.py是讀取結果檔案生成HTML測試報告的指令碼

report.html是測試報告。

reportData.json是介面測試檔案生成的結果檔案。

Run.py是啟動器,執行後就會批量執行介面測試。

注意:本工程只適用於單個介面測試專案,如果有多個介面測試專案,則需要增加一些遍歷的方法。

介面測試檔案

這個檔案是每一個介面測試專案的核心檔案,整個專案的所有介面測試用例和執行方法都在這裡面。

專案的每一個介面,都寫一個類。這個介面的測試用例,在這個類中都以test開頭。

使用unittest作為框架本是最方便的方法,無奈unittest方法對於結果檔案的寫入不方便,我又懶得去翻官方文件,於是簡單的自己寫一個啟動方法。

run方法

def run(classInstance):
    """
    執行類中的所有以test開頭的方法,前提是初始化的內容要與類中的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+'()')

這個方法是啟動測試的實現方法,run()方法需要傳入一個類作為引數,方法中需要獲取這個類中的name屬性用來啟動類中的測試用例,因此需要在類中專門定義這個name屬性,並且例項化的時候需要與這個name屬性一致。

介面類

class StartLive:
    def __init__(self):
        self.classes = []
        self.name = 'startlive'
        InterFaceInfo = {
            'InterFaceName': 'StartLive',
            'FuncNo': 'xxx',
            'Desc': 'xxx'
        }
        self.classes.append(InterFaceInfo)

介面類的初始化需要定製一些內容。

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'] == '建立直播併發布直播公告成功'
            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是請求的引數,第二部分是請求的方法,可以根據自己的需求使用get或者post方法。第三部分是斷言部分,斷言成功則給一個成功的標記,斷言失敗則給一個失敗的標記。第四部分是測試結果的收集,資訊包括url、案例描述、傳送資料、接受資料和測試結果,用於最終報告的展示。第五部分就是把這個結果放到介面類初始化時候的容器中。

清理方法

def testTearClass(self):
        reportElement.append(self.classes)

在執行完畢之後需要做一下資料收集,因此把初始化中的容器self.classes列表的內容放到reportElement這個大容器中。

注意:這個方法必須放在類的最後,確保這個方法是最後一個被執行的,也就確保了所有的測試結果資料都能被收集,當然,由於要被run()方法執行到,因此命名也必須以test開頭

寫結果檔案

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)

最終檔案的執行需要將類初始化,然後在run()方法中傳入這個初始化的類,run()方法就會自動執行所有的測試用例,將結果全部歸集到reportElement這個容器中。

再呼叫寫json檔案的方法把結果檔案寫出來。

生成測試報告

生成測試報告的核心就是去遍歷這個JSON檔案的內容。

def exportReport(jsonName):
    """
    根據結果報告的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表示每個介面的資料
        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結果的最優方式,肯定是用Django來做一個小後臺,這樣可以用模板引擎來來處理HTML,更加快速和靈活。不過為了懶得折騰後臺,在整個生成結果的方法中,我用的是硬編碼的方式,也就是說我把html的內容全部以字串的形式放在程式碼中。然後用format方法把一些遍歷的結果放到字串中,最終把所有的字串全部拼接到一起,直接寫到檔案中,就生成了最終的測試報告。

大部分前端展示的內容,都是使用Bootstrap來處理,比較簡單直觀。餅狀圖使用的是百度Echarts。

最後

這只是一個初步的結果,後期在專案增加時,需要做一些改造,比如測試報告的歸檔,測試用例的歸檔等內容,執行方法的優化等等。