Java 如何在Spring Boot中测试组件/bean
要在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:我之所以提出这个问题,是因为我注意到许多开发人员(甚至有经
@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容器的任何依赖项。
在前面的示例中,
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 { ...}