Maven - 在 JUnit 测试之前将 webapp 部署到 tomcat

Posted

技术标签:

【中文标题】Maven - 在 JUnit 测试之前将 webapp 部署到 tomcat【英文标题】:Maven - deploy webapp to tomcat before JUnit test 【发布时间】:2013-05-31 21:06:27 【问题描述】:

我有提供网络服务的 webapp。我想使用 SoapUI 执行 JUnit 测试以检查此服务是否正常工作。 但要测试 Web 服务应用程序必须部署到我的 Tomcat 7 服务器。

我不知道如何配置 maven 来构建战争,然后将其部署到 tomcat(理想情况下:为此运行单独的 tomcat 实例),然后运行 ​​JUnit 测试。 我将不胜感激。

我使用的是 maven 2.2.1

【问题讨论】:

【参考方案1】:

关于如何使用 Maven 处理这种类型的集成测试,存在多种思想流派。

我应该指出,当您将应用程序部署到应用程序服务器时,您不再处于单元测试领域。因为整个应用程序都部署在一个容器中,所以您正在测试这两个组件的集成。

现在使用 JUnit 运行 集成测试 没有任何问题(尽管您可能会遇到一些限制,例如 单元测试 不应该关心排序单个测试 - 假设您正确编写它们 - 所以 JUnit 通过不保证任何执行顺序来强制执行 ...在 Java 1.7 之前,执行顺序意外地被测试方法的顺序所暗示类,但它不是 JUnit 合同的一部分...如果他们发现 单元测试焦点,有些人会切换到其他测试框架进行 集成 测试,例如 TestNG的 JUnit 正在妨碍他们的测试开发)

要记住的关键点是 Maven 生命周期使用test 阶段来执行单元测试。

对于集成测试,关于使用 Maven 处理测试的正确方法有两种(半)思想流派。

学校 1 - 故障安全和integration-test/verify

这种思想流派使用package之后的阶段来启动容器,运行集成测试,拆除容器,最后检查测试结果并在测试失败时使构建失败。

永远不要运行mvn integration-test,因为这不会正确地拆除容器,任何时候你想输入mvn integration-test你实际上想输入mvn verify(哦,看,它更短更容易还要输入...奖金)

因此,您可以执行以下操作:

使用fork=true 将tomcat7:run 绑定到pre-integration-test 阶段 将failsafe:integration-test 绑定到integration-test 阶段 将tomcat7:shutdown 绑定到post-integration-test 阶段 绑定故障安全:验证到verify 阶段。

对于额外的加分,您可以使用绑定到validate 阶段的build-helper-maven-plugin:reserve-network-port 来确保测试服务器在未使用的网络端口上启动,然后对测试资源使用资源过滤将端口传递到测试或使用通过systemPropertyVariables 传递的系统属性使端口号可用于测试。

优势

干净的 Maven 构建 如果测试失败,您将无法发布项目 如果测试太慢而无法运行每个构建,则可以将集成测试移动到单独的配置文件中(按照约定称为 run-its)。

缺点

很难从 IDE 运行测试。所有的集成测试都在IT 开始/结束,虽然 Maven 知道使用 Surefire 运行测试开始/结束于 Test 并使用 Failsafe 运行测试开始/结束于 IT,但您的 IDE 可能不会。此外,您的 IDE 不会为您启动容器,因此您必须手动完成大量工作才能真正手动运行测试。

调试测试可能需要附加两个调试器,例如一个用于调试在容器中运行的应用程序,另一个用于debug the test cases。

mvnDebug -Dmaven.failsafe.debug=true verify

将您的测试与 Maven 构建过程相结合。

学校 2 - 独立模块

这种思想流派将集成测试移动到一个单独的模块中,该模块依赖于 war 模块,并将 war 复制到测试资源中,例如dependency:copy-dependencies 绑定到 generate-test-resources 阶段以及要测试的 Tomcat7 依赖项。

测试用例本身使用embedded mode启动Tomcat7容器

优势

测试可以在 IDE 中运行 集成测试与单元测试分开,因此要求 IDE 运行所有测试不会启动较慢的测试

