1. 概述

Arquillian 是一个与容器无关的 Jakarta EE 集成测试框架。使用 Arquillian 能极大减轻容器管理、应用部署、框架初始化等繁琐工作。

我们可以专注于编写真正的测试逻辑,而不是折腾测试环境的启动配置。

2. 核心概念

2.1. 部署归档

在容器内部运行测试时,Arquillian 提供了便捷方案:

  • ShrinkWrap 类提供 API 用于创建可部署的 *.jar*.war*.ear 文件
  • 通过 @Deployment 注解配置测试部署,该注解标注的方法需返回 ShrinkWrap 对象

2.2. 容器类型

Arquillian 区分三种容器类型:

远程容器 (Remote)
通过 JMX 等远程协议测试

托管容器 (Managed)
生命周期由 Arquillian 管理的远程容器

嵌入式容器 (Embedded)
使用本地协议执行测试的本地容器

按能力维度分类:

  • Jakarta EE 应用服务器(如 Glassfish、JBoss)
  • Servlet 容器(如 Tomcat、Jetty)
  • 独立容器
  • OSGI 容器

Arquillian 会自动检测运行时类路径并选择可用容器。

2.3. 测试增强

Arquillian 通过依赖注入等功能增强测试能力:

  • 使用 @Inject 注入依赖
  • 使用 @Resource 注入资源
  • 使用 @EJB 注入 EJB 会话 Bean

2.4. 多部署测试

通过注解创建多个部署:

@Deployment(name="myname" order = 1)
  • name:部署文件名称
  • order:部署执行顺序

使用 @OperateOnDeployment 在指定部署上运行测试:

@Test @OperateOnDeployment("myname")

测试将在名为 myname 的部署容器上执行。

2.5. Arquillian 扩展

当核心功能无法满足测试需求时,Arquillian 提供多种扩展:

  • 持久化扩展
  • 事务扩展
  • 客户端/服务端扩展
  • REST 扩展

通过 Maven/Gradle 添加依赖即可启用扩展,常用扩展包括:

  • Drone
  • Graphene
  • Selenium

3. Maven 依赖配置

pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.jboss.arquillian</groupId>
    <artifactId>arquillian-bom</artifactId>
    <version>1.1.13.Final</version>
    <scope>import</scope>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>org.glassfish.main.extras</groupId>
    <artifactId>glassfish-embedded-all</artifactId>
    <version>4.1.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.jboss.arquillian.container</groupId>
    <artifactId>arquillian-glassfish-embedded-3.1</artifactId>
    <version>1.0.0.Final</version>
    <scope>test</scope>
</dependency>

最新版本可在此处查找:arquillian-bom, glassfish-embedded-all, arquillian-glassfish-embedded-3.1

4. 简单测试示例

4.1. 创建组件

先创建一个简单组件(省略复杂逻辑以便聚焦测试):

public class Component {
    public void sendMessage(PrintStream to, String msg) {
        to.println(message(msg));
    }

    public String message(String msg) {
        return "Message, " + msg;
    }
}

我们将测试该类作为 CDI Bean 时的行为。

4.2. 编写首个 Arquillian 测试

首先指定测试运行器:

@RunWith(Arquillian.class)

使用 @Deployment 注解创建测试部署:

@Deployment
public static JavaArchive createDeployment() {
    return ShrinkWrap.create(JavaArchive.class)
      .addClass(Component.class)
      .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}

关键点:

  • ShrinkWrap 创建名为 test.war 的模拟 Web 归档
  • addClass() 指定测试所需类
  • addAsManifestResource 添加空的 beans.xml 启用 CDI

注入组件并编写测试:

@Inject
private Component component;

@Test
public void testComponent() {
    assertEquals("Message, MESSAGE", component.message("MESSAGE"));
    component.sendMessage(System.out, "MESSAGE");
}

5. 测试企业级 Java Bean

5.1. 创建 EJB 组件

创建字符串转换工具类:

public class ConvertToLowerCase {
    public String convert(String word){
        return word.toLowerCase();
    }
}

构建无状态会话 Bean:

@Stateless
public class CapsConvertor {
    public ConvertToLowerCase getLowerCase(){
        return new ConvertToLowerCase();
    }
}

创建服务 Bean 并注入依赖:

@Stateless
public class CapsService {
 
    @Inject
    private CapsConvertor capsConvertor;
    
    public String getConvertedCaps(final String word){
        return capsConvertor.getLowerCase().convert(word);
    }
}

5.2. 测试 EJB

使用 Arquillian 测试 EJB 依赖注入:

@Inject
private CapsService capsService;
    
@Test
public void givenWord_WhenUppercase_ThenLowercase(){
    assertTrue("capitalize".equals(capsService.getConvertedCaps("CAPITALIZE")));
    assertEquals("capitalize", capsService.getConvertedCaps("CAPITALIZE"));
}

确保所有类正确装配:

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

6. 测试 JPA 持久化

6.1. 持久化组件

创建 JPA 实体:

@Entity
public class Car {
 
    @Id
    @GeneratedValue
    private Long id;
 
    @NotNull
    private String name;

    // getters and setters
}

创建 EJB 执行数据库操作:

@Stateless
public class CarEJB {
 
    @PersistenceContext(unitName = "defaultPersistenceUnit")
    private EntityManager em;
 
    public Car saveCar(Car car) {
        em.persist(car);
        return car;
    }
 
    public List<Car> findAllCars() {
        Query query = em.createQuery("SELECT b FROM Car b ORDER BY b.name ASC");
        List<Car> entries = query.getResultList();
        return entries == null ? new ArrayList<>() : entries;    
    }
 
    public void deleteCar(Car car) {
        car = em.merge(car);
        em.remove(car);
    }
}

6.2. 持久化测试

配置 ShrinkWrap 包含持久化资源:

.addClasses(Car.class, CarEJB.class)
.addAsResource("META-INF/persistence.xml")

编写测试验证 CRUD 操作:

@Test
public void testCars() {
    assertTrue(carEJB.findAllCars().isEmpty());
    
    Car c1 = new Car();
    c1.setName("Impala");
    Car c2 = new Car();
    c2.setName("Lincoln");
    
    carEJB.saveCar(c1);
    carEJB.saveCar(c2);
 
    assertEquals(2, carEJB.findAllCars().size());
 
    carEJB.deleteCar(c1);
 
    assertEquals(1, carEJB.findAllCars().size());
}

测试流程:

  1. 验证初始数据库为空
  2. 添加两条记录
  3. 验证记录总数
  4. 删除一条记录
  5. 再次验证记录总数

8. 总结

本教程涵盖内容:

✅ Arquillian 核心概念解析
✅ 组件注入测试实践
✅ EJB 集成测试方案
✅ JPA 持久化测试方法
✅ Maven 测试环境搭建

完整代码示例请查阅 GitHub 仓库


原始标题:Introduction to Testing with Arquillian