HttpRunner 的結果校驗器優化

debugtalk發表於2019-02-19

在測試用例中,包含預期結果這麼一項,用於輔助測試人員執行測試用例時判斷系統的功能是否正常。而在自動化測試中,我們的目標是讓測試用例自動執行,因此自動化測試用例中同樣需要包含預期結果一項,只不過系統響應結果不再由人工來進行判斷,而是交由測試工具或框架來實現。

這部分功能對應的就是測試結果校驗器(validator),基本上能稱得上自動化測試工具或框架的都包含該功能特性。

設計之初

HttpRunner在設計之初,結果校驗器(validator)的實現比較簡單。

對於每一個test,可以指定0個或多個校驗項,放置在validate中。在自動化測試執行的時候,會在發起HTTP請求、解析結果響應之後,逐個檢查各個校驗項,若存在任意校驗項不通過的情況,則該test將終止並被標記為失敗。

- test:
    name: get token
    request:
        url: http://127.0.0.1:5000/api/get-token
        method: GET
    extract:
        - token: content.token
    validate:
        - {"check": "status_code", "comparator": "eq", "expect": 200}
        - {"check": "content.token", "comparator": "len_eq", "expect": 16}
複製程式碼

如上例所示,每一個校驗項均為一個json結構,裡面包含checkexpectcomparator三個屬性欄位。其中,check對應著要檢查的欄位,expect對應著檢查欄位預期的值,這兩項是必須指定的;comparator欄位對應著比較方法,若不指定,則預設採用eq,即檢查欄位與預期值相等。

為了實現儘可能強大的檢查功能,check屬性值可通過鏈式操作精確指定具體的欄位,comparator也內建實現了大量的檢查功能。

舉個例子可能會更清晰些。假如某結構的響應結果如下:

// status code: 200

// response headers
{
   "Content-Type": "application/json"
}

// response body content
{
   "success": False,
   "person": {
       "name": {
           "first_name": "Leo",
           "last_name": "Lee",
       },
       "age": 29,
       "cities": ["Guangzhou", "Shenzhen"]
   }
}
複製程式碼

那麼假如我們要檢查status codecheck就可以指定為status_code;假如要檢查response headers中的Content-Typecheck就可以指定為headers.content-type;假如要檢查response body中的first_namecheck就可以指定為content.person.name.first_name。可以看出,假如下一層級為字典結構,那麼就可以通過.運算子指定下一層級的key,依次類推。

對於欄位內容為列表list的情況略有不同,我們需要通過序號來指定具體檢查哪一項內容。例如,Guangzhou對應的檢查項為content.person.cities.0Shenzhen對應的檢查項為content.person.cities.1

在比較方式(comparator)方面,HttpRunner除了eq,還內建了大量的檢查方法。例如,我們可以通過gtgeltle等比較數值大小,通過len_eqlen_gtlen_lt等比較長度是否相等(列表、字典、字串均適用),通過containscontained_by來判斷包含關係,通過startswithendswith判斷字串的開頭結尾,甚至通過regex_match來判斷是否滿足正則匹配等。詳細的比較方式還有許多,需要時可檢視comparator表格。

存在的侷限性

在大多數情況下,HttpRunner的結果校驗器(validator)是夠用的。不過問題在於,框架不可能為使用者實現所有的檢查方法,假如使用者需要某些特殊的檢查方法時,HttpRunner就沒法實現了。

這的確是一個問題,之前Junho2010提的issue #29中舉了一個例子,應該也算是比較有代表性。

傳送請求時的資料使用了隨機生成,然後需要比較結果中的資料是否是和這個相關(通過某個演算法轉換)。比如我輸入的是321,我的結果是(3+2+1) * avg(3+2+1)這種轉化,目前的comparator是比較難於實現的。

要解決這個問題,最好的方式應該是在HttpRunner中實現自定義結果校驗器的機制;使用者在有需要的時候,可以自己編寫校驗函式,然後在validate中引用校驗函式。之前也介紹過HttpRunner的熱載入機制,《約定大於配置:ApiTestEngine實現熱載入機制》,自定義結果校驗器應該也是可以採用這種方式來實現的。

第二個需要優化的點,HttpRunner的結果校驗器還不支援變數引用,會造成某些場景下的侷限性。例如,testwangchao曾提過一個issue #52

介面response內,會返回資料庫內的自增ID。ID校驗的時候,希望expected為引數化的值。

validate:
    - {"check": "content.data.table_list.0.id", "expected": "$id"}
複製程式碼

另外,在《ApiTestEngine,不再侷限於API的測試》一文中有介紹過,結果提取器(extract)新增實現了通過正規表示式對任意文字響應內容的欄位提取。考慮到結果校驗器(validate)也需要先從結果響應中提取出特定欄位才能與預期值進行比較,在具體實現上完全可以複用同一部分程式碼,因此在validatecheck部分也可以進行統一化處理。

