借助 DSL 來簡化 Loadgen 配置
引言
在上篇文章中,我們介紹了如何用 Loadgen 來簡化 HTTP API 的集成測試。在實際使用中會發(fā)現(xiàn),編寫測試時最令人“頭疼”的部分是設(shè)計測試的輸入和校驗程序的輸出,而針對后者 Loadgen 提供了豐富的條件測試 來對響應(yīng)進行斷言。
回顧上篇文章的示例:
上述配置中各請求的斷言只有一條,但如果我們的檢驗邏輯更加復雜,需要組合多重測試條件,比如我們想盡可能多的檢驗響應(yīng)體中的字段來提高測試的可靠性,那么斷言的部分將會迅速膨脹,可讀性也會隨之下降。
例如,針對如下響應(yīng):
{
??"took":?17,
??"timed_out":?false,
??"hits":?{
????"total":?{
??????"value":?100,
??????"relation":?"eq"
????},
????"max_score":?1.0,
????"hits":?[...]
??},
??"aggregations":?{
????"vavg":?{
??????"value":?51.0
????},
????"vcount":?{
??????"value":?50
????},
????"vmax":?{
??????"value":?100
????},
????"vmin":?{
??????"value":?2
????},
????"vsum":?{
??????"value":?2550
????}
??}
}
我們可能會寫出這樣的配置:
assert:
??and:
????-?range:
????????_ctx.response.body_json.took:
??????????lt:?50
????-?equals:
????????_ctx.response.status:?200
????????_ctx.response.body_json.time_out:?false
????????_ctx.response.body_json.hits.total.value:?100
????????_ctx.response.body_json.max_score:?1.0
????????_ctx.response.body_json.aggregations.vavg.value:?51
????????_ctx.response.body_json.aggregations.vcount.value:?50
????????_ctx.response.body_json.aggregations.vmax.value:?100
????????_ctx.response.body_json.aggregations.vmin.value:?2
????????_ctx.response.body_json.aggregations.vsum.value:?2550
????-?regexp:
????????_ctx.response.body_json.hits.total.relation:?eq|gt|ge
不難發(fā)現(xiàn),上面的配置看起來與原始的響應(yīng)結(jié)構(gòu)有很大的差別,寫起來十分繁瑣,看起來也不直觀,為了解決這一問題,我們?yōu)?Loadgen 的 YAML 配置設(shè)計了一種 DSL ^2。
更直觀的斷言配置
Loadgen DSL 針對各種斷言進行了著重的簡化,比如上述配置我們可以改寫成:
{
??took:?<50,
??time_out:?false,
??hits:?{
????total:?{
??????value:?100,
??????relation:?/eq|gt|ge/,
????},
??},
??max_score:?1.0,
??aggregations:?{
????vavg.value:?51,
????vcount.value:?50,
????vmax.value:?100,
????vmin.value:?2,
????vsum.value:?2550,
??},
}
這樣是不是“清爽”了許多?而且,有沒有發(fā)現(xiàn)這個語法和 JSON 很像?沒錯,Loadgen DSL 完全兼容 JSON 語法!也就是說,可以直接把響應(yīng)體復制下來,然后在其基礎(chǔ)上進行修改:
{
??//?用?<50?來測試此字段的值是否小于?50
??"took":?<50,
??//?普通的值將被測試字段實際值是否與它相等
??"timed_out":?false,
??"hits":?{
????"total":?{
??????"value":?100,
??????//?用正則表達式來檢查此字段的值
??????"relation":?/eq|gt|ge/
????},
????"max_score":?1.0
??},
??"aggregations":?{
????"vavg":?{
??????"value":?51.0
????},
????"vcount":?{
??????"value":?50
????},
????"vmax":?{
??????"value":?100
????},
????"vmin":?{
??????"value":?2
????},
????"vsum":?{
??????"value":?2550
????}
??}
}
注意到 Loadgen DSL 中字段的引號是可以省略的,同時它也支持 not
,and
和 or
的邏輯組合:
{
??took:?>0?and?<50,
??relation:?"eq"?or?"gt"?or?"ge",
??hits.total.value:?not?0,
}
而對于較復雜的條件測試,比如 prefix/contains/in
等,可以通過函數(shù)調(diào)用語法來實現(xiàn):
{
??blog.title:?prefix("INFINI"),
??blog.tag:?contains("Loadgen"),
??blog.status:?in(["reviewed",?"archived"]),
}
進一步的簡化
以上我們展示了 Loadgen DSL 是如何簡化斷言配置的,回想到上一章開頭所說的,HTTP API 測試中主要就是不斷發(fā)起請求然后校驗其響應(yīng),那我們何不在 DSL 中將請求一起解析了呢?
于是,在現(xiàn)有的語法上稍作改進,我們便可以通過如下示例:
PUT?$[[env.PIZZA_SERVER]]/test_create_document_$[[id]]
#?//?注意這里,因為我們定義?register?變量,因此需要使用完整語法
#?register:?[
#??{collection:?"_ctx.response.body_json.collection"},
#?],
#?assert:?{
#???_ctx.response.body_json.success:?true,
#?},
POST?$[[env.PIZZA_SERVER]]/$[[collection]]/_doc
{"hello":?"world"}
#?{result:?"created"}
來替換掉本文最開頭示例中的 requests
部分。
上述示例提到了“完整語法”,在 Loadgen DSL 中,如下“簡短語法”的配置:
POST?$[[env.PIZZA_SERVER]]/$[[collection]]/_doc
{"hello":?"world"}
#?200?//?狀態(tài)碼是可選的
#?{result:?"created"}
等同于:
POST?$[[env.PIZZA_SERVER]]/$[[collection]]/_doc
{"hello":?"world"}
#?assert:?{
#???_ctx.response.status:?200,
#???_ctx.response.body_json:?{result:?"created"},
#?}
Tips:
對于
assert
字段,也可以通過元組表達式來使用簡短語法:POST?$[[env.PIZZA_SERVER]]/$[[collection]]/_doc
{"hello":?"world"}
#?assert:?(200,?{result:?"created"})
對于 Loadgen DSL 詳細的語法定義可以參考本文的附錄部分。
最后一些優(yōu)化
到目前為止,Loadgen DSL 幾乎可以替換掉 Loadgen YAML 配置的大部分內(nèi)容,除了 variables
和 runner
這樣的全局配置項。觀察一下現(xiàn)有的寫法,每個請求都是 METHOD URL
然后緊跟可選的請求體與斷言注釋,其實我們可以在 DSL 的最前面定義一些全局的選項:
#?variables:?[
#???{name:?"id",?type:?"sequence"},
#?],
#?runner:?{
#???assert_error:?true,
#???assert_invalid:?true,
#?},
PUT?$[[env.PIZZA_SERVER]]/test_create_document_$[[id]]
#?register:?[{
#???collection:?"_ctx.response.body_json.collection",
#?}],
#?assert:?{
#???_ctx.response.body_json:?{success:?true},
#?},
POST?$[[env.PIZZA_SERVER]]/$[[collection]]/_doc
{"hello":?"world"}
#?{result:?"created"}
上述示例就等價于本文最開頭給出的配置。
附錄:Loadgen DSL 語法定義
grammer????::=?brief?|?full
brief??????::=?status??object?EOF
full???????::=?fields?EOF
status?????::=?integer
expr???????::=?expr1?(infixop?expr1)*
expr1??????::=?literal
?????????????|?array
?????????????|?object
?????????????|?funcall
?????????????|?prefixop?expr1
?????????????|?'('?exprlist?')'
exprlist???::=?(expr?(','?expr)*?','?)?
object?????::=?'{'?fields?'}'
fields?????::=?(pair?(','?pair)*?','?)?
pair???????::=?path?':'?expr
path???????::=?key?('.'?key)*
key????????::=?name?|?string?|?integer
array??????::=?'['?exprlist?']'
funcall????::=?name?'('?exprlist?')'
literal????::=?null
?????????????|?boolean
?????????????|?integer
?????????????|?float
?????????????|?regex
?????????????|?string
ignore?????::=?whitespace
??????????????|?comment
??????????????/*?ws:?definition?*/
<?TOKENS?>
comment????::=?'//'?char*
name???????::=?ident?-?keyword
keyword????::=?'null'
?????????????|?'true'
?????????????|?'false'
?????????????|?'not'
?????????????|?'and'
?????????????|?'or'
ident??????::=?id_start?(id_start?|?'-'?|?digit)*
id_start???::=?[_a-zA-Z]
prefixop???::=?'-'
?????????????|?'>'
?????????????|?'<'
?????????????|?'>='
?????????????|?'<='
?????????????|?'=='
?????????????|?'not'
infixop????::=?'and'?|?'or'
null???????::=?'null'
boolean????::=?'true'?|?'false'
integer????::=?digit+
exponent???::=?('e'?|?'E')?('+'?|?'-')??integer
float??????::=?integer?exponent
?????????????|?integer?'.'?integer?exponent?
digit??????::=?[0-9]
regex??????::=?'/'?('\/'?|?char?-?'/')+?'/'
string?????::=?'"'?(escape?|?char?-?'"')*?'"'
?????????????|?"'"?(escape?|?char?-?"'")*?"'"
escape?????::=?'\b'
?????????????|?'\f'
?????????????|?'\n'
?????????????|?'\r'
?????????????|?'\t'
?????????????|?"\'"
?????????????|?'\"'
?????????????|?'\\'
?????????????|?'\/'
char???????::=?#x9
?????????????|?[#x20-#xD7FF]
?????????????|?[#xE000-#xFFFD]
?????????????|?[#x10000-#x10FFFF]
whitespace?::=?[#x9#xA#xD#x20]+
EOF????????::=?$
[2] : https://en.wikipedia.org/wiki/Domain-specific_language