Spring boot多模块配置和测试隔离

Spring boot多模块配置和测试隔离,spring,spring-boot,Spring,Spring Boot,我正在开发一个在maven模块中拆分的应用程序,如下所示: myApp父级 框架 模块1 坚持 服务 模块2 坚持 服务 第一部分:持久层 对于module1/persistence,源代码位于包org.company.myApp.module1.persistence中,我有一个配置类: package org.company.myApp.module1.persistence; import org.springframework.boot.autoconfigure.do

我正在开发一个在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 {

}
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 {

}
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 {

}

第一个问题:我在这里使用了
@配置
。我没有把
@SpringBootApplication
放进去,因为持久性不是一个独立的应用程序,而是一种库。然而,我应该在这里使用
@SpringBootConfiguration
而不是
@Configuration

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

文件规定:

指示类提供Spring引导应用程序@配置。可以作为Spring的标准@Configuration注释的替代,以便可以自动找到配置(例如在测试中)

应用程序应该只包含一个@SpringBootConfiguration,大多数惯用的SpringBoot应用程序将从@SpringBootApplication继承它

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

第二部分:持久层测试

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

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 {

}
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 {

}
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 {

}

extensedmodule1persistenceconfiguration
在这里似乎没有用,因为这两个类都在同一个包中,扫描将检测并加载主配置

@springbootplication
允许我加载Spring上下文,因为我的测试使用
@springbootest
检测此类

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

第三部分:服务层

配置类:

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 {

}
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 {

}
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)
来触发持久层的扫描

第三个问题:尽管我对此感到满意,但也许还有更好的选择?服务层应该知道持久层,并通过向
@ComponentScan
添加相应的
basePackages
属性来执行完整扫描

最后一部分:服务层测试

这是我正在努力解决的问题。 我想通过模拟持久性层来测试服务层,唯一的方法是添加一个测试配置类,该类不包括主配置类,以防止spring尝试加载持久性层(由于主配置中的
@Import
)失败,因为属性中没有数据源配置(为了使用H2嵌入式数据库和未绑定的嵌入式LDAP,持久性测试资源包含application.properties):

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

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

我没有问,但在这个答案中,我假设您谈论的是maven模块,而不是java模块。如果您谈论的是Java模块,大多数仍然成立,但可能不是所有

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

通常,我只在需要检查某些特定配置是否正确加载,或者是否用于测试控制器或客户端时才编写SpringBootTests。对于其他任何东西,我都编写常规的单元测试(是的,当它们是服务时)。将测试上下文保持在小范围内,并且是要测试的类的本地上下文。只将特定测试真正需要的bean加载到上下文中。如果您不控制测试上下文,那么测试上下文可能会变成维护的地狱。不要创建一个大规模的测试配置。因为在某些情况下,某些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,whateOtherBeanYouneed.class}中,但只加载您需要测试的内容

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

最后两项说明: 确保SpringBootApplication类位于应用程序的根(所有模块共享的根路径)上