在測試用例中,包含預期結果這麼一項,用於輔助測試人員執行測試用例時判斷系統的功能是否正常。而在自動化測試中,我們的目標是讓測試用例自動執行,因此自動化測試用例中同樣需要包含預期結果一項,只不過系統響應結果不再由人工來進行判斷,而是交由測試工具或框架來實現。
這部分功能對應的就是測試結果校驗器(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
結構,裡面包含check
、expect
、comparator
三個屬性欄位。其中,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 code
,check
就可以指定為status_code
;假如要檢查response headers
中的Content-Type
,check
就可以指定為headers.content-type
;假如要檢查response body
中的first_name
,check
就可以指定為content.person.name.first_name
。可以看出,假如下一層級為字典結構,那麼就可以通過.
運算子指定下一層級的key
,依次類推。
對於欄位內容為列表list
的情況略有不同,我們需要通過序號來指定具體檢查哪一項內容。例如,Guangzhou
對應的檢查項為content.person.cities.0
,Shenzhen
對應的檢查項為content.person.cities.1
。
在比較方式(comparator
)方面,HttpRunner
除了eq
,還內建了大量的檢查方法。例如,我們可以通過gt
、ge
、lt
、le
等比較數值大小,通過len_eq
、len_gt
、len_lt
等比較長度是否相等(列表、字典、字串均適用),通過contains
、contained_by
來判斷包含關係,通過startswith
、endswith
判斷字串的開頭結尾,甚至通過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
)也需要先從結果響應中提取出特定欄位才能與預期值進行比較,在具體實現上完全可以複用同一部分程式碼,因此在validate
的check
部分也可以進行統一化處理。
經過前面的侷限性問題描述,我們的改造目標也明確了,主要有三個方面:
- 新增支援自定義結果校驗器
- 結果校驗器中實現變數引用
- 結果校驗內容新增支援正規表示式提取
改造結果
具體的改造過程就不寫了,有興趣的同學可以直接閱讀原始碼,重點檢視httprunner/context.py
中的parse_validator
、do_validation
和validate
三個函式。
經過優化後,改造目標中的三項功能都實現了。為了更好地展現改造後的結果校驗器,此處將結合例項進行演示。
新增支援自定義結果校驗器
先來看第一個優化項,新增支援自定義結果校驗器。
假設我們需要使用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_code
與HttpRunner
內建的校驗方法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
中,check
和expect
均可實現實現變數的引用;而引用的變數,可以來自四種型別:
- 當前
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"]
複製程式碼
當然,此次優化保證了與歷史版本的相容,之前編寫的測試用例指令碼的執行是完全不會受到任何影響的。