该版本仍在开发中,尚未被视为稳定。最新稳定版请使用Spring Modulith 2.0.0spring-doc.cadn.net.cn

集成测试应用模块

Spring Modulith 允许单独或与其他模块组合运行集成测试,启动单个应用模块。为此,可以像这样将 Spring Modulith 测试Starters添加到你的项目中spring-doc.cadn.net.cn

<dependency>
  <groupId>org.springframework.modulith</groupId>
  <artifactId>spring-modulith-starter-test</artifactId>
  <scope>test</scope>
</dependency>

并将 JUnit 测试类放入应用模块包或其任意子包中,并用@ApplicationModuleTest:spring-doc.cadn.net.cn

应用模块集成测试类
package example.order;

@ApplicationModuleTest
class OrderIntegrationTests {

  // Individual test cases go here
}
package example.order

@ApplicationModuleTest
class OrderIntegrationTests {

  // Individual test cases go here
}

这将运行你的集成测试,类似于@SpringBootTest本可以实现,但引导实际上仅限于测试所在的应用模块。 如果你配置了 的日志层级org.springframework.modulith调试你将看到测试执行如何自定义 Spring Boot 引导的详细信息:spring-doc.cadn.net.cn

应用模块集成测试引导的日志输出
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v3.0.0-SNAPSHOT)

… - Bootstrapping @ApplicationModuleTest for example.order in mode STANDALONE (class example.Application)…
… - ======================================================================================================
… - ## example.order ##
… - > Logical name: order
… - > Base package: example.order
… - > Direct module dependencies: none
… - > Spring beans:
… -       + ….OrderManagement
… -       + ….internal.OrderInternal
… - Starting OrderIntegrationTests using Java 17.0.3 …
… - No active profile set, falling back to 1 default profile: "default"
… - Re-configuring auto-configuration and entity scan packages to: example.order.

注意输出中包含测试运行中模块的详细信息。 它创建应用模块,找到要运行的模块,并将自动配置、组件和实体扫描的应用限制在对应的软件包内。spring-doc.cadn.net.cn

引导模式

应用模块测试可以通过多种方式启动:spring-doc.cadn.net.cn

处理传出性依赖关系

当应用模块被引导时,包含的 Spring 豆会被实例化。 如果这些模块包含跨模块边界的豆子引用,且测试运行中未包含这些其他模块,引导程序将失败(详见引导模式)。 虽然自然反应可能是扩展所包含的应用模块范围,但通常更适合模拟目标豆子。spring-doc.cadn.net.cn

其他应用模块中的 Mocking Spring Bean 依赖
@ApplicationModuleTest
class InventoryIntegrationTests {

  @MockitoBean SomeOtherComponent someOtherComponent;
}
@ApplicationModuleTest
class InventoryIntegrationTests {

  @MockitoBean SomeOtherComponent someOtherComponent
}

Spring Boot 会为定义的类型创建 bean 定义和实例@MockitoBean并将它们添加到应用上下文自力更生地进行测试运行。spring-doc.cadn.net.cn

如果你发现你的应用模块依赖过多其他模块,通常说明它们之间的耦合度很高。 应审查依赖是否适合通过发布域名事件来替代。spring-doc.cadn.net.cn

定义集成测试场景

集成测试应用模块可能成为一项相当复杂的工作。 尤其是当这些集成基于异步事务事件处理时,处理并发执行时可能会出现细微错误。 此外,这还需要处理相当多的基础设施组件:事务作应用事件处理器确保事件被发布并传递给事务监听者,Awaitility负责处理并发,AssertJ断言用于制定测试执行结果的预期。spring-doc.cadn.net.cn

为了简化应用模块集成测试的定义,Spring Modulith 提供了场景可通过声明为测试方法参数来使用抽象,测试声明为@ApplicationModuleTest.spring-doc.cadn.net.cn

