Maven 下多个 spring 引导应用程序的端到端集成测试

End to end integration test for multiple spring boot applications under Maven

运行在Maven构建的验证阶段对多个Spring启动应用程序进行端到端集成测试的推荐方法是什么?

基本上,我有一个多模块 Maven 项目,其中几个模块是独立的 spring 启动应用程序。这些单独的应用程序有自己的数据源配置、与 JMS 队列的集成流等。例如,应用程序 A 将轮询数据库中的事件,当事件发生时,它会生成一个 JSON 文件数据并将消息放入 JMS 队列。应用程序 B 正在轮询 JMS 队列,因此获取消息、读取文件、使用另一个数据库进行一些处理,然后将消息放入不同的队列。然后应用程序 C 将接收该消息,等等。

我已经为各个应用程序设置了集成测试;这些 运行 在 Maven 故障安全插件下。但是,我想在 Maven 下对整个系统进行端到端的集成测试。我已经在专用于此任务的项目中设置了一个单独的模块,因此希望此模块的验证构建阶段使用其他依赖模块进行端到端测试。

是否有执行此操作的最佳实践方法?我看到 3 种可能的方法:

  1. 将每个应用程序的配置加载到相同的应用程序上下文中。然而,由于多个数据源等,这会产生冲突,因此这些数据源都必须手动配置才能启用端到端集成测试 - 所以这对我来说似乎是错误的。
  2. 将每个应用程序作为一个单独的进程启动 - 那么如何正确地跟踪它们并确保它们在测试模块构建时被关闭 stops/crashes/etc?
  3. 有没有办法在同一进程中轻松加载单独的 spring 引导应用程序,每个应用程序都有自己的配置上下文?这似乎是最明智的选择。 Maven build/failsafe插件有什么注意事项吗?

非常好的问题!我自己会对其他人的回答感兴趣。我会分享我的意见。

以我的理解,首先你应该知道你到底想测试什么。 集成测试应该与至少一部分的应用程序一起工作,并确保您开发的组件在 semi-real 环境中正常工作。看来你已经做到了。

现在,关于系统测试(我有意区分集成测试和系统测试)。这些应该 'mimic' QA 人员 :) 因此,他们将系统视为黑匣子。他们不能调用任何内部 API 和 运行 真实流程。 End-to-end 测试 IMO 属于此类。

在这种情况下,您希望根据生产中部署的系统检查它们,并使用生产中的类路径。

所以我和你一样不太相信选项 1。

关于选项 3,我不确定它是否也是一个好的解决方案。 即使你 运行 你的东西有不同的应用程序上下文(我不太了解 Spring 启动所以我不能在技术上评论它),据我所知,它们将在 运行 时间,所以你可能有在第三方之间发生冲突的风险(虽然我知道 spring 引导本身定义了很多版本的 jar,你知道我的意思)特别是当你只升级时一个模块,可能会改变依赖关系。 因此,当您 运行 遵循这种方法时,您实际上并不知道内存中的 运行 到底是什么。

因此,对于 end-to-end 测试,我会选择选项 2。 关于同步,可能的选择是在应用程序级别实现一些逻辑,并结合操作系统级别的进程状态跟踪。 还有一点我想评论的是 end-to-end 测试通常仍然是功能测试(它们检查系统的功能行为)所以通常你不应该在每个测试中检查系统崩溃。如果您检查每个流程的系统崩溃,这些测试将太慢。 当然,您可以维护一个相对较小的测试套件来检查极端情况。

希望对您有所帮助

只是为了跟进并说出我最终做了什么(它继续运作良好):

  • 我在我的测试模块中创建了以下 Maven 配置文件:"default" 默认跳过测试(我们使用 jgitflow 插件所以只需要端到端测试 运行明确要求),"standalone-e2e" 用于不需要外部资源(如数据库)的端到端测试(针对想要进行完整端到端测试的开发人员),"integrated-e2e" 用于端到端测试- 使用真实数据库等结束测试(可以作为 CI 的一部分触发)。 Spring 配置文件(由相应的 Maven 配置文件激活)控制各个组件的配置。
  • 对于standalone-e2e,相关插件如activemq-maven-plugin, hsqldb-maven-plugin etc. launch (and later shut down) resources as part of the end-to-end test, running on ports reserved with build-helper-maven-plugin. The process-exec-maven-plugin is used to launch all the components to be tested in the pre-integration-test phase (as standard Spring Boot apps), and it automatically takes care of shutting them down in the post-integration-test phase. Spring configuration and specific Maven test dependencies take care of other resources such as a fake FTP server。在所有资源和组件 运行ning 之后,测试代码本身会根据需要填充数据库和文件系统,并使用 JMS 触发流程(并等待相应的回复等)。
  • 集成端到端配置文件几乎相同,但使用 "real" 在关联的 Spring 属性中配置的外部资源(在我们的例子中,Amazon SQS 队列、MySQL 数据库等) .
  • 测试所需和生成的所有文件(例如数据文件、HSQLDB 文件、日志文件等)都在 "target" 构建目录下创建,因此很容易检查此区域以查看发生了什么在测试期间,还允许 "mvn clean" 清除所有内容。

我希望这很有用 - 发现无论我需要做什么,都有一个 Maven 插件来处理它,这肯定令人耳目一新!

由于某些原因,我决定使用第二种方法并且有类似的问题需要解决。我想出了 maven-dependency-plugin and process-exec-maven-plugin.

的组合

所以假设我们有一些应用程序(例如 app_Aapp_B、...)和实施测试的模块 T

想法是在开始测试之前将应用程序复制到本地构建目录并启动它们。测试后 运行 应用程序将停止。

为了实现这一点,我在模块 Ts pom 中添加了以下 build-configurations

<build>
  ...
  <plugin>
    <!-- copy apps to current build-dir -->
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>3.1.2</version>
    <executions>
      <execution>
        <id>copy-app-A</id>
        <goals>
          <goal>copy</goal>
        </goals>
        <phase><!-- either pre-test or pre-integration-test --></phase>
        <configuration>
          <artifactItems>
            <artifactItem>
              <groupId>${project.groupId}</groupId>
              <artifactId>app_a</artifactId>
              <version>${project.version}</version>
              <outputDirectory>${project.build.directory}</outputDirectory>
              <destFileName>app_a.jar</destFileName>
            </artifactItem>
          </artifactItems>
        </configuration>
      </execution>
      <execution><!-- repeat as many apps you have --></execution>
    </executions>
  </plugin>
  <plugin>
    <!-- start applications -->
    <groupId>com.bazaarvoice.maven.plugins</groupId>
    <artifactId>process-exec-maven-plugin</artifactId>
    <version>0.9</version>
    <executions>
      <execution>
        <!-- repeat 'start' block for each copied app -->
        <id>start-external-server</id>
        <phase><!-- same as in copy block --></phase>
        <goals>
          <goal>start</goal>
        </goals>
        <configuration>
          <name>run-app-A</name>
          <arguments>
            <argument>java</argument>
            <argument>-jar</argument>
            <argument>${project.build.directory}/app_a.jar</argument>
          </arguments>
        </configuration>
      </execution>
      <execution>
        <!-- this is always executed and ensures apps get stopped -->
        <id>stop-server</id>
        <phase><!-- either post-test or post-integration-test depending on --></phase>
        <goals>
          <goal>stop-all</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
  ...
</build>