【问题标题】:Spring-boot multi-module configuration and test isolationspring-boot多模块配置和测试隔离
【发布时间】:2020-03-03 16:01:32
【问题描述】:

我正在开发一个在 Maven 模块中拆分的应用程序,如下所示:

  • myApp-父级
    • 框架
    • 模块1
      • 坚持
      • 服务
    • 模块2
      • 坚持
      • 服务

第一部分:持久层

对于 module1/persistence,源位于包 org.company.myApp.module1.persistence 中,我有以下配置类:

package org.company.myApp.module1.persistence;

import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.ldap.repository.config.EnableLdapRepositories;

import org.company.myApp.framework.FrameworkConfiguration;

@Configuration
@Import(FrameworkConfiguration.class)
@EntityScan
@ComponentScan
@EnableLdapRepositories("org.company.myApp.module1.persistence.ldap")
@EnableJpaRepositories("org.company.myApp.module1.persistence.db")
public class Module1PersistenceConfiguration {

}

第一个问题:我在这里使用了@Configuration。我没有放@SpringBootApplication,因为持久化不是一个独立的应用程序,而只是一种库。不过,我应该在这里使用@SpringBootConfiguration 而不是@Configuration 吗?

看起来@SpringBootConfiguration 只是@Configuration 的别名,但尽管我阅读了很多文章,但我仍然不清楚。

文档说明:

表示一个类提供了Spring Boot应用@Configuration。可以用作 Spring 标准 @Configuration 注释的替代品,以便可以自动找到配置(例如在测试中)。

应用程序应该只包含一个@SpringBootConfiguration,大多数惯用的 Spring Boot 应用程序都会从​​ @SpringBootApplication 继承它。

由于所有模块的最终应用程序将包含多个@SpringBootConfiguration,因此我选择了简单的@Configuration

第二部分:持久层测试

测试源在同一个包中(当然在src/test/java下),我做了一个测试特定的配置类:

package org.company.myApp.module1.persistence;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.ldap.LdapProperties;
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.ldap.core.support.LdapContextSource;

@SpringBootApplication
public class Module1PersistenceTestConfiguration extends Module1PersistenceConfiguration {

}

extends Module1PersistenceConfiguration 在这里似乎没用,因为两个类在同一个包中,扫描会检测并加载主配置。

@SpringBootApplication 允许我加载 Spring 上下文,因为我使用 @SpringBootTest 进行的测试会检测到此类。

第二个问题:如果我用@SpringBootConfiguration注释主要配置,Module1PersistenceTestConfiguration会没用吗?这里的正确方法是什么?

第三部分:服务层

配置类:

package org.company.myApp.module1.service;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import org.company.myApp.module1.persistence.Module1PersistenceConfiguration;

@Configuration
@ComponentScan
@Import(Module1PersistenceConfiguration.class)
public class Module1ServiceConfiguration {

}

由于服务层和持久层没有同一个包,所以我使用@Import(Module1PersistenceConfiguration.class)触发持久层的扫描。

第三个问题:虽然我觉得这很舒服,但也许有更好的选择?服务层应该知道持久层并通过将相应的basePackages 属性添加到@ComponentScan 来执行完整扫描?

最后部分:服务层测试

这是我苦苦挣扎的部分。 我想通过模拟持久层来测试服务层,我设法做到这一点的唯一方法是添加一个排除主配置类的测试配置类,以防止spring尝试加载持久层(由于@ 987654342@ 在主配置中)并失败,因为属性中没有数据源配置(持久性测试资源包含 application.properties 以便使用 H2 嵌入式数据库和 unboundid 嵌入式 LDAP):

package org.company.myApp.module1.service;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;

@SpringBootApplication
@ComponentScan(excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value = Module1ServiceConfiguration.class))
public class Module1ServiceTestConfiguration {

}

此外,我不得不为每个测试模拟来自持久层的所有 bean。

最后一个问题:实现这一目标的正确方法是什么? 答案可能是:对您的服务进行单元测试或进行包含持久层的集成测试,但您的半集成测试没有任何意义?

