测试元件与上下文链是 Groot 最核心的两个概念。
测试元件
测试元件是能根据其父上下文链独立执行的一个逻辑执行单元,每个测试元件都具有唯一的关键字,比如 HTTP 协议请求的关键字为 http
,条件控制器的关键字为 if
。我们可以先将测试元件理解为 JMeter 的 Controller/Sampler、SoapUI 或 HttpRunner 的 TestStep,不过稍微有些不同。
测试元件作为整个工具的核心,通常由 5 部分构成:基本属性、配置上下文、前置处理器、关键字属性和后置处理器。
- 基本属性:名称、描述、是否禁用等所有测试元件通用的属性。
- 配置上下文:从 HTTP 请求中抽离出 HTTP 配置是非常有必要的,比如 baseUrl、proxy、通用 header 等一些常见的配置,我们在全局配置或环境配置中设置即可,没有必要每次请求时都再写一遍。每个测试元件都可以有自己的特殊配置项,一个测试步骤可以同时拥有多个不同类型的配置上下文,比如变量配置、HTTP 配置、Extract 配置。
- 前置处理器:测试元件执行前,会执行所有的前置处理器,在这里我们可以进行一些预处理。
- 关键字属性:关键字属性由各个测试元件提供,比如 HTTP 请求的关键字为
http
,对应的属性值为HttpRequest
实例,该实例中包含了发送 Http 请求所需的信息。 - 后置处理器:后置处理器可以在测试元件执行后进行一些后处理操作。后置处理器有两个特殊的子接口:提取器接口和断言接口。
每个测试元件实例都由基本属性、配置上下文、前置处理器、关键字属性以及后置处理器构成,下面演示了测试元件的标准结构。
yaml
name: 标准结构测试用例
steps:
- name: 步骤名称
config: # 配置上下文
variables: # 变量配置上下文
username: tomcat
password: tacmot
http: # HttpSampler 配置上下文
any:
base_url: https://www.baidu.com
setup: # 前置处理器
before: # before 前置
- hooks: # Hooks 前置处理器
- ${sleep(10)}
group: "简单分组" # 关键字属性
teardown: # 后置处理器
- hooks: # Hooks 后置处理器
- ${do(xxx)}
- ${print(yyy)}
- validate$equalTo:
check: $.code
expect: 200
- extract$jsonpath:
refName: code
expression: $.msg
default: 'OK'
- hooks:
- ${doOther(zzz)}
name: 标准结构测试用例
steps:
- name: 步骤名称
config: # 配置上下文
variables: # 变量配置上下文
username: tomcat
password: tacmot
http: # HttpSampler 配置上下文
any:
base_url: https://www.baidu.com
setup: # 前置处理器
before: # before 前置
- hooks: # Hooks 前置处理器
- ${sleep(10)}
group: "简单分组" # 关键字属性
teardown: # 后置处理器
- hooks: # Hooks 后置处理器
- ${do(xxx)}
- ${print(yyy)}
- validate$equalTo:
check: $.code
expect: 200
- extract$jsonpath:
refName: code
expression: $.msg
default: 'OK'
- hooks:
- ${doOther(zzz)}
上下文链
每个测试元件实例都会继承其父上下文链中的配置上下文,子元件实例会覆盖父元件的同名配置。基于配置上下文的继承关系,我们就实现了配置继承与隔离。比如每个测试用例都会继承全局变量和环境变量,同时每个测试用例的用例变量又相互独立。
以变量配置上下文为例,假设现在有一个测试用例 TCA,用例结构如下:
yml
name: TCA
variables:
lang: java
steps:
- name: 执行三次
repeat: 3
steps:
- name: GET 请求
variables:
lang: python
http:
url: /get/${lang}
method: GET
name: TCA
variables:
lang: java
steps:
- name: 执行三次
repeat: 3
steps:
- name: GET 请求
variables:
lang: python
http:
url: /get/${lang}
method: GET
那么对于 GET 请求这个步骤来说,其上下文链表现为:
text
全局上下文 - 环境上下文 - 用例组上下文 - 用例上下文 - Repeat 控制器上下文 - HTTP 取样器上下文
全局上下文 - 环境上下文 - 用例组上下文 - 用例上下文 - Repeat 控制器上下文 - HTTP 取样器上下文
由于配置上下文是继承关系,所以 url 中变量 lang 的值是 python
,而不是 java
。
每一级上下文就是一级作用域,当前层级的上下文仅作用于当前层级及继承链上的子级。继承机制有两点好处:
- 配置隔离:当前层级定义的变量不会污染任何父级的变量,将变量声明控制在最小作用域内,防止变量数量膨胀和变量命名冲突,也方便问题排查(同级的步骤都可以定义自己的局部变量 count,用于处理内部事务,无需在用例级别上定义)
- 配置继承:针对特定用例或特定步骤使用不同的配置,比如只针对某个请求添加 HTTP 代理配置但继承其他 HTTP 配置,或是默认提取作用域为当前层级时,对某个提取器设置提取作用域为用例级别。
简写语法
有时我们的用例比较简单,或者我们对配置语法结构已经熟悉,我们希望尽可能省略一些明确语义的书写。
简写语法实际上是一些语法糖,最终执行前都会转换成标准结构,下面列出了一些常见的简写语法。
yaml
# *** setup before Hooks 简写语法 ***
setupBeforeHooks:
- ${xxx}
# 等价于
setupBefore:
- hooks: ${xxx}
# 等价于
setup: # 前置处理器(处理器类型)
before: # before 前置(前置处理器的执行时机)
- hooks: # Hooks 前置处理器(一个具体的处理器)
hooks: # Hooks 前置处理器的属性
- ${xxx}
# *** 变量配置上下文简写语法 ***
variables:
username: tomcat
# 等价于
config:
variables:
username: tomcat
# *** 提取器简写语法 ***
extract:
- jsonpath: [code, $.data.code, { default: 200, scope: session }]
# 等价于
extract:
- jsonpath:
refName: code # 变量名称
expression: $.data.code # 提取表达式
default: 200 # 提取失败时的默认值
scope: session # 变量提取作用域,提取到 session 作用域中
# 等价于
teardown:
- extract$jsonpath:
refName: code
expression: $.data.code
default: 200
scope: session
# *** 断言简写语法 ***
validate:
- equalTo: ['iii', 'III']
# 在第三个参数中进行额外的配置
- equalTo: ['iii', 'III', { ignoreCase: true }]
# 等价于
teardown:
- validate$equalTo:
check: "iii"
expect: "III"
ignoreCase: true
# *** setup before Hooks 简写语法 ***
setupBeforeHooks:
- ${xxx}
# 等价于
setupBefore:
- hooks: ${xxx}
# 等价于
setup: # 前置处理器(处理器类型)
before: # before 前置(前置处理器的执行时机)
- hooks: # Hooks 前置处理器(一个具体的处理器)
hooks: # Hooks 前置处理器的属性
- ${xxx}
# *** 变量配置上下文简写语法 ***
variables:
username: tomcat
# 等价于
config:
variables:
username: tomcat
# *** 提取器简写语法 ***
extract:
- jsonpath: [code, $.data.code, { default: 200, scope: session }]
# 等价于
extract:
- jsonpath:
refName: code # 变量名称
expression: $.data.code # 提取表达式
default: 200 # 提取失败时的默认值
scope: session # 变量提取作用域,提取到 session 作用域中
# 等价于
teardown:
- extract$jsonpath:
refName: code
expression: $.data.code
default: 200
scope: session
# *** 断言简写语法 ***
validate:
- equalTo: ['iii', 'III']
# 在第三个参数中进行额外的配置
- equalTo: ['iii', 'III', { ignoreCase: true }]
# 等价于
teardown:
- validate$equalTo:
check: "iii"
expect: "III"
ignoreCase: true