在本文中我们不会详细罗列整个 Scenarios 文件的语法结构, 而是通过对 Todo 服务进行自动化测试来介绍 Scenarios 文件的用法.
5.3 为 Todo 服务实现自动测试
打开 src/main/resources/e2e/scenarios.yml 文件, 清除其中的内容, 添加针对 TODO 服务的测试脚本. Todo 服务足够简单, 一个场景足够涵盖基本测试, 我们首先加入场景声明:
Scenario(Main):
description: Test TODO service
5.3.1 第一个交互: 添加 Todo 项目
接下来加入第一个交互:
Scenario(Main):
description: Test TODO service
interactions:
- description: Add one todo item
request:
method: POST
url: /todos
params:
desc: Task A
response:
json:
result:
- exists: true
分解一下该交互的定义. 首先是请求:
request:
method: POST
url: /todos
params:
desc: Task A
- description: Fetch todo item added in last interaction
request:
method: GET
url: /todos/${last:result}
response:
json:
desc: Task A
这个交互定义有个地方值得注意: url: /todos/${last:result}, 这个的意思是取上一个交互对象的 result 引用的值填充到 /todos/ 后面, 也就是说 url 最后会是 /todos/{id}, 而 {id} 是上次添加 Todo 项生成的 id.
对于 response 的定义则是检测是否 JSON 数据中的 desc 为 Task A.
回到浏览器, 按 F5 刷新:
5.3.3 获得 Todo 列表
- description: Fetch todo item list
request:
method: GET
url: /todos
response:
json:
size: 1
0:
desc: Task A
这个交互定义中比较有趣的是响应的验证定义. 其含义是 json 数据是一个数组, 其中有一个元素, 第一个元素的 desc 为 Task A
刷新浏览器得到:
5.3.4 查询 Todo 列表
- description: Search todo item list
request:
method: GET
url: /todos
params:
q: A
response:
json:
size: 1
0:
desc: Task A
这个和前面的交互 [5.3.3 获得 Todo 列表] 非常接近, 唯一不同的地方是 GET 请求多了请求参数: q=A. 表示查询含有 A 字母的 Todo 项.
再次刷新浏览器:
Oops, 怎么最后的交互没有通过. 回到后台, 发现这样的错误:
[FAIL] Search todo item list
error running scenario: Main
org.osgl.exception.UnexpectedException: Cannot verify value[0] with test [1]
at org.osgl.util.E.unexpected(E.java:179)
at act.e2e.Scenario.verifyValue(Scenario.java:569)
at act.e2e.Scenario.verifyList(Scenario.java:509)
at act.e2e.Scenario.verifyBody(Scenario.java:440)
at act.e2e.Scenario.verify(Scenario.java:406)
这应该是希望返回数组有一个元素, 但实际返回数组没有元素. 仔细检查一下, 应该是在 A 前面加上 % 才行, 更改我们的 scenarios.yml 文件, 将 q: A 改成 q: %A, 之后再刷新浏览器:
luog@luog-X510UQR:/tmp/2/todo-service$ mvn -q clean compile act:e2e
Listening for transport dt_socket at address: 5005
___ _ _ _ __ _ _ ___ _ _
| / \ | \ / \ __ (_ |_ |_) \ / | / |_
| \_/ |_/ \_/ __) |_ | \ \/ _|_ \_ |_
powered by ActFramework r1.8.8-RC8-7ed4
version: v1.0-SNAPSHOT-180530_2132
scan pkg:
base dir: /tmp/2/todo-service
pid: 13566
profile: e2e
mode: DEV
zen: Special cases aren't special enough to break the rules.
Although practicality beats purity.
2018-05-30 21:32:21,043 INFO a.Act@[main] - loading application(s) ...
2018-05-30 21:32:21,057 INFO a.a.App@[main] - App starting ....
2018-05-30 21:32:21,236 WARN a.h.b.ResourceGetter@[main] - URL base not exists: META-INF/resources/webjars
2018-05-30 21:32:21,255 WARN a.a.DbServiceManager@[main] - DB configuration not found. Will try to init default service with the sole db plugin: act.db.eclipselink.EclipseLinkPlugin@31e90355
2018-05-30 21:32:23,089 WARN a.m.MailerConfig@[main] - smtp host configuration not found, will use mock smtp to send email
2018-05-30 21:32:23,529 WARN a.Act@[jobs-thread-2] - No data source user configuration specified. Will use the default 'sa' user
2018-05-30 21:32:23,530 WARN a.Act@[jobs-thread-2] - No database URL configuration specified. Will use the default h2 inmemory test database
2018-05-30 21:32:23,530 WARN a.Act@[jobs-thread-2] - JDBC driver not configured, system automatically set to: org.h2.Driver
2018-05-30 21:32:23,998 INFO o.xnio@[main] - XNIO version 3.3.8.Final
2018-05-30 21:32:24,041 INFO o.x.nio@[main] - XNIO NIO Implementation Version 3.3.8.Final
2018-05-30 21:32:24,089 INFO a.a.App@[jobs-thread-2] - App[todo-service] loaded in 3032ms
2018-05-30 21:32:24,106 INFO a.a.ApiManager@[jobs-thread-6] - start compiling API book
2018-05-30 21:32:24,272 INFO a.a.ApiManager@[jobs-thread-6] - API book compiled
2018-05-30 21:32:24,311 INFO a.Act@[main] - network client hooked on port: 5460
2018-05-30 21:32:24,313 INFO a.Act@[main] - CLI server started on port: 5461
2018-05-30 21:32:24,317 INFO a.Act@[main] - app is ready at: http://192.168.1.2:5460
2018-05-30 21:32:24,318 INFO a.Act@[main] - it takes 9396ms to start the app
Start running E2E test scenarios
================================================================================
MAIN
Test TODO service
--------------------------------------------------------------------------------
[EL Info]: 2018-05-30 21:32:25.399--ServerSession(1256982899)--EclipseLink, version: Eclipse Persistence Services - 2.7.1.v20171221-bd47e8f
[EL Info]: connection: 2018-05-30 21:32:25.516--ServerSession(1256982899)--/file:/tmp/2/todo-service/./_default login successful
[PASS] Add one todo item
[PASS] Fetch todo item added in last interaction
[PASS] Fetch todo item list
[PASS] Search todo item list
--------------------------------------------------------------------------------
It takes 0s to run this scenario.
这里还需要一些改进, 方便 CI 工具更容易判断测试是否通过. 老码农会在以后的版本中持续改进对端到端自动测试的支持.
老码农在上一篇博客 给出了如何从头开始创建一个 自带自动化测试工具的 RESTful 服务项目的例子. 今天我们在这个简单例子上做延伸, 把这个例子改写为一个简单的 TODO Task 应用. 该应用会提供以下服务端口:
1. 创建项目
下面开始创建初始项目:
下面我们将项目用 Intellij IDEA 打开. (推荐使用 IDEA 开发 Act 应用, 社区版足够使用了)
2. 加入数据库访问插件依赖
可以删除掉项目创建的
Service.java
文件. 然后在pom.xml
中加入一下依赖:act-eclipselink
使用 EclipseLink 提供数据库访问服务. 也可以换为act-hibernate
, 对我们这个 todo-service 的开发没有任何变化.3. 加入 Todo 服务相关类
现在可以在项目中创建我们的 Model 类 - Todo:
注意, 这里实现了
SimpleBean
框架会自动为Todo
创建 Getter 和 Setter 方法. 在Todo
之外的地方调用todo.desc = "abc"
会被自动更换为todo.setDesc("abc")
, 而String s = todo.desc
则会被自动更换为String s = todo.getDesc()
.下面创建 Todo 的服务类 TodoService:
4. 启动并试用服务
好了, 开发工作搞定. 现在运行起来试试. 有两种方法运行程序.
mvn
命令在控制台运行:选择任何一种方式, 把应用跑起来之后, 我们用 httpie 来试试我们的 Todo 服务:
4.1 创建一个 todo 项
4.2 获取 todo 列表:
4.3 获取 id 为 1 的 todo 项
4.4 删除 id 为 1 的 todo 项, 并验证
到此基本上我们的实现部分就完成了, 同时也手工做了测试. 看看代码统计:
所有 Java 代码加起来也就 58 行, 虽说是一个非常粗糙的 Todo 服务, 也算是麻雀虽小五脏俱全了.
5. 自动测试的实现
下面开始进入肉戏了. 我们刚刚使用了 httpie 对服务进行了测试, 貌似也很简单, 可这毕竟需要人工介入啊. 没听葛先生说过二十一世纪人工最贵吗? 怎么能把这么贵的人工浪费在重复性的工作上面. 更重要的问题是人工在这种重复性劳作上远远不如机器可靠, 如果没有自动化测试的保障, 即便是大牛也不敢随便对代码动刀子搞搞重构之类的高级手术.
那自动化测试怎么搞, 这是一个问题. 老码农也思考了很多年. 最初的想法是复制 Spring 的招数, 搞一个
ActRunner
之类的东西让开发人员能在 JUnit 框架下跑对 Act 应用的测试. 可当老码农看到 3.4.5. Meta-Annotation Support for Testing 一节的时候已经被吓傻了, 仅仅是为了支持测试可能会用到的注解就已经是这番模样了:这完全和 Act 的核心理念背道而驰啊. 欲破珍珑棋局, 必须跳出棋盘, 自动测试也不是一定要在 JUnit 框架下进行. 多年寻寻觅觅找不到满意的方案, 这思路一开, act-e2e 插件便横空出世.
5.1 act-e2e 简介
act-e2e 是老码农为 Act 应用开发提供的自动化测试插件, 其设计目的主要有一下几点:
5.2 Scenarios 文件
定义 Scenarios.yml 文件内容是使用 act-e2e 进行自动化测试的核心活动. 这个文件的结构如下:
在本文中我们不会详细罗列整个 Scenarios 文件的语法结构, 而是通过对 Todo 服务进行自动化测试来介绍 Scenarios 文件的用法.
5.3 为 Todo 服务实现自动测试
打开
src/main/resources/e2e/scenarios.yml
文件, 清除其中的内容, 添加针对 TODO 服务的测试脚本. Todo 服务足够简单, 一个场景足够涵盖基本测试, 我们首先加入场景声明:5.3.1 第一个交互: 添加 Todo 项目
接下来加入第一个交互:
分解一下该交互的定义. 首先是请求:
请求的元素主要为:
本例中响应的定义为:
这个定义的意思是, 这个响应应该是一个
{"result": ...}
JSON 结构. 该定义来自 Todo 服务的下面的服务端口:从代码看来, 返回的应该是一个整数类型的值. 但因为我们前面定义了整个控制器都是
@JsonView
, 也就是任何返回都应该是合法的 JSON 结构. 对于无结构的值, 我们使用result
来包裹返回值. 因此, 我们的响应定义中有result: -exists: true
这样的验证.加入第一个交互之后我们就可以试试 e2e 了, 打开浏览器, 导航到 localhost:5460/~/e2e, 会看到定义的测试以及运行情况:
5.3.2 查询新创建的记录
这个交互定义有个地方值得注意:
url: /todos/${last:result}
, 这个的意思是取上一个交互对象的result
引用的值填充到/todos/
后面, 也就是说 url 最后会是/todos/{id}
, 而{id}
是上次添加 Todo 项生成的 id.对于 response 的定义则是检测是否 JSON 数据中的
desc
为Task A
.回到浏览器, 按 F5 刷新:
5.3.3 获得 Todo 列表
这个交互定义中比较有趣的是响应的验证定义. 其含义是 json 数据是一个数组, 其中有一个元素, 第一个元素的
desc
为Task A
刷新浏览器得到:
5.3.4 查询 Todo 列表
这个和前面的交互 [5.3.3 获得 Todo 列表] 非常接近, 唯一不同的地方是 GET 请求多了请求参数:
q=A
. 表示查询含有 A 字母的 Todo 项.再次刷新浏览器:
Oops, 怎么最后的交互没有通过. 回到后台, 发现这样的错误:
这应该是希望返回数组有一个元素, 但实际返回数组没有元素. 仔细检查一下, 应该是在
A
前面加上%
才行, 更改我们的scenarios.yml
文件, 将q: A
改成q: %A
, 之后再刷新浏览器:貌似错误更加严重了, 原来
%
是 yaml 的保留字符, 需要用引号括起来, 即q: %A
变为q: '%A'
. 最后终于全部搞定了:5.4 在 CI 中集成 e2e 测试
我们上面的过程都使用了浏览器访问
/~/e2e
来完成测试. 这个对于开发调试 sceanrios.yml 测试脚步非常方便. 但是对于 CI 集成自动测试过程就不友好了. 不用担心, Act 的 maven 插件可以帮忙解决这个问题:获得的结果是:
这里还需要一些改进, 方便 CI 工具更容易判断测试是否通过. 老码农会在以后的版本中持续改进对端到端自动测试的支持.
另外在操作过程中有可能出现一些异常现象, 需要重启动应用. 这个问题老码农已经提交 issue 报告了. 将会在以后的版本中修复.
本文讲述的项目代码在 gitee 上有完整版本, 有兴趣可以参考