使用场景JUnit 5测试中的API
@ApplicationModuleTest
class SomeApplicationModuleTest {

  @Test
  public void someModuleIntegrationTest(Scenario scenario) {
    // Use the Scenario API to define your integration test
  }
}
@ApplicationModuleTest
class SomeApplicationModuleTest {

  @Test
  fun someModuleIntegrationTest(scenario: Scenario) {
    // Use the Scenario API to define your integration test
  }
}

测试定义通常遵循以下骨架:spring-doc.cadn.net.cn

  1. 对系统的刺激被定义。这通常是事件发布或模块暴露的 Spring 组件调用。spring-doc.cadn.net.cn

  2. 可选择自定义执行的技术细节(如超时等)spring-doc.cadn.net.cn

  3. 定义某些预期结果,例如触发符合某些条件的另一个应用事件,或通过调用暴露组件检测到的模块状态变化。spring-doc.cadn.net.cn

  4. 可选的,对接收事件或观察到的状态变化进行额外验证。spring-doc.cadn.net.cn

场景它会提供一个 API 来定义这些步骤并引导你完成定义。spring-doc.cadn.net.cn

将刺激定义为场景
// Start with an event publication
scenario.publish(new MyApplicationEvent(…)).…

// Start with a bean invocation
scenario.stimulate(() -> someBean.someMethod(…)).…
// Start with an event publication
scenario.publish(MyApplicationEvent(…)).…

// Start with a bean invocation
scenario.stimulate(Runnable { someBean.someMethod(…) }).…

事件发布和Beans调用都会在交易回调中进行,以确保给定事件或Beans调用期间发布的任何事件都能传递给事务事件监听者。 注意,无论测试用例是否已经在事务中运行,这都需要启动新的事务。 换句话说,刺激触发的数据库状态变化永远不会被回滚,必须手动清理。 参见......和清理(...)为此目的的方法。spring-doc.cadn.net.cn

生成的对象现在可以通过通用......定制(...)方法或专门针对常见用例(如设置超时)的方案(......等一下,最多......).spring-doc.cadn.net.cn

准备阶段将通过定义刺激结果的实际预期来结束。 这可以是特定类型的事件,也可以选择匹配器进一步约束:spring-doc.cadn.net.cn

预期事件作为作结果发布
….andWaitForEventOfType(SomeOtherEvent.class)
 .matching(event -> …) // Use some predicate here
 .…
….andWaitForEventOfType(SomeOtherEvent.class)
 .matching(event -> …) // Use some predicate here
 .…

这些行设定了一个完成标准,最终执行将等待该条件继续执行。 换句话说,上述示例会导致执行最终阻塞,直到达到默认超时或别的事件与定义的谓词相匹配。spring-doc.cadn.net.cn

执行基于事件的终端作场景被命名为......到达......()并允许选择访问预期发布事件,或原始刺激中定义的豆子调用结果对象。spring-doc.cadn.net.cn

触发验证
// Executes the scenario
….toArrive(…)

// Execute and define assertions on the event received
….toArriveAndVerify(event -> …)
// Executes the scenario
….toArrive(…)

// Execute and define assertions on the event received
….toArriveAndVerify(event -> …)

单独看步骤时,方法名称的选择可能有点奇怪,但实际上组合起来相当顺畅。spring-doc.cadn.net.cn

一个完整的场景定义
scenario.publish(new MyApplicationEvent(…))
  .andWaitForEventOfType(SomeOtherEvent.class)
  .matching(event -> …)
  .toArriveAndVerify(event -> …);
scenario.publish(new MyApplicationEvent(…))
  .andWaitForEventOfType(SomeOtherEvent::class.java)
  .matching { event -> … }
  .toArriveAndVerify { event -> … }

除了作为预期完成信号的事件发布外,我们还可以通过调用暴露组件中的一个方法来检查应用模块的状态。 情景更可能如下:spring-doc.cadn.net.cn

