Integration Testing with Maven – 用Maven进行集成测试

最后修改: 2018年 8月 23日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Maven is the most popular build tool in the Java space, while integration testing is an essential part of the development process. Therefore, it’s a natural choice to configure and execute integration tests with Maven.

Maven是Java领域最受欢迎的构建工具,而集成测试是开发过程中必不可少的一部分。因此,用Maven配置和执行集成测试是一种自然选择。

In this tutorial, we’ll go over a number of different ways to use Maven for integration testing and to separate integration tests from unit tests.

在本教程中,我们将介绍使用Maven进行集成测试以及将集成测试与单元测试分开的若干不同方法。

2. Preparation

2.准备

To make the demonstration code close to a real-world project, we’ll set up a JAX-RS application. This application is deployed to a server before the execution of integration tests and dismantled afterward.

为了使演示代码接近于真实世界的项目,我们将设置一个JAX-RS应用程序。这个应用在执行集成测试之前被部署到服务器上,之后被拆除。

2.1. Maven Configuration

2.1.Maven配置

We’ll build our REST application around Jersey – the reference implementation of JAX-RS. This implementation requires a couple of dependencies:

我们将围绕Jersey(JAX-RS的参考实现)构建我们的REST应用。这个实现需要一些依赖性。

<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet-core</artifactId>
    <version>2.27</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.inject</groupId>
    <artifactId>jersey-hk2</artifactId>
    <version>2.27</version>
</dependency>

We can find the latest versions of these dependencies here and here.

我们可以找到这些依赖的最新版本这里这里

We’ll use the Jetty Maven plugin to set up a testing environment. This plugin starts a Jetty server during the pre-integration-test phase of the Maven build lifecycle, then stops it in the post-integration-test phase.

我们将使用Jetty Maven插件来建立一个测试环境。该插件在Maven构建生命周期的前集成测试阶段启动Jetty服务器,然后在后集成测试阶段停止它。

Here’s how we configure the Jetty Maven plugin in pom.xml:

下面是我们如何在pom.xml中配置Jetty Maven插件。

<plugin>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>9.4.11.v20180605</version>
    <configuration>
        <httpConnector>
            <port>8999</port>
        </httpConnector>
        <stopKey>quit</stopKey>
        <stopPort>9000</stopPort>
    </configuration>
    <executions>
        <execution>
            <id>start-jetty</id>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>start</goal>
            </goals>
        </execution>
        <execution>
            <id>stop-jetty</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>stop</goal>
            </goals>
        </execution>
    </executions>
</plugin>

When the Jetty server starts up, it’ll be listening on port 8999. The stopKey and stopPort configuration elements are used solely by the plugin’s stop goal and their value isn’t important from our perspective.

当Jetty服务器启动时,它将在端口8999上监听。stopKeystopPort配置元素只被插件的stop目标使用,从我们的角度来看,它们的值并不重要。

Here is where to find the latest version of the Jetty Maven plugin.

这里可以找到最新版本的Jetty Maven插件。

Another thing to notice is that we must set the packaging element in the pom.xml file to war, otherwise the Jetty plugin cannot start the server:

另外需要注意的是,我们必须把pom.xml文件中的packaging元素设置为war,否则Jetty插件无法启动服务器。

<packaging>war</packaging>

2.2. Creating a REST Application

2.2.创建一个REST应用程序

The application endpoint is very simple – returning a welcome message when a GET request hits the context root:

应用端点非常简单–当一个GET请求击中上下文根时返回一个欢迎信息。

@Path("/")
public class RestEndpoint {
    @GET
    public String hello() {
        return "Welcome to Baeldung!";
    }
}

This is how we register the endpoint class with Jersey:

这就是我们如何向Jersey注册端点类。

package com.baeldung.maven.it;

import org.glassfish.jersey.server.ResourceConfig;

public class EndpointConfig extends ResourceConfig {
    public EndpointConfig() {
        register(RestEndpoint.class);
    }
}

To have the Jetty server aware of our REST application, we can use a classic web.xml deployment descriptor:

为了让Jetty服务器了解我们的REST应用程序,我们可以使用一个经典的web.xml部署描述符。

<web-app 
  xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">
    <servlet>
        <servlet-name>rest-servlet</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.baeldung.maven.it.EndpointConfig</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>rest-servlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

This descriptor must be placed in the directory /src/main/webapp/WEB-INF to be recognized by the server.

这个描述符必须放在/src/main/webapp/WEB-INF目录下,才能被服务器识别。

2.3. Client-Side Test Code

2.3.客户端测试代码

All test classes in the following sections contain a single method:

以下各节的所有测试类都包含一个方法。

@Test
public void whenSendingGet_thenMessageIsReturned() throws IOException {
    String url = "http://localhost:8999";
    URLConnection connection = new URL(url).openConnection();
    try (InputStream response = connection.getInputStream();
      Scanner scanner = new Scanner(response)) {
        String responseBody = scanner.nextLine();
        assertEquals("Welcome to Baeldung!", responseBody);
    }
}

As we can see, this method does nothing but sending a GET request to the web application we set up before and verifying the response.

