这篇文章重点介绍Arquillian和ShrinkWrap以及为什么它们是用于企业Java应用程序集成测试的出色工具。
可以在GitHub上的arquillian-shrinkwrap
文件夹下找到用于本文的源代码。
工具
-
Arquillian
-
Arquillian将测试执行带到目标运行时,从而减轻了开发人员从测试或项目构建中管理运行时的负担。
要反转此控件,Arquillian将生命周期包装在执行以下操作的测试执行周围:
- 管理一个或多个容器的生命周期
- 将测试用例,相关类和资源捆绑为ShrinkWrap档案
- 将档案部署到容器
- 通过依赖注入和其他声明式服务丰富测试用例
- 在容器内部(或针对容器)执行测试
- 将结果返回给测试跑步者进行报告
收缩包装
-
ShrinkWrap是Arquillian的核心组件,它提供了一种简单的机制,可以使用友好,流利的API来组装JAR,WAR和EAR等档案。
使用Arquillian的主要好处之一是,您可以在远程容器(即应用程序服务器)中运行测试。 这意味着您将测试真实的交易 。 没有嘲笑。 甚至没有嵌入式运行时!
议程
这篇文章将涵盖以下主题:
- 在基于Maven的Java项目中配置Arquillian基础架构
- 直接在测试实例中注入EJB和受管Bean(CDI)
- 测试Java持久性API(JPA)层
- 在客户端模式下运行Arquillian
- 在IDE中运行和调试Arquillian测试
配置Maven以运行集成测试
要使用Maven运行集成测试,我们需要一种不同的方法。 通过不同的方法,我指的是一个不同的插件: Maven故障安全插件 。
故障安全插件是Maven Surefire插件的分支,旨在运行集成测试。
Failsafe插件目标旨在在集成测试阶段的程序包阶段之后运行。
Maven生命周期有四个运行集成测试的阶段:
- 集成前测试:在此阶段,我们可以启动任何必需的服务或执行任何操作,例如启动数据库或启动Web服务器,等等。
- 集成测试:故障安全将在此阶段运行测试,因此在所有必需的服务启动之后。
- 集成后测试:关闭所有服务的时间…
- 验证: failsafe运行另一个在此处解释测试结果的目标,如果没有通过任何测试,failsafe将显示结果并退出构建。
在POM中配置故障保护:
<!-- clip -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.12</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<id>integration-test</id>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
<execution>
<id>verify</id>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- clip -->
默认情况下,Surefire插件执行**/Test*.java
, **/*Test.java
和**/*TestCase.java
测试类。 Failsafe插件将查找**/IT*.java
, **/*IT.java
和**/*ITCase.java
。 如果同时使用Surefire和Failsafe插件,请确保使用此命名约定,以使其更容易识别哪个插件正在执行哪些测试。
在Maven中配置Arquillian基础架构
通过附加以下XML片段,将Maven项目描述符配置为使用Arquillian:
<!-- clip -->
<repositories>
<repository>
<id>jboss-public-repository-group</id>
<name>JBoss Public Repository Group</name>
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>1.0.1.Final</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.arquillian.testng</groupId>
<artifactId>arquillian-testng-container</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec</groupId>
<artifactId>jboss-javaee-6.0</artifactId>
<version>3.0.1.Final</version>
<scope>provided</scope>
<type>pom</type>
</dependency>
</dependencies>
<profiles>
<profile>
<id>jbossas-remote-7</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-arquillian-container-remote</artifactId>
<version>7.1.1.Final</version>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
</profiles>
<!-- clip -->
Arquillian有大量的容器适配器 。 Arquillian测试可以在与测试中使用的编程模型兼容的任何容器中执行。 但是,在本文中,仅使用JBoss AS 7。
与Java EE 6测试第I部分类似,我选择使用TestNG测试框架,但同样, JUnit应该也能正常工作。
创建可测试的组件
在研究如何使用Arquillian编写集成测试之前,我们首先需要有一个要测试的组件。
会话Bean是Java EE堆栈中的常见组件,将用作测试主题。 在本文中,我将创建一个非常基本的后端,用于向数据库中添加新用户。
@Stateless
public class UserServiceBean {
@PersistenceContext
private EntityManager em;
public User addUser(User user) {
em.persist(user);
return user;
}
// Annotation says that we do not need to open a transaction
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public User findUserById(Long id) {
return em.find(User.class, id);
}
}
在上面的代码中,我使用JPA ,因此我们需要一个持久性单元。
持久性单元定义由应用程序中的EntityManager
实例管理的所有实体类的集合。 这组实体类表示单个数据存储中包含的数据。 持久性单元由persistence.xml
配置文件定义:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="example">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
在本示例中,我使用的示例数据源使用H2数据库,并且已经使用JBoss AS 7进行了配置。
最后,我们还需要一个映射到数据库表的实体:
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
@NotNull
private String name;
// Removed constructors, getters and setters for brevity
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
使用Arquillian测试JPA
现在,我们都准备编写我们的第一个Arquillian测试。
Arquillian测试用例看上去就像带有一些额外功能的单元测试。 它必须具有三件事:
- 扩展Arquillian类(这特定于TestNG,对于JUnit,您需要在类上使用
@RunWith(Arquillian.class)
批注) - 用@Deployment注释的公共静态方法,该方法返回ShrinkWrap存档
- 至少一种用@Test注释的方法
public class UserServiceBeanIT extends Arquillian {
private static final Logger LOGGER = Logger.getLogger(UserServiceBeanIT.class.getName());
@Inject
private UserServiceBean service;
@Deployment
public static JavaArchive createTestableDeployment() {
final JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "example.jar")
.addClasses(User.class, UserServiceBean.class)
.addAsManifestResource("META-INF/persistence.xml", "persistence.xml")
// Enable CDI
.addAsManifestResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"));
LOGGER.info(jar.toString(Formatters.VERBOSE));
return jar;
}
@Test
public void callServiceToAddNewUserToDB() {
final User user = new User("Ike");
service.addUser(user);
assertNotNull(user.getId(), "User id should not be null!");
}
}
该测试非常简单,它插入一个新用户并检查id
属性是否已被数据库生成的值填充。
由于Arquillian丰富了该测试,因此通常可以使用@EJB
或@Inject
批注来注入EJB和受管bean。
用@Deployment
注释的方法使用ShrinkWrap来构建一个JAR归档文件,该归档文件将部署到容器中并对其进行测试。 ShrinkWrap将测试所需的类和资源与类路径的其余部分隔离开来,您应该包括测试所需的每个组件才能在部署存档中运行。
客户端模式
Arquillian支持三种测试运行模式:
- 容器内模式用于测试您的应用程序内部。 这使Arquillian能够与测试进行通信,丰富测试并远程运行测试。 在这种模式下,测试在远程容器中执行; Arquillian默认使用此模式。
- 客户端模式用于测试客户端如何使用您的应用程序。 与容器内模式重新打包并覆盖测试执行相反,客户端模式的作用尽可能小。 它不会重新打包
@Deployment
也不会将测试执行转发到远程服务器。 您的测试用例正在JVM中按预期运行,并且您可以自由地从外部测试容器,如客户所见。 Arquillian唯一要做的就是控制@Deployment
的生命周期。 - 混合模式允许在同一测试类中混合两种运行模式。
要以客户端模式运行Arquillian,首先要构建要测试的servlet:
@WebServlet("/User")
public class UserServlet extends HttpServlet {
private static final long serialVersionUID = -7125652220750352874L;
@Inject
private UserServiceBean service;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.println(service.addUser(new User("Ike")).toString());
out.close();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
doGet(request, response);
}
}
现在让我们测试一下:
public class UserServletIT extends Arquillian {
private static final Logger LOGGER = Logger.getLogger(UserServletIT.class.getName());
// Not managed, should be used for external calls (e.g. HTTP)
@Deployment(testable = false)
public static WebArchive createNotTestableDeployment() {
final WebArchive war = ShrinkWrap.create(WebArchive.class, "example.war")
.addClasses(User.class, UserServiceBean.class, UserServlet.class)
.addAsResource("META-INF/persistence.xml")
// Enable CDI
.addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"));
LOGGER.info(war.toString(Formatters.VERBOSE));
return war;
}
@RunAsClient // Same as @Deployment(testable = false), should only be used in mixed mode
@Test(dataProvider = Arquillian.ARQUILLIAN_DATA_PROVIDER)
public void callServletToAddNewUserToDB(@ArquillianResource URL baseURL) throws IOException {
// Servlet is listening at <context_path>/User
final URL url = new URL(baseURL, "User");
final User user = new User(1L, "Ike");
StringBuilder builder = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
reader.close();
assertEquals(builder.toString(), user.toString());
}
}
尽管此测试非常简单,但它允许您通过单个方法调用来测试应用程序的多个层。
在Eclipse中运行测试
您可以从IDE内部运行Arquillian测试,就像单元测试一样。
运行Arquillian测试
(点击图片以放大)
- 安装TestNG和JBoss Tools Eclipse插件。
- 将新的JBoss AS服务器添加到Eclipse:
- 启动JBoss AS服务器:
- 从Eclipse运行测试用例,右键单击Project Explorer上的测试文件,然后选择
Run As > TestNG Test
:
结果应类似于以下内容:
调试Arquillian测试
(点击图片以放大)
由于我们使用的是远程容器,因此Debug As > TestNG Test
不会导致断点被激活。
相反,我们需要以调试模式启动容器并附加调试器。 这是因为测试是在与原始测试运行器不同的JVM中运行的。 调试测试所需要做的唯一更改是在调试模式下启动JBoss AS服务器:
- 启动JBoss AS服务器调试模式:
- 将所需的断点添加到代码中。
- 并通过右键单击Project Explorer上的测试文件并选择它来调试它
Run As > TestNG Test
:
更多资源
我希望能够强调Arquillian的一些好处。
有关Arquillian的更多信息,请查看以下资源:
相关文章
- 单元测试JBoss 5服务
- Java EE 6测试第I部分– EJB 3.1可嵌入API
- Maven 2 Cobertura插件–更新
- JBoss PojoCache配置
- JBoss AS 5.0已经发布!
- 上一篇文章:Java EE 6测试第I部分– EJB 3.1可嵌入API
- 下一篇文章:比较OpenDDR与WURFL
参考: Java EE 6测试第II部分–来自我们JCG合作伙伴 Samuel Santos的Arquillian和ShrinkWrap简介,位于Samaxes博客上。
翻译自: https://www.javacodegeeks.com/2012/06/java-ee-6-testing-part-ii-introduction.html