【问题讨论】:

    标签: spring spring-boot


    【解决方案1】:

    我没有问,但在这个答案中,我假设您在谈论 maven 模块而不是 java 模块。如果您谈论的是 Java 模块,那么大多数情况仍然是正确的,但可能不是全部。

    您有很多问题,我不确定我是否能全部回答,但我可以与您分享我的 SpringBootTest 上下文控制策略。

    通常我只在有一些我想检查的特定配置是否正确加载或测试控制器或客户端时才编写 SpringBootTests。对于其他任何事情,我都会编写常规的单元测试(是的,当它们是服务时)。将测试上下文保持在您要测试的类的小范围和本地。仅将特定测试真正需要的那些 bean 加载到上下文中。如果您不控制测试上下文,则维护它们可能会变得很麻烦。不要创建一个庞大的 TestConfiguration。因为在某些情况下需要模拟某些 bean,而在其他情况下您不想模拟它们。这可能成为一场绝对的噩梦

    一般我的SpringBootTests设置如下:

    @SpringBootTest(classes = MyService.class)
    @Import(MyServiceTestConfiguration.class)
    class MyServiceTest {
       // test logic
    }
    
    @TestConfiguration
    class MyServiceTestConfiguration.class {
    
        @MockBean
        BeanINeedMocked beanINeedMocked;
        // or whatever other strategy you want to use to create a mocked bean
    }
    

    通过在@SpringBootTest 下定义类,它不会在类路径上寻找@SpringBootApplication。加载测试时,Spring 上下文将加载提到的类(我相信同一包中的任何其他 Bean)。如果您需要任何其他非模拟 bean 来加载应用程序上下文,请将它们包含在 @SpringBootTest(classes = {MyService.class, WhatEverOtherBeanYouNeed.class} 但只加载您需要测试的任何内容!

    这样,您的应用程序中的所有 springboottests 都不需要 OneTestConfigurationToRuleThemAll.class,而且每当您向应用程序添加新的 spring bean 时,您都不会有 3000 个测试失败。

    最后两点: 确保您的 SpringBootApplication 类位于应用程序的根目录(所有模块共享的根路径)。这样,您就不必使用 @ComponentScan 或类似的东西。只要所有@Configuration、@Service、@Component 都有SpringBootApplication所在的完整路径,都会自动扫描加载。

    如果您正确执行此操作,并且您的所有 maven 模块都正确连接,您将不必从其他模块导入配置。只要模块依赖于它需要 spring bean 的模块,它就应该开箱即用地自动装配。

    另外,如果你使用@SpringBootApplication,我不会在任何地方使用@SpringBootConfiguration,而是坚持使用@Configuration。

    【讨论】:

    • 我尝试了嵌套的@TestConfiguration 方法,但据我记得它并没有完全解决我的问题。您能否提供一个简单的工作示例?
    • 我基本上已经做过了,我所有的测试看起来都像上面的例子。确保将要测试的类作为类添加到 SpringBootTest 注释中,否则它将向上扫描类路径以查找 SpringBootApplication 注释并加载比您想要的更多的上下文。通常有一些小事情,具体到你的情况,阻止它立即工作,你需要弄清楚。一旦掌握了这一点,您就可以设置任何测试。请一次尝试一堂课,然后回来解决具体问题。
    • 还要确保您遵守有关正确设置和连接 Spring Boot 应用程序的其他说明。一旦你开始使用组件扫描或到处导入配置,它可能会变得一团糟,可能会导致 Spring Boot 测试等基本设置失败。
    • 为什么要加载 Spring 上下文进行单元测试?如果我需要对一个类进行单元测试,我只需使用@ExtendWith(MockitoExtension.class) 并使用@Mock@InjectMocks。实际上,我认为我正在寻找一个功能,例如:“在我的 spring 上下文中模拟所有不满足的依赖项,让我在我的测试类中自动装配模拟,以便我可以定义它们的行为”。
    • 关于spring上下文,你的意思是好的做法是只在应用程序模块中设置配置?假设我有一个模块 rest-controller 通过 REST API 公开一些服务,它应该有一个 @SpringBootApplication 扫描它所依赖的所有模块?所有其他模块都不应该有@Configuration 类?
    猜你喜欢
    • 1970-01-01
    • 2018-04-15
    • 1970-01-01
    • 1970-01-01
    • 2019-03-29
    • 1970-01-01
    • 2020-01-30
    • 2021-02-16
    • 1970-01-01
    相关资源
    最近更新 更多