预期状态变化
scenario.publish(new MyApplicationEvent(…))
  .andWaitForStateChange(() -> someBean.someMethod(…)))
  .andVerify(result -> …);
scenario.publish(MyApplicationEvent(…))
  .andWaitForStateChange { someBean.someMethod(…) }
  .andVerify { result -> … }

结果交给......并且验证(...)方法将是调用时返回的值,用于检测状态变化。 默认情况下,不是——值和非空自选s 将被视为决定性的状态变化。 这可以通过使用......等待状态变化(...,谓词)超载。spring-doc.cadn.net.cn

场景执行的定制化

要自定义单个场景的执行,请调用......定制(...)设置链中的方法场景:spring-doc.cadn.net.cn

定制场景执行
scenario.publish(new MyApplicationEvent(…))
  .customize(conditionFactory -> conditionFactory.atMost(Duration.ofSeconds(2)))
  .andWaitForEventOfType(SomeOtherEvent.class)
  .matching(event -> …)
  .toArriveAndVerify(event -> …);
scenario.publish(MyApplicationEvent(…))
  .customize { it.atMost(Duration.ofSeconds(2)) }
  .andWaitForEventOfType(SomeOtherEvent::class.java)
  .matching { event -> … }
  .toArriveAndVerify { event -> … }

全球范围内对所有内容进行定制场景测试类实例,实现场景定制器并注册为 JUnit 扩展。spring-doc.cadn.net.cn

注册场景定制器
@ExtendWith(MyCustomizer.class)
class MyTests {

  @Test
  void myTestCase(Scenario scenario) {
    // scenario will be pre-customized with logic defined in MyCustomizer
  }

  static class MyCustomizer implements ScenarioCustomizer {

    @Override
    Function<ConditionFactory, ConditionFactory> getDefaultCustomizer(Method method, ApplicationContext context) {
      return conditionFactory -> …;
    }
  }
}
@ExtendWith(MyCustomizer::class)
class MyTests {

  @Test
  fun myTestCase(scenario: Scenario) {
    // scenario will be pre-customized with logic defined in MyCustomizer
  }

  class MyCustomizer : ScenarioCustomizer {

    override fun getDefaultCustomizer(method: Method, context: ApplicationContext): UnaryOperator<ConditionFactory> {
      return UnaryOperator { conditionFactory -> … }
    }
  }
}

变更感知测试执行

自1.3版本起,Spring Modulith自带JUnit Jupiter扩展,可优化测试执行,因此未受项目更改影响的测试将被跳过。 为了实现该优化,请声明Spring-模块-朱尼特作为测试范围中的依赖的工件:spring-doc.cadn.net.cn

<dependency>
  <groupId>org.springframework.modulith</groupId>
  <artifactId>spring-modulith-junit</artifactId>
  <scope>test</scope>
</dependency>

如果测试位于根模块、发生变化的模块或传递依赖于已变化模块的模块中,则会被选中执行。 在以下情况下,优化会停止优化执行:spring-doc.cadn.net.cn

  • 测试执行源自一个集成开发环境(IDE),因为我们假设执行是显式触发的。spring-doc.cadn.net.cn

  • 该变更集包含与构建系统相关的资源变更(pom.xml,build.gradle(.kts),gradle.propertiessettings.gradle(.kts)).spring-doc.cadn.net.cn

  • 该变更集包含对任意类路径资源的变更。spring-doc.cadn.net.cn

  • 该项目完全没有任何更改(CI 构建中常见)。spring-doc.cadn.net.cn

为了优化配置项环境的执行,你需要填充Spring.Modulith.test.reference-commit属性指向上一个成功构建的提交,并确保构建检查了直到参考提交的所有提交。 检测应用模块变更的算法将考虑该差异中所有更改的文件。 要覆盖项目修改检测,声明 的实现文件修改检测器通过spring.modulith.test.file-modification-detector属性.spring-doc.cadn.net.cn