当前位置:   article > 正文

Spring Boot 单元测试最佳实践_单元测试需要启动整个项目吗

单元测试需要启动整个项目吗

标题党,各位大佬手下留情~_~

单测是十分重要的,既能提升代码健壮性,又能降低代码重构的风险;但在当下国内环境中,单测又是不现实的,单测耗费的时间可能比开发还多,这对老板来说显然是不能接受的(万恶的资本家);关键业务缺少单测不仅提高了测试回归的难度,也成为了代码重构的拦路虎,看着那一堆屎山代码,要是没有单测的保障,你敢去重构吗!!(不怕死的当我没说)

!! 笔者使用的是 2.6.6 版本

SpringBoot 常规单元测试

常规的单元测试如下图所示,这样会将整个容器启动起来,需要加载各种各样的外部化配置,耗时时间长且容易失败;大部分场景下我们只是测试某个功能,只需加载部分组件即可

SpringBoot 单元测试指定加载配置

为了解决以上问题,我们可以指定配置进行加载,避免加载整个容器;如下图所示,只会加载基础的Spring容器以及IdGenerator,大大提升了单测的效率

推荐学习spring-test-examples[1]

SprongBoot 固定组件单元测试

以上指定配置加载已经基本满足了我们的需求(加载部分组件);但在日常开发中,要求每次单测都指定加载的配置本身就是个伪命题,一是因为本身开发可能对于需要加载的配置不太熟悉,二是因为这种重复的工作过于啰嗦;那么我们该怎么优化这个流程呢?

  1. 要简化配置,第一步就是禁用所有自动加载的配置

  • 仿造SpringBootTest的注解,构建一个元注解,禁用所有自动加载的配置

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @BootstrapWith(EmptyTestContextBootstrapper.class)
  6. @ExtendWith(SpringExtension.class)
  7. @OverrideAutoConfiguration(enabled = false)
  8. @TypeExcludeFilters(EmptyTypeExcludeFilter.class)
  9. public @interface TestEmptyEnvironment {
  10.     String[] properties() default {};
  11.     boolean useDefaultFilters() default true;
  12.     ComponentScan.Filter[] includeFilters() default {};
  13.     ComponentScan.Filter[] excludeFilters() default {};
  14. }
  15. public class EmptyTestContextBootstrapper extends SpringBootTestContextBootstrapper {
  16.     @Override
  17.     protected String[] getProperties(final Class<?> testClass) {
  18.         final TestEmptyEnvironment annotation = AnnotatedElementUtils.getMergedAnnotation(testClass, TestEmptyEnvironment.class);
  19.         return (annotation != null) ? annotation.properties() : null;
  20.     }
  21. }
  22. public class EmptyTypeExcludeFilter extends AnnotationCustomizableTypeExcludeFilter {
  23.     private final TestEmptyEnvironment annotation;
  24.     EmptyTypeExcludeFilter(final Class<?> testClass) {
  25.         this.annotation = AnnotatedElementUtils.getMergedAnnotation(testClass, TestEmptyEnvironment.class);
  26.     }
  27.     @Override
  28.     protected boolean hasAnnotation() {
  29.         return this.annotation != null;
  30.     }
  31.     @Override
  32.     protected ComponentScan.Filter[] getFilters(final FilterType type) {
  33.         switch (type) {
  34.             case INCLUDE:
  35.                 return this.annotation.includeFilters();
  36.             case EXCLUDE:
  37.                 return this.annotation.excludeFilters();
  38.             default:
  39.                 throw new IllegalStateException("Unsupported type " + type);
  40.         }
  41.     }
  42.     @Override
  43.     protected boolean isUseDefaultFilters() {
  44.         return this.annotation.useDefaultFilters();
  45.     }
  46.     @Override
  47.     protected Set<Class<?>> getDefaultIncludes() {
  48.         return Collections.emptySet();
  49.     }
  50.     @Override
  51.     protected Set<Class<?>> getComponentIncludes() {
  52.         return Collections.emptySet();
  53.     }
  54. }
  • TestEmptyEnvironment: 禁用所有自动配置,只加载最基础的spring容器

  • EmptyTestContextBootstrapper: 重写properties加载方法,将TestEmptyEnvironment注解中的properties属性加载到容器中

  • EmptyTypeExcludeFilter: 容器过滤

  • @OverrideAutoConfiguration(enabled = false): 禁用自动配置加载,如果是boot2.2.x之前的版本,此配置不会生效,可以使用 @ContextConfiguration(classes = EmptyConfiguration.class) 替代,其中 EmptyConfiguration 表示空的配置

  1. 要简化单测的流程,就需要将重复的工作声明化,即使用注解完成自动配置的大部分工作;具体需要如何处理呢,我们可以将常用单测注解进行声明化处理,编写单测时只需引入对应组件的注解即可

  • service 仅对service进行单测,可声明以下注解,构建一个简单的spring容器即可,需要测试哪个service,直接Import加载即可;若此service中有其他注解,可进行mock处理,这里不再赘述mock的使用

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @TestEmptyEnvironment
  6. public @interface TestService {
  7. }
  8. @TestService
  9. @Import(value = {
  10.         LabelService.class
  11. })
  12. public class SimpleServiceTest {
  13.     @Autowired
  14.     private LabelService labelService;
  15. }
  • redis 对redis进行单测,需要引入redis相关的自动配置,如下代码中的 RedisTestAutoConfiguration 类,不同项目使用的框架不同,自动装配也不相同,这里需要根据项目进行个性化设置

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @TestEmptyEnvironment
  6. @ImportAutoConfiguration(classes = {
  7.         RedisTestAutoConfiguration.class
  8. })
  9. public @interface TestRedis {
  10. }
  11. @Configuration
  12. @ImportAutoConfiguration(classes = {
  13.         LettuceAutoConfiguration.class
  14. })
  15. public class RedisTestAutoConfiguration {
  16. }
  17. @TestRedis
  18. @TestPropertySource(properties = {
  19.         "redis.host=localhost:6379"
  20. })
  21. public class SimpleRedisTest {
  22.     @Autowired
  23.     private RedisClient redisClient;
  24.     @Test
  25.     public void test_getRedisHost() {
  26.         assertThat(redisClient)
  27.         .isNotNull();
  28.     }
  29. }
  • kafka kafka单测也和redis一样,进行个性化配置即可

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @TestEmptyEnvironment
  6. @ImportAutoConfiguration(classes = {
  7.         KafkaTestAutoConfiguration.class
  8. })
  9. public @interface TestKafka {
  10. }
  11. @Configuration
  12. @ImportAutoConfiguration(classes = {
  13.         KafkaAutoConfiguration.class
  14. })
  15. public class KafkaTestAutoConfiguration {
  16. }
  • 其他组件也都是一样的做法,笔者暂时用到的组件如下如所示

tips: 如果不知道组件需要加载哪些配置,可通过完整启动项目打印所有装配的配置,然后再筛选需要的即可

  1. @Component
  2. public class LoaderPrint implements CommandLineRunner {
  3.     @Autowired
  4.     private ApplicationContext applicationContext;
  5.     @Override
  6.     public void run(String... args) throws Exception {
  7.         Arrays.stream(applicationContext.getBeanDefinitionNames())
  8.                 .forEach(System.out::println);
  9.     }
  10. }

参考资料

[1]

https://github.com/chanjarster/spring-test-examples: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fchanjarster%2Fspring-test-examples

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/653900
推荐阅读
相关标签
  

闽ICP备14008679号