写点什么

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:1518597

评论

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

CRM是什么,你有认真了解过CRM吗?

低代码小观

企业管理 CRM

CRM系统如何帮助你的业务?

低代码小观

企业管理 CRM

「AI时代下的融合通信技术」专场等你赴约|QCon 2021

网易云信

人工智能 音视频

融云 IM+RTC+X「全」通信解决方案,赋能全平台、多语言开发者

融云 RongCloud

通信 技术栈 语言 & 开发

开发实践 —— 教你使用Z-ONE Studio Lite + Digital car创建智能场景

SOA开发者平台

软件定义汽车 流程图绘制工具 数字汽车 汽车SOA

开发实践 —— 教你使用Z-ONE Studio Lite + Digital car创建智能场景

SOA开发者

P9都爱不释手!Alibaba9月最新出品776页JDK源码+并发核心原理解析小册

Java 架构 面试 程序人生 编程语言

StartDT Hackathon | 泛元数据:让数据成为资产

奇点云

译文 | MySQL 8.0 密码管理策略(一)

RadonDB

MySQL 数据库

Automotive SPICE® 汽车功能安全

SOA开发者

必须得会的汽车ECU研发基础--ECU软件架构概览3

SOA开发者

SOA架构概述

SOA开发者

AWE Asia 2021 | 百度AI+AR,塑造AR应用智能新体验

百度开发者中心

AI 最佳实践 方法论

如何利用国产图数据库打造金融行业方案?

星环科技

美女程序“媛”:从工程师到架构师,我的代码人生

融云 RongCloud

程序员 代码人生

Go 语言实现 WebSocket 推送

全象云低代码

websocket 低代码开发 Go 语言

Nacos-Group

平凡人生

从头到尾没有一句废话!阿里Redis神级手册,从基础到源码一次讲清

Java 架构 面试 程序人生 编程语言

推动数据安全战略落地 新思科技给出这些建议

InfoQ_434670063458

四种主要网络IO虚拟化模型

hanaper

第7章-《Linux一学就会》-Centos8 用户管理

学神来啦

Linux linux运维 linux学习 Linux教程

上汽零束 | 官方有奖征文正式启动,速来挑战!

SOA开发者平台

带你了解数仓安全测试的TLS协议

华为云开发者联盟

安全 通信 密钥 SSL/TLS协议 加密通信

2021年海南企业办理等保业务指南

行云管家

网络安全 等保测评 等保评测 海南

乌镇回溯 | 构建网络空间命运共同体,旺链科技做了哪些?

旺链科技

数字经济 产业区块链 世界互联网大会

基于云的 CRM 能为您的业务提供哪些服务?

低代码小观

企业管理 CRM

上汽零束 | 官方有奖征文正式启动,速来挑战!

SOA开发者

【编程语言】[Go] Go语言入门

衣谷

golang 协程 GC

未来E/E架构的发展情况

SOA开发者

云短信服务孰优孰劣?博睿数据重磅发布云短信评测报告

博睿数据

Talk to AI,揭秘背后的语音识别数据

澳鹏Appen

语音 nlp 语音识别 训练数据 语音识别模型

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