写点什么

Maven 实战(五)——自动化 Web 应用集成测试

  • 2011-03-13
  • 本文字数:4677 字

    阅读完需:约 15 分钟

自动化集成测试的角色

本专栏的上一篇文章讲述了 Maven 与持续集成的一些关系及具体实践,我们都知道,自动化测试是持续集成必不可少的一部分,基本上,没有自动化测试的持续集成,都很难称之为真正的持续集成。我们希望持续集成能够尽早的暴露问题,但这远非配置一个 Hudson/Jenkins 服务器那么简单,只有真正用心编写了较为完整的测试用例,并一直维护它们,持续集成才能孜孜不倦地运行测试并第一时间报告问题。

自动化测试这个话题很大,本文不想争论测试先行还是后行,这里强调的是测试的自动化,并基于具体的技术(Maven、 JUnit、Jetty 等)来介绍一种切实可行的自动化 Web 应用集成测试方案。当然,自动化测试还包括单元测试、验收测试、性能测试等,在不同的场景下,它们都能为软件开发带来极大的价值。本文仅限于讨论集成测试,主要是因为笔者觉得这是一个非常重要却常常被忽略的实践。

基于 Maven 的一般流程

集成测试与单元测试最大的区别是它需要尽可能的测试整个功能及相关环境,对于测试 Web 应用而言,通常有这么几步:

  1. 启动 Web 容器
  2. 部署待测试 Web 应用
  3. 以 Web 客户端的角色运行测试用例
  4. 停止 Web 容器

启动 Web 容器可以有很多方式,例如你可以通过 Web 容器提供的 API 采用编程的方式来启动容器,但在 Maven 的环境下,配置插件显得更简单。如果你了解 Maven 的生命周期模型,就可能会想到,我们可以在 pre-integration-test 阶段启动容器,部署待测试应用,然后在 integration-test 阶段运行集成测试用例,最后在 post-integrate-test 阶段停止容器。也就是说,对于步骤 1,2 和 4 我们只须进行一些简单的配置,不必编写额外的代码。第 3 步是以黑盒的形式模拟客户端进行测试,需要注意的是,这里通常要求你理解一些基本的 HTTP 协议知识,例如服务端在什么情况下应该返回 HTTP 代码 200,什么时候应该返回 401 错误,以及所支持的 Content-Type 是什么等等。

至于测试用例该怎么写,除了需要用到一些用来访问 Web 以及解析响应详细的基础设施工具类之外,其他内容与单元测试大同小异,基本就是准备测试数据、访问服务、验证返回值等等。

一个简单的例子

谈了不少理论,现在该给个具体的例子了,譬如现在有个简单的 Servlet,它接受参数 a 和 b,做加法后返回二者之和,如果参数不完整,则返回 HTTP 400 错误,表示客户端的请求有问题。

复制代码
public class AddServlet
extends HttpServlet
{
@Override
protected void doGet( HttpServletRequest req, HttpServletResponse resp )
throws ServletException,
IOException
{
String a = req.getParameter( "a" );
String b = req.getParameter( "b" );
if ( a == null || b == null )
{
resp.setStatus( 400 );
return;
}
int result = Integer.parseInt( a ) + Integer.parseInt( b );
resp.setStatus( 200 );
resp.getWriter().print( result );
}
}

为了测试这段代码,我们需要一个 Web 容器,这里暂且使用 Jetty,因为目前来说它与 Maven 集成的相对最好。Jetty 提供了一个 Jetty Maven Plugin ,借助该插件,我们可以随时启动 Jetty 并部署 Maven 默认目录布局的 Web 项目,实现快速开发和测试。这里我们需要的是在 pre-integration-test 阶段启动 Jetty,在 post-integrate-test 阶段停止容器,对应的 POM 配置如下:

复制代码
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>7.3.0.v20110203</version>
<configuration>
<stopPort>9966</stopPort>
<stopKey>stop-jetty-for-it</stopKey>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<daemon>true</daemon>
</configuration>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>

XML 代码中第一处 configuration 是插件的全局配置,stopPort 和 stopKey 是该插件用来停止 Jetty 需要用到的 TCP 端口及消息关键字。接着是两个 executation 元素,第一个 executation 将 jetty-maven-plugin 的 run 目标绑定至 Maven 的 pre-integration-test 生命周期阶段,表示启动容器,第二个 executation 将 stop 目标绑定至 post-integration-test 生命周期阶段,表示停止容器。需要注意的是,启动 Jetty 时我们需要配置 deamon 为 true,让 Jetty 在后台运行以免阻塞 mvn 命令。此外,jetty-maven-plugin 的 run 目标也会自动部署当前 Web 项目。

