引言

上篇文章中,我们介绍了如何用 Loadgen 来简化 HTTP API 的集成测试。在实际使用中会发现,编写测试时最令人“头疼”的部分是设计测试的输入和校验程序的输出,而针对后者 Loadgen 提供了丰富的条件测试 [1] 来对响应进行断言。

回顾上篇文章的示例:

# loadgen.yml
variables:
- name: id
type: sequence
runner:
assert_error: true
assert_invalid: true
requests:
- request:
method: PUT
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

上述配置中各请求的断言只有一条,但如果我们的检验逻辑更加复杂,需要组合多重测试条件,比如我们想尽可能多的检验响应体中的字段来提高测试的可靠性,那么断言的部分将会迅速膨胀,可读性也会随之下降。

例如,针对如下响应:

{
"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

不难发现,上面的配置看起来与原始的响应结构有很大的差别,写起来十分繁琐,看起来也不直观,为了解决这一问题,我们为 Loadgen 的 YAML 配置设计了一种 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,
},
}

这样是不是“清爽”了许多?而且,有没有发现这个语法和 JSON 很像?没错,Loadgen DSL 完全兼容 JSON 语法!也就是说,可以直接把响应体复制下来,然后在其基础上进行修改:

{
// 用 <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 中字段的引号是可以省略的,同时它也支持 notandor 的逻辑组合:

{
took: >0 and <50,
relation: "eq" or "gt" or "ge",
hits.total.value: not 0,
}

而对于较复杂的条件测试,比如 prefix/contains/in 等,可以通过函数调用语法来实现:

{
blog.title: prefix("INFINI"),
blog.tag: contains("Loadgen"),
blog.status: in(["reviewed", "archived"]),
}

进一步的简化

以上我们展示了 Loadgen DSL 是如何简化断言配置的,回想到上一章开头所说的,HTTP API 测试中主要就是不断发起请求然后校验其响应,那我们何不在 DSL 中将请求一起解析了呢?

于是,在现有的语法上稍作改进,我们便可以通过如下示例:

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 // 状态码是可选的
# {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 详细的语法定义可以参考本文的附录部分

最后一些优化

到目前为止,Loadgen DSL 几乎可以替换掉 Loadgen YAML 配置的大部分内容,除了 variablesrunner 这样的全局配置项。观察一下现有的写法,每个请求都是 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 语法定义

以下是 Loadgen DSL 的 BNF[3] 语法定义:

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 ::= $

  1. https://www.infinilabs.com/docs/latest/gateway/references/flow/#条件类型

  2. https://en.wikipedia.org/wiki/Domain-specific_language

  3. https://en.wikipedia.org/wiki/Backus–Naur_form

借助 DSL 来简化 Loadgen 配置的更多相关文章

  1. OEL上使用yum install oracle-validated 简化主机配置工作

    环境:OEL 5.7 + Oracle 10.2.0.5 RAC 如果你正在用OEL(Oracle Enterprise Linux)系统部署Oracle,那么可以使用yum安装oracle-vali ...

  2. springboot学习笔记-4 整合Druid数据源和使用@Cache简化redis配置

    一.整合Druid数据源 Druid是一个关系型数据库连接池,是阿里巴巴的一个开源项目,Druid在监控,可扩展性,稳定性和性能方面具有比较明显的优势.通过Druid提供的监控功能,可以实时观察数据库 ...

  3. ApplicationContextRunner如何简化自动配置测试

    1. 概览 众所周知,自动配置是Spring Boot的关键功能之一, 但测试自动配置可能会很棘手. 在以下部分中,我们将展示ApplicationContextRunner如何简化自动配置测试. 2 ...

  4. 通用、封装、简化 webpack 配置

    通用.封装.简化 webpack 配置 现在,基本上前端的项目打包都会用上 webpack,因为 webpack 提供了无与伦比强大的功能和生态.但在创建一个项目的时候,总是免不了要配置 webpac ...

  5. 标签简化Spring-MVC配置

    新填入@RequestMapping标签 和@org.springframework.stereotype.Controller标签 这样做就是通过标签来简化之前,对HandlerMapping的配置 ...

  6. 使用MapperScannerConfigurer简化MyBatis配置

    MyBatis的一大亮点就是可以不用DAO的实现类.如果没有实现类,Spring如何为Service注入DAO的实例呢?MyBatis-Spring提供了一个MapperFactoryBean,可以将 ...

  7. spring用注解简化bean配置

    组件扫描: <context:component-scan base-package="com"/> 容器启动后如果发现配置文件有上面的标签会自动扫描对应的包及子包,如 ...

  8. 在Android用ZXing.jar识别二维码的精简版(简化了配置和代码)

            近期公司做了一款OTP令牌激活的产品,因为之前激活手机令牌须要输入非常多的激活信息才干进行激活. 经过一段使用后,发现易用性不是非常强,考虑假设添加二维码的的扫码功能岂不是大大添加了易 ...

  9. isopod dsl 框架管理kubernetes 配置

    isopod 是一个包含了丰富能力的dsl 框架我们可以不用编写yaml 文件来进行k8s 管理 说明 语法类似python,目前移植内置了一些不错的功能kube 方法 vault 集成,helm 集 ...

  10. 简化SpringMVC配置

    映射器处理器和适配器是可以省略的 为什么可以省略?因为有默认配置 SpringMVC的默认配置

随机推荐

  1. 力扣71(java)-简化路径(中等)

    题目: 给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/' 开头),请你将其转化为更加简洁的规范路径. 在 Unix 风格的文件系统中,一个点(.)表示当前目 ...

  2. DataV 3D 平面地图 2.0 焕新上线

    ​简介:DataV3月,3D平面地图2.0现已上线~ 3D 平面地图 2.0 现已上线~ 让我们来看看更新了哪些功能吧! 01 交互升级,省市区自由下钻 自带行政区域数据,无需配置: ​ 甚至,可以通 ...

  3. [Trading] 专业交易: 专业交易员和散户交易员的不同, 什么是专业交易员

    专业交易员可能用的是公司的钱或者自己的钱 有基本工资支持,散户用的是自己的钱 没有人提供工资来做交易. 目标不同,专业交易员的目的是增长投资账户和获得奖金,散户大部分是为了提取盈利收入而无法增长投资账 ...

  4. [FAQ] Laravel 验证未通过 Route [login] not defined 处理

    一种方式是在路由中定义一个name为 login 的请求. Route::get('xxx', [XxxController::class, 'x'])->name('login'); 第二种方 ...

  5. [Go] 浅谈 gorm 执行 AutoMigrate 的两种时机

    第一种就是直接在操作 model 的逻辑中,执行 db.AutoMigrate,模型没有更新时不会有 schema 相关的 sql 被执行. 第二种就是单独定义一个属于 main 包的 go 文件,专 ...

  6. IIncrementalGenerator 获取引用程序集的所有类型

    本文告诉大家如何在使用 IIncrementalGenerator 进行增量的 Source Generator 生成代码时,如何获取到当前正在分析的程序集所引用的所有的程序集,以及引用的程序集里面的 ...

  7. 1.prometheus源码安装

    一.prometheus安装前准备 prometheus官网:https://prometheus.io/ grafana官网:https://grafana.com/ 资源下载: # 1.资源下载 ...

  8. vue关于this.$refs.tabs.refreshs()刷新组件,缓存

    当更改了用户信息后,需要刷新页面或者组件. 1.当前组件刷新.定义一个请求用户信息的方法,在需要时调用: sessionStorage.setItem('userInfo',JSON.stringif ...

  9. 01、Windows 排查

    Windows 分析排查 分析排查是指对 Windows 系统中的文件.进程.系统信息.日志记录等进行检测,挖掘 Windows 系统中是否具有异常情况 1.开机启动项检查 一般情况下,各种木马.病毒 ...

  10. Vue-Plugin-HiPrint

    Vue-Plugin-HiPrint 是一个Vue.js的插件,旨在提供一个简单而强大的打印解决方案.通过 Vue-Plugin-HiPrint,您可以轻松地在Vue.js应用程序中实现高度定制的打印 ...