經過前面的侷限性問題描述,我們的改造目標也明確了,主要有三個方面:

  • 新增支援自定義結果校驗器
  • 結果校驗器中實現變數引用
  • 結果校驗內容新增支援正規表示式提取

改造結果

具體的改造過程就不寫了,有興趣的同學可以直接閱讀原始碼,重點檢視httprunner/context.py中的parse_validatordo_validationvalidate三個函式。

經過優化後,改造目標中的三項功能都實現了。為了更好地展現改造後的結果校驗器,此處將結合例項進行演示。

新增支援自定義結果校驗器

先來看第一個優化項,新增支援自定義結果校驗器。

假設我們需要使用HTTP響應狀態碼各個數字的和來進行校驗,例如,201狀態碼對應的數字和為3,503狀態碼對應的數字和為8。該例項只是為了演示用,實際上並不會用到這樣的校驗方式。

首先,該種校驗方式在HttpRunner中並沒有內建,因此需要我們自己來實現。實現方式與熱載入機制相同,只需要將自定義的校驗函式放置到當前YAML/JSON檔案同級或者父級目錄的debugtalk.py中。

對於自定義的校驗函式,需要遵循三個規則:

  • 自定義校驗函式需放置到debugtalk.py
  • 引數有兩個:第一個為原始資料,第二個為原始資料經過運算後得到的預期結果值
  • 在校驗函式中通過assert將實際運算結果與預期結果值進行比較

對於前面提到的演示案例,我們就可以在debugtalk.py中編寫如下校驗函式。

def sum_status_code(status_code, expect_sum):
    """ sum status code digits
        e.g. 400 => 4, 201 => 3
    """
    sum_value = 0
    for digit in str(status_code):
        sum_value += int(digit)

    assert sum_value == expect_sum
複製程式碼

然後,在YAML/JSON格式測試用例的validate中,我們就可以將校驗函式名稱sum_status_code作為comparator進行使用了。

- test:
    name: get token
    request:
        url: http://127.0.0.1:5000/api/get-token
        method: GET
    validate:
        - {"check": "status_code", "comparator": "eq", "expect": 200}
        - {"check": "status_code", "comparator": "sum_status_code", "expect": 2}
複製程式碼

由此可見,自定義的校驗函式sum_status_codeHttpRunner內建的校驗方法eq在使用方式上完全相同,應該沒有理解上的難度。

結果校驗器中實現變數引用

對於第二個優化項,結果校驗器中實現變數引用。在使用方式上我們應該與request中的變數引用一致,即通過$var的方式來引用變數var

- test:
    name: get token
    request:
        url: http://127.0.0.1:5000/api/get-token
        method: GET
    variables:
        - expect_status_code: 200
        - token_len: 16
    extract:
        - token: content.token
    validate:
        - {"check": "status_code", "comparator": "eq", "expect": "$expect_status_code"}
        - {"check": "content.token", "comparator": "len_eq", "expect": "$token_len"}
        - {"check": "$token", "comparator": "len_eq", "expect": "$token_len"}
複製程式碼

通過以上示例可以看出,在結果校驗器validate中,checkexpect均可實現實現變數的引用;而引用的變數,可以來自四種型別:

  • 當前test中定義的variables,例如expect_status_code
  • 當前test中提取(extract)的結果變數,例如token
  • 當前測試用例集testset中,先前test中提取(extract)的結果變數
  • 當前測試用例集testset中,全域性配置config中定義的變數

check欄位除了可以引用變數,以及保留了之前的鏈式操作定位欄位(例如上例中的content.token)外,還新增了採用正規表示式提取內容的方式,也就是第三個優化項。

結果校驗內容新增支援正規表示式提取

假設如下介面的響應結果內容為LB123abcRB789,那麼要提取出abc部分進行校驗,就可以採用如下描述方式。

- test:
    name: get token
    request:
        url: http://127.0.0.1:5000/api/get-token
        method: GET
    validate:
        - {"check": "LB123(.*)RB789", "comparator": "eq", "expect": "abc"}
複製程式碼

可見在使用方式上與在結果提取器(extract)中完全相同。

結果校驗器的進一步簡化

最後,為了進一步簡化結果校驗的描述,我在validate中新增實現了一種描述方式。

簡化後的描述方式與原始方式對比如下:

validate:
    - comparator_name: [check_item, expect_value]
    - {"check": check_item, "comparator": comparator_name, "expect": expect_value}
複製程式碼

同樣是前面的例子,採用新的描述方式後會更加簡潔。而兩種方式表達的含義是完全等價的。

- test:
    name: get token
    request:
        url: http://127.0.0.1:5000/api/get-token
        method: GET
    validate:
        - eq: ["status_code", $expect_status_code]
        - sum_status_code: ["status_code", 2]
        - len_eq: ["$token", $token_len]
        - len_eq: ["content.token", 16]
        - eq: ["LB123(.*)RB789", "abc"]
複製程式碼

當然,此次優化保證了與歷史版本的相容,之前編寫的測試用例指令碼的執行是完全不會受到任何影響的。

相關文章