正如我们所看到的,这个方法除了向我们之前设置的网络应用程序发送一个GET请求并验证响应外,什么都没做。

3. Integration Testing in Action

3.集成测试在行动

An important thing to notice about integration testing is that test methods often take quite a long time to run.

关于集成测试,需要注意的一个重要问题是,测试方法往往需要相当长的时间来运行。

As a result, we should exclude integration tests from the default build lifecycle, keeping them from slowing down the whole process each time we build a project.

因此,我们应该将集成测试从默认的构建生命周期中排除,使它们在每次构建项目时不会拖累整个过程。

A convenient way to separate integration tests is to use build profiles. This kind of configuration enables us to execute integration tests only when necessary – by specifying a suitable profile.

分离集成测试的一个方便方法是使用构建配置文件。这种配置使我们能够只在必要时执行集成测试–通过指定一个合适的配置文件。

In the sections that follow, we’ll configure all integration tests with build profiles.

在接下来的章节中,我们将用构建配置文件配置所有的集成测试。

4. Testing With the Failsafe Plugin

4.使用故障安全插件进行测试

The simplest way to run integration tests is to use the Maven failsafe plugin.

运行集成测试的最简单方法是使用Maven failsafe插件

By default, the Maven surefire plugin executes unit tests during the test phase, while the failsafe plugin runs integration tests in the integration-test phase.

默认情况下,Maven的surefire插件在test阶段执行单元测试,而failsafe插件在integration-test阶段运行集成测试

We can name test classes with different patterns for those plugins to pick up the enclosed tests separately.

我们可以为那些插件命名不同模式的测试类,以分别拾取所附的测试。

The default naming conventions enforced by surefire and failsafe are different, thus we just need to follow these conventions to segregate unit and integration tests.

surefirefailsafe执行的默认命名约定是不同的,因此我们只需要遵循这些约定来隔离单元和集成测试。

The execution of the surefire plugin includes all classes whose name starts with Test, or ends with Test, Tests or TestCase. In contrast, the failsafe plugin executes test methods in classes whose name starts with IT, or ends with IT or ITCase.

surefire插件的执行包括所有名字以Test开头,或以TestTestsTestCase结束的类。相比之下,failsafe插件执行的是名字以IT开头,或以ITITCase结尾的类的测试方法。

This is where we can find the documentation regarding test inclusion for surefire, and here is the one for failsafe.

这里是我们可以找到关于surefire的测试包含的文档,而这里是关于failsafe的。

Let’s add the failsafe plugin to the POM with default configuration:

让我们把failsafe插件以默认配置添加到POM中。

<profile>
    <id>failsafe</id>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.22.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

This link is where to find the latest version of the failsafe plugin.

这个链接可以找到最新版本的failsafe插件。

With the above configuration, the following test method will be executed in the integration-test phase:

通过上述配置,以下测试方法将在集成测试阶段被执行。

public class RestIT {
    // test method shown in subsection 2.3
}

Since the Jetty server starts up in the pre-integration-test phase and shuts down in post-integration-test, the test we have just seen passes with this command:

由于Jetty服务器在pre-integration-test阶段启动,在post-integration-test阶段关闭,我们刚才看到的测试通过了这个命令。

mvn verify -Pfailsafe

We can also customize the naming patterns to include classes with different names:

我们还可以自定义命名模式,以包括具有不同名称的类。

<plugin>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.22.0</version>
    <configuration>
        <includes>
            <include>**/*RestIT</include>
            <include>**/RestITCase</include>
        </includes>
    </configuration>
    ...
</plugin>

5. Testing With the Surefire Plugin

5.使用Surefire插件进行测试

Apart from the failsafe plugin, we can also use the surefire plugin to execute unit and integration tests in different phases.

除了failsafe插件,我们还可以使用surefire插件,在不同阶段执行单元和集成测试。

Let’s assume we want to name all integration tests with the suffix IntegrationTest. Since the surefire plugin runs tests with such a name in the test phase by default, we need to exclude them from the default execution:

让我们假设我们想用后缀IntegrationTest来命名所有的集成测试。由于surefire插件默认在test阶段运行具有这样名字的测试,我们需要将它们从默认执行中排除。

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <configuration>
        <excludes>
            <exclude>**/*IntegrationTest</exclude>
        </excludes>
    </configuration>
</plugin>

The latest version of this plugin is here.

该插件的最新版本是这里

We’ve taken all test classes having a name ending with IntegrationTest out of the build lifecycle. It’s time to put them back with a profile:

我们已经把所有名字以IntegrationTest结尾的测试类从构建生命周期中移除。现在是时候把它们放回配置文件中了。

<profile>
    <id>surefire</id>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <executions>
                    <execution>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>test</goal>
                        </goals>
                        <configuration>
                            <excludes>
                                <exclude>none</exclude>
                            </excludes>
                            <includes>
                                <include>**/*IntegrationTest</include>
                            </includes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

Instead of binding the test goal of the surefire plugin to the test build phase, as usual, we bound it to the integration-test phase. The plugin will then kick in during the integration testing process.

