当前位置: 首页 > 知识库问答 >
问题:

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

汝墨一
2023-03-14

为了测试Spring Boot应用程序中的组件/bean,Spring Boot文档中的测试部分提供了大量信息和多种方法:@test@springboottest@webmvctest@datajpatest以及许多其他方法。
为什么要提供这么多方法呢?如何决定支持的方式?
我是否应该将使用Spring Boot test注解(如@springboottest@webmvctest@datajpatest)注解的测试类视为集成测试?

PS:我之所以提出这个问题,是因为我注意到许多开发人员(甚至有经验的开发人员)并没有得到使用注释而不是其他注释的后果。

共有1个答案

郑俊美
2023-03-14

>

  • 为无需加载Spring容器就可以直接测试的组件编写简单的单元测试(在本地和CI构建中运行它们)。

    为不加载Spring容器就无法直接测试的组件编写部分集成测试/切片单元测试,例如与JPA、控制器、REST客户机、JDBC相关的组件...(在本地和CI构建中运行它们)

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

      null
    @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;
       }
    }
    

    fooservice执行一些不需要Spring来执行的计算和逻辑。
    实际上,compute()方法包含我们想要断言的核心逻辑,无论有没有容器。
    相反,如果没有Spring,您将很难测试foorepository,因为Spring Boot为您配置数据源、JPA上下文,并为foorepository接口以提供默认实现和其他多种功能。
    测试控制器(rest或MVC)也是如此。
    没有Spring如何将控制器绑定到endpoint?如果没有Spring,控制器如何解析HTTP请求并生成HTTP响应?这根本做不到。

    在应用程序中使用Spring Boot并不意味着您需要为运行的任何测试类加载Spring容器。
    当您编写一个不需要来自Spring容器的任何依赖项的测试时,您不必在测试类中使用/加载Spring。
    您将自己实例化要测试的类,如果需要,则使用模拟库将被测试的实例与其依赖项隔离开来。
    这样做是因为它速度快,并且有利于被测试组件的隔离。
    这里是如何单元测试FooService类的介绍。
    您只需要模拟FooService以便能够测试FooService的逻辑。
    使用JUnit5和Mockito测试类的外观如下:

    import org.mockito.junit.jupiter.MockitoExtension;
    import org.mockito.Mock;
    import org.mockito.Mockito;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.BeforeEach;
    
    
    @ExtendWith(MockitoExtension.class)
    class FooServiceTest{
    
        FooService fooService;  
    
        @Mock
        FooRepository fooRepository;
    
        @BeforeEach 
        void init{
            fooService = new FooService(fooRepository);
        }
    
        @Test
        void compute(){
            List<Foo> fooData = ...;
            Mockito.when(fooRepository.findAll(...))
                   .thenReturn(fooData);
            long actualResult = fooService.compute(...);
            long expectedResult = ...;
            Assertions.assertEquals(expectedResult, actualResult);
        }
    
    }
    

    编写端到端测试需要加载一个容器,其中包含应用程序的整个配置和bean。
    要实现这一点,@springboottest的方法是:

    注释的工作原理是通过SpringApplication创建测试中使用的ApplicationContext

    您可以以这种方式使用它来测试它,而不需要任何mock:

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

    加载完整的spring上下文需要时间。因此,对于@springboottest,您应该谨慎,因为这可能会使单元测试的执行变得非常长,而且通常您不希望在开发人员的机器上强烈减慢本地构建和测试反馈,这对于开发人员编写愉快和高效的测试至关重要。
    这就是为什么“慢”测试通常不在开发人员的机器上执行。
    所以您应该将它们设置为集成测试(it后缀,而不是在测试类的命名中以test后缀),并确保这些只在持续集成构建中执行。
    但是作为Spring Boot的作用在应用程序中的许多方面(rest控制器、MVC控制器、JSON序列化/反序列化、持久性等等)您可以编写许多只在CI构建上执行的单元测试,这也不好。
    只在CI构建上执行端到端测试是可以的,但也要具有持久性,仅在CI构建上执行的控制器或JSON测试根本不合适。
    实际上,开发人员构建会很快,但作为缺点,在本地执行的测试只会检测到一小部分可能的回归...
    为了防止这种警告,Spring Boot提供了一种中间方式:部分集成测试或切片测试(他们称之为):下一点。

    正如在“识别一个可以简单测试的测试(没有spring)”这一点中所解释的那样,有些组件只能用运行的容器进行测试。
    但是为什么要使用@springboottest来加载应用程序的所有bean和配置,而您只需要加载一些特定的配置类和bean来测试这些组件呢?
    例如,为什么要加载完整的Spring JPA上下文(beans,配置、内存数据库等)来测试控制器部分?
    以及为什么要加载与Spring控制器相关的所有配置和bean来测试JPA存储库部分?
    Spring Boot通过切片测试特性解决了这一点。
    这些不如普通单元测试(即没有容器)快,但它们确实比加载整个Spring上下文快得多。因此,在本地机器上执行它们通常是非常可以接受的。
    每个切片测试风格加载了一组非常有限的自动配置类,如果需要,您可以根据需求修改这些类。

    一些常见的切片测试特性:

    • 自动配置的JSON测试:@jsontest

    要测试对象JSON序列化和反序列化是否按预期工作,可以使用@JSONTEST注释。

      null
      null
    • 自动配置数据JPA测试:@datajpatest

    您可以使用@datajpatest注释来测试JPA应用程序。

    您还有许多Spring Boot提供的其他切片类型。
    查看文档中的测试部分以获得更多细节。
    注意,如果您需要定义一组特定的beans来加载,而内置测试切片注释并不涉及这些beans,您也可以创建自己的测试切片注释(https://Spring.io/blog/2016/08/30/custom-test-slice-with-spring-boot-1-4)。

    几天前,我遇到了一个案例,我将在部分集成中测试一个服务bean,该服务bean依赖于几个bean,而这些bean本身也依赖于其他bean。我的问题是,由于常见的原因(http请求和数据库中有大量数据的查询),必须模拟两个深度依赖bean。
    加载所有Spring Boot上下文看起来是一个开销,所以我尝试只加载特定的bean。为此,我使用@springboottest注释测试类,并指定classes属性来定义要加载的configuration/beans类。
    经过多次尝试,我得到了一些似乎有效的东西,但我必须定义一个重要的bean/configuration列表来包括。
    这确实不整洁也不可维护性。
    为了更清楚,我选择使用Spring Boot2.2提供的懒惰bean初始化特性:

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

    这样做的好处是只加载运行时使用的bean。
    我不认为在测试类中必须使用该属性,但在一些特定的测试用例中,这似乎是正确的。

  •  类似资料:
    • 当我使用@service时,它工作得很好。 当我使用@RESTController时,它抛出一个NullPointerException。 它不能把控制器连上电线是不是有什么原因?我想它可能与创建web上下文有关,但我也尝试添加SpringBootTest和指向MOCK(和其他)的webEnvironment,看看这是否能让它发挥作用。 我也踢过MockMvc的东西,但我不确定它应该如何工作。 有

    • 我有一个生成屏幕截图的类: 我的应用程序是用创建的,我需要对它进行测试。但我得到 我的测试: 我试图防止无头异常: 我使用的是spring启动版本1.5.6。 但这无济于事。我得到日志: java.awt.HeadlessException在sun.awt.HeadlesStoolKit.GetScreenSize(headlesStoolKit.java:284)在org.robinhood.i

    • 英文原文:http://emberjs.com/guides/testing/testing-components/ 单元测试方案和计算属性与之前单元测试基础中说明的相同,因为Ember.Component集成自Ember.Object。 设置 在测试组件之前,需要确定测试应用的div已经加到测试的html文件中: 1 2 <!-- as of time writing, ID attribut

    • 与@mockbean和@spybean一样,有没有类似于@fakebean/@dummybean的东西? 其思想是,该实例是100%真实的(具有预期的生产内部状态),并且它覆盖(或者添加bean,以防在配置中没有声明)上下文中的bean。理想情况下,您不需要创建TestConfiguration类并将其设置为Primary,因为这样可以在每个测试的基础上控制假冒,只有在需要时才可以。否则它使用主的

    • 问题内容: 我用来对我的React组件进行单元测试。我知道,为了测试原始未连接的组件,我只需要导出它并对其进行测试(我已经做到了)。我已经设法为连接的组件编写了一个测试,但是我真的不确定这是否是正确的方法,以及我究竟想对连接的组件进行什么测试。 Container.jsx Container.test.js 问题答案: 这是个有趣的问题。 我通常会同时导入容器和组件以进行测试。对于容器测试,我使用