尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

【JUnit实战3_17】第九章:容器内测试(下)——Arquillian 框架的用法简介 - 实践

【JUnit实战3_17】第九章:容器内测试(下)——Arquillian 框架的用法简介 - 实践
📅 发布时间:2026/6/20 0:41:31

【JUnit实战3_17】第九章:容器内测试(下)——Arquillian 框架的用法简介 - 实践

JUnit in Action, Third Edition

《JUnit in Action》全新第3版封面截图

写在前面
本篇重点介绍容器内测试的专用框架——Arquillian。作者成书之时该框架还没能全面支持 JUnit 5,因此只能沿用 JUnit 4。最新消息据说已经实现了 JUnit 5 的兼容(待学完本书后验证)。Arquillian 框架貌似解了容器场景下的燃眉之急,但从这几年的爆冷也暴露了一些问题,让其团队尝到了热脸贴冷屁股的滋味……

(接上篇)

9.4 Arquillian 框架用法简介

Arquillian(https://arquillian.org/)是一款针对 Java 的测试框架。它利用了 JUnit 在 Java 容器中执行测试用例。

Arquillian 框架主要分为三个核心部分:

  • 测试运行器(Test runners):由 JUnit 测试框架提供;
  • 容器(Containers):如 WildFly、Tomcat、GlassFish、Jetty 等;
  • 测试增强工具(Test enrichers):负责将容器资源和各种 Bean 直接注入到测试类中。

遗憾的是,该书出版五年后的今天,Arquillian 框架仍然没有与 JUnit 5 实现完美集成,相关演示只能在 JUnit 4 中进行。

Arquillian 框架使用 ShrinkWrap 这一外部依赖提供的流畅 API 接口完成归档文件的组装工作(如组装成 jar、war 和 ear 文件等),并在测试期间由 Arquillian 直接部署。

本节演示了一个航班与乘客管理的模拟场景,航班对象可以动态添加或删除乘客集合中的元素,并通过该航班的总座位数对乘客总数进行限制。航班中的乘客数据以 HashSet<Passenger> 的形式存在,并从一个 CSV 文件中完成初始化。具体情况如下。

首先添加所需的 Maven 依赖:

<dependencyManagement><dependencies><dependency><groupId>org.jboss.arquillian</groupId><artifactId>arquillian-bom</artifactId><version>1.4.0.Final</version><scope>import</scope><type>pom</type></dependency></dependencies>
</dependencyManagement>
<dependencies><dependency><groupId>org.jboss.spec</groupId><artifactId>jboss-javaee-7.0</artifactId><version>1.0.3.Final</version><type>pom</type><scope>provided</scope></dependency><dependency><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId><version>5.9.2</version><scope>test</scope></dependency><dependency><groupId>org.jboss.arquillian.junit</groupId><artifactId>arquillian-junit-container</artifactId><scope>test</scope></dependency><dependency><groupId>org.jboss.arquillian.container</groupId><artifactId>arquillian-weld-ee-embedded-1.1</artifactId><version>1.0.0.CR9</version><scope>test</scope></dependency><dependency><groupId>org.jboss.weld</groupId><artifactId>weld-core</artifactId><version>2.4.8.Final</version><scope>test</scope></dependency>
</dependencies>

注意:由于本地实测距图书出版时相隔近五年,为了消除 IDEA 提示的易遭攻击风险,JUnit 版本最好升至 5.9.2、weld-core 的版本提升到 2.4.8.Final。同时为了消除 JDK11 限制使用 Java 反射机制的警告,可以按照运行提示修改如下插件配置:

<plugins><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.2</version><configuration><argLine>--add-opens java.base/java.lang=ALL-UNNAMED--add-opens java.base/java.security=ALL-UNNAMED--add-opens java.base/java.io=ALL-UNNAMED--add-opens java.base/java.util=ALL-UNNAMED</argLine></configuration></plugin>
</plugins>

Passenger 乘客实体类:

public class Passenger {
private String identifier;
private String name;
public Passenger(String identifier, String name) {
this.identifier = identifier;
this.name = name;
}
public String getIdentifier() {
return identifier;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Passenger " + getName() + " with identifier: " + getIdentifier();
}
}

Flight 航班实体类:

public class Flight {
private String flightNumber;
private int seats;
Set<Passenger> passengers = new HashSet<>();public Flight(String flightNumber, int seats) {this.flightNumber = flightNumber;this.seats = seats;}public String getFlightNumber() {return flightNumber;}public int getSeats() {return seats;}public void setSeats(int seats) {if (passengers.size() > seats) {throw new RuntimeException("Cannot reduce seats under the number of existing passengers!");}this.seats = seats;}public int getNumberOfPassengers() {return passengers.size();}public boolean addPassenger(Passenger passenger) {if (passengers.size() >= seats) {throw new RuntimeException("Cannot add more passengers than the capacity of the flight!");}return passengers.add(passenger);}public boolean removePassenger(Passenger passenger) {return passengers.remove(passenger);}@Overridepublic String toString() {return "Flight " + getFlightNumber();}}

乘客集合的初始化通过一个静态工具方法实现,需要从一个 CSV 文件 flights_information.csv 读取:

1236789; John Smith
9006789; Jane Underwood
1236790; James Perkins
9006790; Mary Calderon
1236791; Noah Graves
9006791; Jake Chavez
1236792; Oliver Aguilar
9006792; Emma McCann
1236793; Margaret Knight
9006793; Amelia Curry
1236794; Jack Vaughn
9006794; Liam Lewis
1236795; Olivia Reyes
9006795; Samantha Poole
1236796; Patricia Jordan
9006796; Robert Sherman
1236797; Mason Burton
9006797; Harry Christensen
1236798; Jennifer Mills
9006798; Sophia Graham

对应的工具类代码如下:

public class FlightBuilderUtil {
public static Flight buildFlightFromCsv() throws IOException {
Flight flight = new Flight("AA1234", 20);
try (BufferedReader reader = new BufferedReader(new FileReader("src/test/resources/flights_information.csv"))) {
String line = null;
do {
line = reader.readLine();
if (line != null) {
String[] passengerString = line.toString().split(";");
Passenger passenger = new Passenger(passengerString[0].trim(), passengerString[1].trim());
flight.addPassenger(passenger);
}
} while (line != null);
}
return flight;
}
}

最终的 Arquillian 测试类如下:

@RunWith(Arquillian.class)
public class FlightWithPassengersTest {
@Deployment
public static JavaArchive createDeployment() {
return ShrinkWrap.create(JavaArchive.class)
.addClasses(Passenger.class, Flight.class, FlightProducer.class)
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}
@Inject
Flight flight;
@Test(expected = RuntimeException.class)
public void testNumberOfSeatsCannotBeExceeded() throws IOException {
assertEquals(20, flight.getNumberOfPassengers());
flight.addPassenger(new Passenger("1247890", "Michael Johnson"));
}
@Test
public void testAddRemovePassengers() throws IOException {
flight.setSeats(21);
Passenger additionalPassenger = new Passenger("1247890", "Michael Johnson");
flight.addPassenger(additionalPassenger);
assertEquals(21, flight.getNumberOfPassengers());
flight.removePassenger(additionalPassenger);
assertEquals(20, flight.getNumberOfPassengers());
assertEquals(21, flight.getSeats());
}
}

上述代码中,相关组件的打包通过 @Deployment 注解的方法完成,具体由 ShrinkWrap 相关 API 实现。最初没有 FlightProducer.class 这个类(L7),但由于首次运行时 Arquillian 无法顺利注入 Flight 实例(仅支持无参构造函数):

Fig9.2

因此需要利用 JavaEE 中的 CDI(Context & Dependency Injection)机制,手动注入 Flight 实例,通过新增一个带 @Produces 注解方法的普通工具类:

// FlightProducer.java
import javax.enterprise.inject.Produces;
public class FlightProducer {
@Produces
public Flight createFlight() throws IOException {
return FlightBuilderUtil.buildFlightFromCsv();
}
}

最后再将这个 FlightProducer 类一并打包到归档文件中即可(L4):

@Deployment
public static JavaArchive createDeployment() {
return ShrinkWrap.create(JavaArchive.class)
.addClasses(Passenger.class, Flight.class, FlightProducer.class)
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}

最终实测截图:

Fig9.3

后话
Arquillian 官方文档貌似很长时间没有更新了,里面的一些示例还用的是 Eclipse 作展示,可见近年来并没有想象中的那么受欢迎。出发点很好、但好心办坏事的情况也比比皆是,本就不受重视的测试环节,为了贴近容器的真实环境还得搭一堆脚手架一样的东西,使用时又得改配置又得创建工具类,实在是不讨喜。因此本章只作为了解基本理念的拓展阅读即可,不必过于纠结。

相关新闻

  • 利用多项式模型对二维平面上的数据点进行拟合时,需要预先指定多项式的次数吗?
  • SkeyeVSS视频融合系统——安全帽AI检测算法 - 教程
  • U636457 刺客

最新新闻

  • 大兴安岭地区黄金回收去哪儿好?整理了5家靠谱实体店地址电话 - 三大殿
  • 承德市今日黄金回收价格多少?本地5家口碑门店报价参考 - 马刺总冠军
  • 2026 正规备案收金店,称重透明结算无隐藏扣费 - 讯息早知道
  • 贺州市黄金回收实体店怎么选?这份清单帮你货比三家 - 开始就结束
  • 金华市黄金回收猫腻多怎么办?整理了5家诚信回收店供参考 - 三大殿
  • 2026安徽省宣城市中考一两百分怎么办?口碑优选宠物护理专业最新发布 - cc江江

日新闻

  • 信任的进化:技术实现详解——如何用JavaScript构建博弈论模拟器
  • Terrakube自定义工作流:如何集成OPA、Infracost等工具扩展IaC能力
  • grunt-concurrent快速入门:5分钟学会并行运行Grunt任务

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号