缺点

war 工件只有在您超过 package 阶段时才会重建,因此,在使用 IDE 时,您需要至少定期运行 mvn clean package 以刷新被测代码。 集成测试的失败不会破坏war 模块的构建,因此您最终可以释放一个损坏的war 工件,然后让集成测试模块的反应器构建失败。有些人通过在 src/it 中使用集成测试模块并使用 Maven Invoker 插件来运行测试来解决这个问题......虽然这提供了较差的 IDE 集成,所以我不推荐该行。 很难从 Maven 获得综合测试覆盖率报告。 必须在测试用例中自己编写容器启动/停止代码。

School 2.5 - 测试用例启动自己的 Tomcat7 服务器的故障保护

这是两种方法的混合体。

您使用 Failsafe 执行测试,但测试本身负责启动和停止您要测试的 Tomcat7 容器。

优势

不必在 Maven pom 中配置服务器启动/停止 IDE 可以安全地运行所有测试(尽管集成测试可能会比较慢,您可能不想运行它们,但除非测试失败,否则它们不会全部失败) 更容易从 IDE 中调试测试(只需附加一个进程,IDE 通常会通过提供特殊的测试运行程序来轻松调试测试)

缺点

必须在测试用例中自己编写容器启动/停止代码

我希望以上内容可以帮助您了解您拥有的选项。可能还有其他调整,但总的来说,以上被认为是目前与 Maven 进行集成测试的最佳实践。

【讨论】:

这是美丽的回应。可惜我只能投一票;-) +1 我在一段时间内看到的关于 SO 的最佳答案之一。 很多项目都使用该配置文件名称,因此它已成为惯例......尤其是因为它是我们在 maven 项目本身中使用的惯例 Failsafe 和 integration-test/verify plus Eclipse:在我们的例子中,我们通常从 Eclipse 本地运行我们的应用服务器,并在我们的集成测试中使用注解 @RunWith(SpringJUnit4ClassRunner.class),这似乎足够Eclipse 来识别我们的单元测试。 有没有很好的例子说明如何实现“2.5”? “缺点”:必须在测试用例中自己编写容器启动/停止代码正是我想要做的,但我找不到说明如何做到这一点的指南。【参考方案2】:

@Stephen Connolly - 你上面的回答非常好。我想我会开始为您所谓的School 1 响应显示完整配置。

这个配置:

独立于集成测试运行单元测试。它在单元测试和集成测试扩展的根类上使用 @Category 注释。 在集成测试之前,它通过查找开放端口在本地计算机上启动依赖应用程序(在运行时作为 maven 依赖项加载) 在集成测试之后,它会拆除依赖的应用程序

其中还有其他内容,例如如何仅在依赖应用程序上设置某些系统属性。