准备好 Web 容器环境之后,我们接着看一下测试用例代码:

复制代码
public class AddServletIT
{
@Test
public void addWithParametersAndSucceed()
throws Exception
{
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet( "http://localhost:8080/add?a=1&b=2" );
HttpResponse response = httpclient.execute( httpGet );
Assert.assertEquals( 200, response.getStatusLine().getStatusCode() );
Assert.assertEquals( "3", EntityUtils.toString( response.getEntity() ) );
}
@Test
public void addWithoutParameterAndFail()
throws Exception
{
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet( "http://localhost:8080/add" );
HttpResponse response = httpclient.execute( httpGet );
Assert.assertEquals( 400, response.getStatusLine().getStatusCode() );
}
}

为了能够访问应用,这里用到了 HttpClient ,两个测试方法都初始化一个 HttpClient,然后创建 HttpGet 对象用来访问 Web 地址。第一个测试方法顾名思义用来测试成功的场景,它提供参数 a=1 和 b=2,执行请求后,验证返回结果成功(HTTP 状态码 200)并且内容为正确的值 3。第二个测试方法则用来测试失败的场景,当不提供参数的时候,服务器应该返回一个 HTTP 400 错误。该测试类其实是相当粗糙的,例如有硬编码的服务器 URL,这里的目的仅仅是通过尽可能简单的代码来展现一个自动化集成测试的实现过程。

上述代码中,测试类的名称为 AddServletIT,而不是一般的 **Test,IT 表示 IntegrationTest,这么命名是为了和单元测试区分开来,这样,鉴于 Maven 默认的测试命名约定,Maven 在 test 生命周期阶段执行单元测试时,就不会涉及集成测试。现在,我们希望 Maven 在 integration-test 阶段执行所有以 IT 结尾命名的测试类,配置 Maven Surefire Plugin 如下:

复制代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.7.2</version>
<executions>
<execution>
<id>run-integration-test</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<includes>
<include>**/*IT.java</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>

通过命名规则和插件配置,我们优雅地分离了单元测试和集成测试,而且我们知道在 integration-test 阶段,Jetty 容器已经启动完成了。如果你在使用 TestNG,那你还可以使用其测试组的特性来分离单元测试和集成测试,Maven Surefire Plugin 对其也有着很好的支持

一切就绪了,运行 mvn clean install 以自动运行集成测试,我们可以看到如下的输出片段:

复制代码
[INFO] --- jetty-maven-plugin:7.3.0.v20110203:run (start-jetty) @ webapp-demo ---
[INFO] Configuring Jetty for project: webapp-demo
[INFO] webAppSourceDirectory /home/juven/git_juven/webapp-demo/src/main/webapp does not exist. Defaulting to /home/juven/git_juven/webapp-demo/src/main/webapp
[INFO] Reload Mechanic: automatic
[INFO] Classes = /home/juven/git_juven/webapp-demo/target/classes
[INFO] Context path = /
...
2011-03-06 14:55:15.676:INFO::Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
[INFO]
[INFO] --- maven-surefire-plugin:2.7.2:test (run-integration-test) @ webapp-demo ---
[INFO] Surefire report directory: /home/juven/git_juven/webapp-demo/target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.juvenxu.webapp.demo.AddServletIT
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.344 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- jetty-maven-plugin:7.3.0.v20110203:stop (stop-jetty) @ webapp-demo ---

可以看到 jetty-maven-plugin:7.3.0.v20110203:run 对应了 start-jetty,maven-surefire- plugin:2.7.2:test 对应了 run-integration-test,jetty-maven- plugin:7.3.0.v20110203:stop 对应了 stop-jetty,与我们的配置和期望完全一致。此外两个测试也都成功了!

小结

相对于单元测试来说,集成测试更难编写,因为需要准备更多的环境,本文只涉及了Web 容器最简单的情形,实际的开发情形中,你可能会遇到数据库,第三方Web 服务,更复杂的容器配置和数据格式等等,这都使得编写集成测试变得让人畏惧。然而反过来考虑,无论如何你都需要测试,虽然这个自动化过程的投入很大,但收益往往更加客观,这不仅仅是手动测试时间的节省,更重要的是,你无法保证手动测试能被高频率的反复执行,也就无法保证问题能被尽早暴露。

对于Web 应用来说,编写集成测试有助于你考虑和设计Web 应用对外暴露的接口,这种“开发实现”/“测试审察”之间的角色转换往往能造就更清晰的设计,这也是编写测试最大的好处之一。

Maven 用户能够得益于 Maven 的插件系统,不仅能节省大量的编码,还能得到稳定的工具,Jetty Maven Plugin 和 Maven Surefire Plugin 就是最好的例子。本文只涉及了 Jetty,如果读者的环境是 Tomcat 或者 JBoss 等其他容器,则需要查阅相关的文档以得到具体的实现细节,你可能对 Tomcat Maven Plugin JBoss Maven Plugin 、或者 Cargo Maven2 Plugin 感兴趣。

关于作者

许晓斌(Juven Xu),国内社区公认的 Maven 技术专家、Maven 中文用户组创始人、Maven 技术的先驱和积极推动者。对 Maven 有深刻的认识,实战经验丰富,不仅撰写了大量关于 Maven 的技术文章,而且还翻译了开源书籍《Maven 权威指南》,对 Maven 技术在国内的普及和发展做出了很大的贡献。就职于 Maven 之父的公司,负责维护 Maven 中央仓库,是 Maven 仓库管理器 Nexus(著名开源软件)的核心开发者之一,曾多次受邀到淘宝等大型企业开展 Maven 方面的培训。此外,他还是开源技术的积极倡导者和推动者,擅长 Java 开发和敏捷开发实践。他的个人网站是: http://www.juvenxu.com

2011-03-13 00:1518417

评论

发布
暂无评论
发现更多内容

mybatis中如何防止sql注入和传参

网络安全学海

黑客 网络安全 信息安全 渗透测试 安全漏洞

Redis AOF 持久化详解

程序员历小冰

redis aof 28天写作 12月日更

百分点认知智能实验室:基于NL2SQL的问答技术和实践

百分点科技技术团队

关于Stream转Map的Duplicate key异常处理

一盐难进

java基础

Flink CDC 系列 - 同步 MySQL 分库分表,构建 Iceberg 实时数据湖

Apache Flink

大数据 flink 编程 后端 实时计算

Golang中的协程是干什么用的?

liuzhen007

28天写作 12月日更

一指阁:用宜搭实现全面数字化管理,助力企业打开十亿市场新空间

一只大光圈

阿里巴巴 低代码 数字化 钉钉宜搭

读《思辨与立场》-09决策的艺术10掌控你的非理性倾向

wood

28天写作 批判性思维 思辨与立场

Yes And

将军-技术演讲力教练

学习基本黑客技术

喀拉峻

网络安全 安全 渗透测试

案例应用 l 机器视觉"OCR识别"技术,高效解决医疗药盒字符检测难题

矩视智能

机器视觉 工业视觉 工业机器视觉 工业界机器学习

农信机构如何推动供应链金融?

CECBC

盘点 2021|日更一年的收获与改变

石云升

28天写作 12月日更 盘点2021

技术进步和个人幸福

mtfelix

28天写作

优酷弱网平台落地实践

阿里巴巴终端技术

弱网 弱网环境高可用

阿里云混合云重磅亮相中国信通院2021混合云大会

架构实战营第 4 期 -- 模块四作业

烈火干柴烛灭田边残月

架构实战营

微服务架构下请求调用失败的解决方案

JavaEdge

12月日更

5G很美丽,也要解好题:为什么今天必须关注5G-Advanced?

脑极体

区块链将在元宇宙旅游中发挥哪些价值?

CECBC

Hoo虎符研究院 | 币海寻珠——2021年12月月底NFT榜单(2021.11.22 - 2021.12.21)

区块链前沿News

NFT 虎符交易所 虎符研究院 Hoo交易所

十大排序算法思想与Python实现

宇宙之一粟

Python 排序算法 12月日更

圣诞节

圣迪

圣诞节 圣诞老人 平安夜 尼古拉斯 长筒袜

Java 中的 xx ≠ null 是什么新语法?

CRMEB

数字化石油的开采利器:智能图像识别系统

百度大脑

人工智能

LabVIEW灰度图像操作与运算(基础篇—2)

不脱发的程序猿

机器视觉 图像处理 LabVIEW 灰度图像操作与运算

如何利用区块链提高供应链金融数字化水平?

CECBC

面试官:说说32位和64位

喵叔

28天写作 12月日更

Agora Flat:在线教室的开源初体验

声网

人工智能 开源 flat

HarmonyOS(鸿蒙)——单击事件

李子捌

28天写作 21天挑战 12月日更

元宇宙如何改写人类社会生活

CECBC

Maven实战(五)——自动化Web应用集成测试_Java_许晓斌_InfoQ精选文章