如何使用 Loadgen 來簡化 HTTP API 請求的集成測試
引言
在編寫 HTTP 服務(wù)的過程中,集成測試 是保證程序正確性的重要一環(huán),如下圖所示,其基本的流程就是不斷向服務(wù)發(fā)起請求然后校驗響應(yīng)的狀態(tài)和數(shù)據(jù)等:
為大量的 API 和用例編寫測試是一件繁瑣的工作,而 Loadgen ^2 正是為了簡化這一過程而設(shè)計的。
一個簡單的測試
假定我們在 127.0.0.1:9100
端口監(jiān)聽了一個 Pizza ^3 服務(wù),現(xiàn)在我們通過如下配置來測試集合(collection)的創(chuàng)建:
然后運行 loadgen -config loadgen.yml
:
$?loadgen?-config?loadgen.yml
???__???___??_??????___??___???__????__
??/?/??/___\/_\????/???\/?_?\?/__\/\?\?\
?/?/??//??///_\\??/?/\?/?/_\//_\?/??\/?/
/?/__/?\_//??_??\/?/_//?/_\\//__/?/\??/
\____|___/\_/?\_/___,'\____/\__/\_\?\/
[LOADGEN]?A?http?load?generator?and?testing?suite.
[INF]?warmup?started
[INF]?loadgen?is?up?and?running?now.
[INF]?[PUT]?http://127.0.0.1:9100/test_create_document?-
[INF]?status:?200,?error:?<nil>,?response:?{"success":true,"collection":"test_create_document"}
[INF]?warmup?finished
...
為了便于閱讀,筆者對程序輸出進(jìn)行了簡化,實際會略有區(qū)別
可以看到,Loadgen 實際上幫我們做了類似這樣的操作:
curl?-XPUT?http://127.0.0.1:9100/test_create_document
一些簡單的測試
上述示例中我們只測試了創(chuàng)建單個集合,但是實際情況下短時間內(nèi)會有許多請求涌入,對于創(chuàng)建大量的集合我們又該如何測試呢?
這里就需要用到變量 ^4 的概念:
#?loadgen.yml
variables:
??-?name:?id
????type:?sequence
requests:
??-?request:
??????method:?PUT
??????url:?http://127.0.0.1:9100/test_create_document_$[[id]]
上述配置中,我們定義了一個名為 id
的變量,sequence
是一個特殊的類型——每次被讀取時它的值會遞增,因此 Loadgen 會不斷發(fā)起類似這樣的請求:
curl?-XPUT?http://127.0.0.1:9100/test_create_document_0
curl?-XPUT?http://127.0.0.1:9100/test_create_document_1
curl?-XPUT?http://127.0.0.1:9100/test_create_document_2
...
在 Pizza 的日志中也記錄了這些請求:
$?pizza
???___?_____??__________???_
??/?_?\\_???\/?_??/?_??/??/_\
?/?/_)/?/?/\/\//?/\//?/??//_\\
/?___/\/?/_???/?//\/?//\/??_??\
\/???\____/??/____/____/\_/?\_/
[PIZZA]?The?Next-Gen?Real-Time?Hybrid?Search?&?AI-Native?Innovation?Engine.
[INFO]?Collection?test_create_document_0?created
[INFO]?Collection?test_create_document_1?created
[INFO]?Collection?test_create_document_2?created
...
不那么簡單的測試
目前為止,我們只是不斷的向一個服務(wù)“塞”大量的請求,但比起發(fā)起請求,我們常常更關(guān)心程序的響應(yīng)是否符合預(yù)期,也就是說,響應(yīng)需要滿足我們定義的一些條件,這可以通過 Loadgen 提供的 斷言 ^5 功能來實現(xiàn):
#?loadgen.yml
variables:
??-?name:?id
????type:?sequence
runner:
??#?檢查返回值是否正常
??assert_error:?true
??#?檢查斷言是否通過
??assert_invalid:?true
requests:
??-?request:
??????method:?PUT
??????url:?http://127.0.0.1:9100/test_create_document_$[[id]]
????assert:
??????equals:
????????#?注意,這里我們故意設(shè)置了一個“不正?!钡闹?,以迫使斷言失敗
????????_ctx.response.body_json.success:?false
在上述配置中,我們啟用了 Loadgen 的檢查,然后定義了一個會失敗的斷言:
equals
會校驗給定路徑_ctx.response.body_json.success
是否與期望值false
相等_ctx.response.body_json
表示 JSON 格式的響應(yīng)體success
表示響應(yīng)體中該字段對應(yīng)的值,可以用path.to.nested.key
來訪問嵌套的字段
也就是說,給定響應(yīng)體 {"success":true,"collection":"test_create_document"}
,Loadgen 會檢查 success
的值是否為 false
:
$?loadgen?-debug?-r?1?-d?3?-config?loadgen.yml
#0?request,?PUT?http://127.0.0.1:9100/test_create_document_$[[id]],?assertion?failed,?skiping?subsequent?requests
[WRN]?'_ctx.response.body_json.success'?is?not?equal?to?expected?value:?true
#0?request,?PUT?http://127.0.0.1:9100/test_create_document_$[[id]],?assertion?failed,?skiping?subsequent?requests
[WRN]?'_ctx.response.body_json.success'?is?not?equal?to?expected?value:?true
#0?request,?PUT?http://127.0.0.1:9100/test_create_document_$[[id]],?assertion?failed,?skiping?subsequent?requests
[WRN]?'_ctx.response.body_json.success'?is?not?equal?to?expected?value:?true
#0?request,?PUT?http://127.0.0.1:9100/test_create_document_$[[id]],?assertion?failed,?skiping?subsequent?requests
[WRN]?'_ctx.response.body_json.success'?is?not?equal?to?expected?value:?true
上述命令我們使用了:
-debug
啟用更詳細(xì)的報錯-r 1 -d 3
減少發(fā)起的請求數(shù)(1req/s
持續(xù)3s
)還有一個需要注意的細(xì)節(jié)是
... is not equal to expected value: true
,這里報告的是success
字段實際的值,而不是斷言中定義的期望值。
可以看到,Loadgen 每次請求的斷言都失敗了,不過我們可以通過日志來快速定位出錯的原因以便于調(diào)試。
更進(jìn)一步的測試
現(xiàn)在我們創(chuàng)建了大量的空集合,是時候向其中添加一些文檔(document)了,但是,一個首要解決的問題是,每次測試創(chuàng)建的集合名稱是帶有 $[[id]]
這個變量的,我們?nèi)绾沃缿?yīng)該向哪個集合上傳數(shù)據(jù)呢?一個可靠的解決方案是借助 Loadgen 的寄存器 功能:
#?loadgen.yml
variables:
??-?name:?id
????type:?sequence
runner:
??assert_error:?true
??assert_invalid:?true
requests:
??-?request:
??????method:?PUT
??????url:?http://127.0.0.1:9100/test_create_document_$[[id]]
????assert:
??????equals:
????????_ctx.response.body_json.success:?true
????register:
??????#?把響應(yīng)體的?collection?字段賦值給?$[[collection]]
??????-?collection:?_ctx.response.body_json.collection
??-?request:
??????method:?POST
??????#?在上個請求創(chuàng)建的集合里添加一個文檔
??????url:?http://127.0.0.1:9100/$[[collection]]/_doc
??????body:?'{"hello":?"world"}'
????assert:
??????equals:
????????_ctx.response.body_json.result:?created
上述示例中,我們利用動態(tài)注冊的變量記錄了每次測試創(chuàng)建的集合以便于后續(xù)請求使用。
最后的優(yōu)化
為了使我們的配置更加靈活和“便攜”,我們可以用環(huán)境變量來替換一些硬編碼的值:
#?loadgen.yml
variables:
??-?name:?id
????type:?sequence
runner:
??assert_error:?true
??assert_invalid:?true
requests:
??-?request:
??????method:?PUT
??????#?讀取?PIZZA_SERVER?這個環(huán)境變量
??????url:?$[[env.PIZZA_SERVER]]/test_create_document_$[[id]]
????assert:
??????equals:
????????_ctx.response.body_json.success:?true
????register:
??????-?collection:?_ctx.response.body_json.collection
??-?request:
??????method:?POST
??????url:?$[[env.PIZZA_SERVER]]/$[[collection]]/_doc
??????body:?'{"hello":?"world"}'
????assert:
??????equals:
????????_ctx.response.body_json.result:?created
這樣就可以通過:
PIZZA_SERVER=http://127.0.0.1:9101?loadgen?-config?loadgen.yml
在不同的 Pizza 服務(wù)上運行測試。
https://en.wikipedia.org/wiki/Integration_testing???
https://www.infinilabs.com/docs/latest/gateway/getting-started/benchmark???
https://www.infinilabs.com/en/docs/latest/pizza???
https://www.infinilabs.com/docs/latest/gateway/getting-started/benchmark#變量的使用???
https://www.infinilabs.com/docs/latest/gateway/getting-started/benchmark#返回值判斷???
https://www.infinilabs.com/docs/latest/gateway/getting-started/benchmark#動態(tài)變量注冊???