我们没有像往常一样将test插件的目标绑定到test构建阶段,而是将其绑定到integration-test阶段。然后该插件将在整合测试过程中启动。

Notice that we must set an exclude element to none to override the exclusion specified in the base configuration.

注意,我们必须将一个exclude元素设置为none,以覆盖基本配置中指定的排除法。

Now, let’s define an integration test class with our naming pattern:

现在,让我们用我们的命名模式定义一个集成测试类。

public class RestIntegrationTest {
    // test method shown in subsection 2.3
}

This test will be running with the command:

这个测试将用命令来运行。

mvn verify -Psurefire

6. Testing With the Cargo Plugin

6.使用货物插件进行测试

We can use the surefire plugin with the Maven cargo plugin. This plugin comes with built-in support for embedded servers, which are very useful for integration testing.

我们可以将surefire插件与Maven的cargo插件一起使用。该插件内置了对嵌入式服务器的支持,这对集成测试非常有用。

More details about this combination can be found here.

关于这一组合的更多细节,可以在这里找到。

7. Testing With JUnit’s @Category

7.用JUnit的@Category测试

A convenient way to selectively execute tests is to leverage the @Category annotation in the JUnit 4 framework. This annotation lets us exclude particular tests from unit testing, and include them in integration testing.

选择性地执行测试的方便方法是利用 JUnit 4 框架中的 @Category 注解该注解使我们能够从单元测试中排除特定的测试,并将其纳入集成测试中。

First off, we need an interface or class to work as a category identifier:

首先,我们需要一个接口或类来作为类别标识符。

package com.baeldung.maven.it;

public interface Integration { }

We can then decorate a test class with the @Category annotation and Integration identifier:

然后我们可以用@Category注解和Integration标识符来装饰一个测试类。

@Category(Integration.class)
public class RestJUnitTest {
    // test method shown in subsection 2.3
}

Rather than declaring the @Category annotation on a test class, we can also use it at the method level to categorize individual test methods.

与其在测试类上声明@Category注解,我们还可以在方法层面使用它来对单个测试方法进行分类。

Excluding a category from the test build phase is simple:

测试构建阶段排除一个类别很简单。

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <configuration>
        <excludedGroups>com.baeldung.maven.it.Integration</excludedGroups>
    </configuration>
</plugin>

Including the Integration category in the integration-test phase is also straightforward:

集成类纳入集成测试阶段也是直接的。

<profile>
    <id>category</id>
        <build>
        <plugins>
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.22.0</version>
                <configuration>
                    <includes>
                        <include>**/*</include>
                    </includes>
                    <groups>com.baeldung.maven.it.Integration</groups>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

We can now run integration tests with a Maven command:

现在我们可以用Maven命令运行集成测试。

mvn verify -Pcategory

8. Adding a Separate Directory for Integration Tests

8.为集成测试添加一个单独的目录

It’s desirable at times to have a separate directory for integration tests. Organizing tests this way allows us to entirely isolate integration tests from unit tests.

有时,为集成测试建立一个单独的目录是很理想的。通过这种方式组织测试,我们可以将集成测试与单元测试完全隔离。

We can use the Maven build helper plugin for this purpose:

我们可以使用Maven的build helper插件来实现这一目的。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
        <execution>
            <id>add-integration-test-source</id>
            <phase>generate-test-sources</phase>
            <goals>
                <goal>add-test-source</goal>
            </goals>
            <configuration>
                <sources>
                    <source>src/integration-test/java</source>
                </sources>
            </configuration>
        </execution>
    </executions>
</plugin>

Here is where we can find the latest version of this plugin.

这里是我们可以找到这个插件的最新版本。

The configuration we’ve just seen adds a test source directory to the build. Let’s add a class definition to that new directory:

我们刚才看到的配置在构建时添加了一个测试源目录。让我们为这个新目录添加一个类定义。

public class RestITCase {
    // test method shown in subsection 2.3
}

It’s time to run integration tests in this class:

是时候在这个班上运行集成测试了。

mvn verify -Pfailsafe

The Maven failsafe plugin will execute methods in this test class due to the configuration we set in subsection 3.1.

由于我们在3.1小节中设置了配置,Maven failsafe插件将执行该测试类中的方法。

A test source directory often goes with a resource directory. We can add such a directory in another execution element to the plugin configuration:

一个测试源目录经常与一个资源目录一起。我们可以在插件配置的另一个执行元素中添加这样一个目录。

<executions>
    ...
    <execution>
        <id>add-integration-test-resource</id>
        <phase>generate-test-resources</phase>
        <goals>
            <goal>add-test-resource</goal>
        </goals>
        <configuration>
            <resources>
                <resource>
                    <directory>src/integration-test/resources</directory>
                </resource>
            </resources>
        </configuration>
    </execution>
</executions>

9. Conclusion

9.结论

This article went over using Maven to run integration tests with a Jetty server, focusing on the configuration of the Maven surefire and failsafe plugins.

本文介绍了使用Maven在Jetty服务器上运行集成测试,重点是Maven surefirefailsafe插件的配置。

The complete source code for this tutorial can be found over on GitHub.

本教程的完整源代码可以在GitHub上找到over