Contents

Arquillian 简介

1. 概述

Arquillian 是 Jakarta EE 的容器无关集成测试框架。使用 Arquillian 可以最大限度地减少管理容器、部署、框架初始化等的负担。

我们可以专注于编写实际测试,而不是引导测试环境。

2. 核心概念

2.1. 部署

在容器内运行时,有一种简单的方法可以测试我们的应用程序。

首先,ShrinkWrap类提供了一个 API 来创建可部署的 *.jar、*.war 和 *.ear文件。

然后,Arquillian 允许我们在返回ShrinkWrap对象的方法上使用*@Deployment*注解配置测试部署。

2.2. 容器

Arquillian 区分了三种不同类型的容器:

  • 远程 - 使用 JMX 等远程协议进行测试
  • 托管 - 远程容器,但它们的生命周期由 Arquillian 管理
  • 嵌入式 - 使用本地协议执行测试的本地容器

此外,我们可以根据容器的功能对容器进行分类:

  • 部署在 Glassfish 或 JBoss 等应用服务器上的 Jakarta EE 应用程序
  • 部署在 Tomcat 或 Jetty 上的 Servlet 容器
  • 独立容器
  • OSGI 容器

它检查运行时类路径并自动选择可用的容器。

2.3. 测试丰富

Arquillian 通过提供例如依赖注入来丰富测试,以便我们可以轻松编写测试。

我们可以使用*@Inject注入依赖项,使用@Resource注入资源,使用@EJB*注入EJB 会话 bean等。

2.4. 多个测试

我们可以使用注解创建多个部署:

@Deployment(name="myname" order = 1)

其中 name 是部署文件的名称,order 参数是部署的执行顺序,所以我们现在可以使用注解同时在多个部署上运行测试:

@Test @OperateOnDeployment("myname")

使用*@Deployment注释中定义的顺序在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-bomorg.glassfish.main.extrasorg.jboss.arquillian.container

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;
    }
}

使用 Arquillian,我们想测试这个类在作为 CDI bean 调用时的行为是否正确。

4.2. 编写我们的第一个 Arquillian 测试

首先,我们需要指定我们的测试类应该使用特定于框架的运行器来运行:

@RunWith(Arquillian.class)

如果我们要在容器中运行测试,我们需要使用*@Deployment*注解。

Arquillian 不使用整个类路径来隔离测试存档。相反,它使用ShrinkWrap类,这是一个用于创建档案的 Java API。当我们创建要测试的存档时,我们指定要在类路径中包含哪些文件以使用测试。在部署期间,ShrinkWrap仅隔离测试所需的类。

使用*addclass()*方法,我们可以指定所有必要的类,还可以添加一个空的清单资源。

JavaArchive.class创建一个名为 test.war 的模型 Web 存档,该文件被部署到容器中,然后被 Arquillian 用于执行测试:

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

然后我们在测试中注入我们的组件:

@Inject
private Component component;

最后,我们执行我们的测试:

assertEquals("Message, MESSAGE",component.message(("MESSAGE")));
 
component.sendMessage(System.out, "MESSAGE");

5. 测试 Enterprise Java Bean

5.1. Enterprise Java Bean

使用 Arquillian,我们可以测试 Enterprise Java Bean 的依赖注入,为此我们创建一个类,该类具有将任何单词转换为小写的方法:

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

使用这个类,我们创建一个无状态类来调用之前创建的方法:

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

CapsConvertor类被注入到服务 bean 中:

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

5.2. 测试Enterprise Java Bean

现在我们可以使用 Arquillian 来测试我们的Enterprise Java Bean,注入CapsService

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

使用ShrinkWrap,我们确保所有类都正确连接:

@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. 持久性

我们还可以使用 Arquillian 来测试持久性。首先,我们将创建我们的实体:

@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);
    }
}

使用saveCar我们可以将汽车名称保存到数据库中,我们可以使用findAllCars 获取存储的所有汽车,还可以使用deleteCar从数据库中删除汽车。

6.2. 使用 Arquillian 测试持久性

现在我们可以使用 Arquillian 执行一些基本测试。

首先,我们将我们的类添加到我们的ShrinkWrap 中:

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

然后我们创建我们的测试:

@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());
}

在这个测试中,我们首先创建了四个汽车实例,并检查数据库中的行数是否与我们创建的相同。