Java 如何在Spring Boot中测试组件/bean

Java 如何在Spring Boot中测试组件/bean,java,unit-testing,spring-boot,junit,mockito,Java,Unit Testing,Spring Boot,Junit,Mockito,要在Spring Boot应用程序中测试组件/bean,提供了许多信息和多种方法: @Test,@springbootest,@WebMvcTest,@DataJpaTest,还有许多其他方法。 为什么要提供这么多方式? 如何决定青睐的方式? 如果我认为是集成测试,我的测试类用Spring BooT测试注释来注释,比如 @ Spring BooTest< , @ WebMVCtest, @ DATAjPATest < /COD>? PS:我之所以提出这个问题,是因为我注意到许多开发人员(甚至有经

要在Spring Boot应用程序中测试组件/bean,提供了许多信息和多种方法:
@Test
@springbootest
@WebMvcTest
@DataJpaTest
,还有许多其他方法。
为什么要提供这么多方式? 如何决定青睐的方式?
如果我认为是集成测试,我的测试类用Spring BooT测试注释来注释,比如<代码> @ Spring BooTest< <代码>,<代码> @ WebMVCtest,<代码> @ DATAjPATest < /COD>? PS:我之所以提出这个问题,是因为我注意到许多开发人员(甚至有经验的开发人员)都不知道使用注释而不是其他注释的后果

TL-DR
  • 为组件编写简单的单元测试,无需加载Spring容器即可直接测试这些组件(在本地和CI构建中运行它们)

  • 为不加载Spring容器就无法直接测试的组件编写部分集成测试,例如与JPA、控制器、REST客户端、JDBC相关的组件。。。(在本地和CI内部版本中运行它们)

  • 为一些带来价值的高级组件编写一些完整集成测试(端到端测试)(在CI构建中运行)


测试组件的3种主要方法
  • 普通单元测试(不加载弹簧容器)
  • 完全集成测试(加载包含所有配置和bean的Spring容器)
  • 部分集成测试/测试切片(加载具有非常有限的配置和bean的Spring容器)
是否可以用这三种方法测试所有部件? 一般来说,使用Spring可以在集成测试中测试任何组件,并且只有某些类型的组件适合单独测试(无容器)。
但请注意,无论是否使用spring,酉测试和集成测试都不是对立的,而是互补的

如何确定部件是否可以进行普通测试(无弹簧)或仅使用弹簧进行测试? 由于组件/方法不使用Spring功能来执行其逻辑操作,因此您可以识别一个测试代码,该代码不具有来自Spring容器的任何依赖项。
在前面的示例中,
FooService
执行一些不需要Spring执行的计算和逻辑。
实际上,无论是否使用容器,
compute()
方法都包含我们想要断言的核心逻辑。
相反,如果没有Spring,您将很难测试
FooRepository
,因为Spring Boot为您配置了数据源、JPA上下文,并为
FooRepository
接口提供了默认实现和其他多项功能。
测试控制器(rest或MVC)也一样。
如果没有Spring,控制器如何绑定到端点?没有Spring,控制器如何解析HTTP请求并生成HTTP响应?这根本无法做到

1) 编写简单的单元测试 在应用程序中使用Spring Boot并不意味着您需要为运行的任何测试类加载Spring容器。
当您编写不需要来自Spring容器的任何依赖项的测试时,您不必在测试类中使用/加载Spring。
不使用Spring,您将自己实例化要测试的类,如果需要,使用模拟库将被测试的实例与其依赖项隔离开来。
这是应该遵循的方法,因为它速度快,有利于隔离被测组件。
例如,注释为Spring服务的
FooService
执行一些计算并依赖
foosrepository
检索一些数据,可以在没有Spring的情况下进行测试:

@Service
public class FooService{
   private FooRepository fooRepository;

   public FooService(FooRepository fooRepository){
       this.fooRepository = fooRepository;
   }

   public long compute(...){
      List<Foo> foos = fooRepository.findAll(...);
       // core logic
      long result = 
           foos.stream()
               .map(Foo::getValue)
               .filter(v->...)
               .count();
       return result;
   }
}
2) 编写完全集成测试 编写端到端测试需要加载包含整个配置和应用程序bean的容器。
实现这一目标的途径是:

注释通过创建应用程序中使用的ApplicationContext来工作 通过SpringApplication进行测试

您可以这样使用它来测试它,而无需任何模拟:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;

@SpringBootTest
public class FooTest {

   @Autowired
   Foo foo;

   @Test
   public void doThat(){
      FooBar fooBar = foo.doThat(...);
      // assertion...
   }    

}
但如果有意义,您也可以模拟容器的一些bean:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

@SpringBootTest
public class FooTest {

   @Autowired
   Foo foo;

   @MockBean
   private Bar barDep;

   @Test
   public void doThat(){
      Mockito.when(barDep.doThis()).thenReturn(...);
      FooBar fooBar = foo.doThat(...);
      // assertion...
   }    

}
注意模拟的区别,因为您想要模拟
Bar
类(
org.mockito.mock
annotation)的普通实例,并且想要模拟Spring上下文的
Bar
bean(
org.springframework.boot.test.mock.mockito.MockBean
annotation)

完整集成测试必须由CI构建执行 加载完整的spring上下文需要时间。因此,您应该谨慎使用
@SpringBootTest
,因为这可能会导致单元测试执行时间过长,并且通常您不希望严重减慢开发人员机器上的本地构建和测试反馈,这对于使测试编写愉快且高效对开发人员非常重要。
这就是为什么“慢”测试通常不会在开发人员的机器上执行。
因此,您应该让它们进行集成测试(
IT
后缀,而不是测试类命名中的
Test
后缀),并确保这些测试仅在连续集成构建中执行。
但是,由于Spring Boot作用于应用程序中的许多事情(rest控制器、MVC控制器、JSON序列化/反序列化、持久性等等…),您可以编写许多只在CI构建上执行的单元测试,这也不好。
仅在CI构建上执行端到端测试是可以的,但仅在CI构建上执行持久性、控制器或JSON测试则完全不可以。
事实上,开发人员的构建速度很快,但作为缺点,在本地执行测试只会检测到一小部分可能的回归…
为了防止这种警告,Spring Boot提供了一种中间方法:部分集成测试或切片测试(他们称之为切片测试):下一点

3) Wri
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

@SpringBootTest
public class FooTest {

   @Autowired
   Foo foo;

   @MockBean
   private Bar barDep;

   @Test
   public void doThat(){
      Mockito.when(barDep.doThis()).thenReturn(...);
      FooBar fooBar = foo.doThat(...);
      // assertion...
   }    

}
@SpringBootTest(properties="spring.main.lazy-initialization=true")
public class MyServiceTest { ...}