到目前为止,这个配置运行得非常好..

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>1.9.1</version>
            <executions>
                <execution>
                    <id>reserve-network-port</id>
                    <goals>
                        <goal>reserve-network-port</goal>
                    </goals>
                    <phase>pre-integration-test</phase>
                    <configuration>
                        <portNames>
                            <portName>tomcat.maven.http.port</portName>
                        </portNames>
                    </configuration>
                </execution>
                <execution>
                    <id>get-local-ip</id>
                    <goals>
                        <goal>local-ip</goal>
                    </goals>
                    <configuration>
                        <!-- if not given, 'local.ip' name is used -->
                        <localIpProperty>local.ip</localIpProperty>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <!-- http port from reserve-network-port-plugin-->
                <port>$tomcat.maven.http.port</port>
                <!-- application path always starts with /-->
                <path>/</path>
                <webapps>
                    <webapp>
                        <groupId>com.company.other.app</groupId>
                        <artifactId>web-rest</artifactId>
                        <version>1.0.1-SNAPSHOT</version>
                        <type>war</type>
                        <contextPath>/webapi-loopback</contextPath>
                        <asWebapp>true</asWebapp>
                    </webapp>
                </webapps>
            </configuration>
            <executions>
                <execution>
                    <id>start-server</id>
                    <configuration>
                        <fork>true</fork>
                        <skip>$skipTests</skip>
                        <systemProperties>
                            <spring.profiles.active>test,h2</spring.profiles.active>
                        </systemProperties>
                    </configuration>
                    <phase>pre-integration-test</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
                <execution>
                    <id>stop-server</id>
                    <configuration>
                        <skip>$skipTests</skip>
                    </configuration>
                    <phase>post-integration-test</phase>
                    <goals>
                        <goal>shutdown</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.19</version>
            <configuration>
                <excludedGroups>com.company.app.service.IntegrationTestRootClassAnnotatedWithAtCategory</excludedGroups>
            </configuration>
            <executions>
                <execution>
                    <id>unit-test</id>
                    <phase>test</phase>
                    <goals>
                        <goal>test</goal>
                    </goals>
                    <configuration>
                        <argLine>-Xmx1024m -XX:MaxPermSize=256m @jacocoArgLine</argLine>
                        <excludedGroups> com.company.app.service.IntegrationTestRootClassAnnotatedWithAtCategory </excludedGroups>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.18</version>
            <dependencies>
                <dependency>
                    <groupId>org.apache.maven.surefire</groupId>
                    <artifactId>surefire-junit47</artifactId>
                    <version>2.18</version>
                </dependency>
            </dependencies>
            <executions>
                <execution>
                    <id>start-integration-test</id>
                    <phase>integration-test</phase>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                    <configuration>
                        <argLine>-Xmx1024m -XX:MaxPermSize=256m @jacocoArgLine</argLine>
                        <groups>com.company.app.IntegrationTestRootClassAnnotatedWithAtCategory</groups>
                        <includes>
                            <include>**/*.java</include>
                        </includes>
                        <systemPropertyVariables>
                            <program.service.url>
                                http://$local.ip:$tomcat.maven.http.port/webapi-loopback
                            </program.service.url>
                        </systemPropertyVariables>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

【讨论】:

【参考方案3】:

正如 Stephen Connolly 解释的那样,没有直接的方法来配置它。我将解释如何使用故障安全插件解决这个问题。在maven生命周期类型的测试中可以进行测试。其中一个是单元测试,另一个是集成测试。单元测试可以在 maven 生命周期的测试阶段运行。当您想做集成测试时,可以在验证阶段完成。如果您想知道单元测试和集成测试之间的区别,这是good one。默认情况下,单元测试类应该是 ***/*Test.java**/*TestCase.java 这种格式。故障保护插件将查找 **/IT*.java**/*IT.java**/*ITCase.java

这是一个例子。

这里我有一个单元测试类和一个集成测试类。现在我将解释一下,maven pom.xml 的样子应该如何。 Maven 配置的构建部分应如下所示。

<build>
    <plugins>
        <plugin>
            <artifactId>maven-war-plugin</artifactId>
            <version>2.3</version>
            <configuration>
                <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
                <warName>$name</warName>
                <outputDirectory>/home/jobs/wso2/wso2as-5.3.0/repository/deployment/server/webapps</outputDirectory>
                <goal>
                </goal>
            </configuration>
        </plugin>

        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.7</source>
                <target>1.7</target>
            </configuration>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.12.4</version>
            <executions>
                <execution>
                    <id>integration-test</id>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>

在部署 Web 应用程序(war 文件)之前运行单元测试。但是集成测试在验证阶段运行。我希望您在这个阶段的要求得到满足。

【讨论】:

以上是关于Maven - 在 JUnit 测试之前将 webapp 部署到 tomcat的主要内容,如果未能解决你的问题,请参考以下文章

在 Maven/Junit/DBUnit 项目的集成测试之前/之后创建/删除数据库的最佳方法?

如何从 jUnit 测试启动 Maven 过滤?

maven项目的单元测试junit配置文件加载不到

如何使用 JVM 参数在终端中通过 maven 运行 junit 测试

从Eclipse中的Maven项目调试Junit测试

如何使用 Java 启动和停止 Tomcat 容器?