首页 > 其他分享 >Spring Boot-如何优雅的写单元测试

Spring Boot-如何优雅的写单元测试

时间:2024-04-10 12:26:08浏览次数:32  
标签:Spring Mockito 单元测试 public Boot userService 方法 class

Spring Boot-如何优雅的写单元测试

什么是单元测试

  • 当一个测试满足下面任意一点时,测试就不是单元测试
    • 与数据库交流
    • 与网络交流
    • 与文件系统交流
    • 不能与其他单元测试在同一时间运行
    • 不得不为运行它而作一些特别的事
  • 如果一个测试做了上面的任何一条,那么它就是集成测试

Mockito 介绍

  • 单元测试就是对一个系统中的某个最小单元的逻辑正确性的测试,通常是对一个方法来进行测试,因为只测试逻辑正确性,所以这个测试是独立的,不与任何外界环境相关,比如不需要连接数据库,不访问网络和文件系统,不依赖其他单元测试
  • Mockito 是一个用来在单元测试中快速模拟那些需要与外界环境沟通的对象,以便我们快速的、方便的进行单元测试而不用启动整个系统
  • 采用 Mock 框架,我们可以 虚拟 出一个 外部依赖,只注重代码的 流程与结果,真正地实现测试目的
  • Mock测试框架的好处:
    1. 可以很简单的虚拟出一个复杂对象(比如虚拟出一个接口的实现类);
    2. 可以配置 mock 对象的行为;
    3. 可以使测试用例只注重测试流程与结果;
    4. 减少外部类、系统和依赖给单元测试带来的耦合
  • Mockito的局限
    1. 不能mock静态方法
    2. 不能mock私有方法
    3. 不能mock final class
  • Mock原理
    • mock 模拟的对象可以理解为真实方法的一个代理,每次对方法的调用其实都是调用了代理方法,这个代理方法是一个空方法,不会做任何事情
  • Mock两种使用方法
    • 直接代码mock一个对象
    • 用@Mock造一个对象

Mockito使用

  • 方法1:调用完方法后指定返回值

    //格式
    Mockito.when(调用的类.方法).thenReturn(指定的返回值);
    // 示例:
    Mockito.when(service01.sendMail(Mockito.anyString(), Mockito.anyString()
    
  • 如果是 @Mock 标注的对象方法,这样设置后不会进去方法执行,直接返回指定值。

  • 如果是 @Spy 标注的对象方法,这样设置后会进去执行方法,但是返回指定的返回值。

  • 指定方法的返回值时,入参的设置可以使用 matcher 做匹配,其中的 any 方法匹配任意值

      // 示例1:当使用任何整数值调用 userService 的 getUser() 方法时,就回传一个自定义 User 对象
      Mockito.when(userService.getUserById(Mockito.anyInt())).thenReturn(new User(3, "I'm mock"));
      User user1 = userService.getUserById(3); // 回传的user的名字为I'm mock
      User user2 = userService.getUserById(200); // 回传的user的名字也为I'm mock
    
      // 示例2:限制只有当参数的数字是 3 时,才会回传名字为 I'm mock 3 的 user 对象
      Mockito.when(userService.getUserById(3)).thenReturn(new User(3, "I'm mock"));
      User user1 = userService.getUserById(3); // 回传的user的名字为I'm mock
      User user2 = userService.getUserById(200); // 回传的user为null
    
      // 示例3:当调用 userService 的 insertUser() 方法时,不管传进来的 user 是什么,都回传 100
      Mockito.when(userService.insertUser(Mockito.any(User.class))).thenReturn(100);
      Integer i = userService.insertUser(new User()); //会返回100
    
    • 有多个入参的方法,一个入参使用了 matcher 做匹配,那么其他入参也要用 matcher 匹配。例如下面用了 any 方法,那么第二入参就不能写死了,可以用 eq 方法来做匹配
      // 错误的写法:
      Mockito.when(service01.sendMail(Mockito.anyString(), "王五")).thenReturn(true);
      // 正确的写法:
      Mockito.when(service01.sendMail(Mockito.anyString(), Mockito.eq("王五"))).thenReturn(true);
    
    
    • 方法2: 直接返回指定值
      // 格式:
      Mockito.doReturn(方法返回值).when(spy标注的对象).调用的方法;
      // 示例:
      Mockito.doReturn(false).when(service01).sendMail(Mockito.anyString(), Mockito.anyString());
      
    • 方式3: 设置抛出异常
          // 格式:
          Mockito.when(调用方法).thenThrow(抛出的异常类);
      
          // 示例:
          Mockito.when(service01.sendMail(Mockito.anyString(),Mockito.anyString())).thenThrow(RuntimeException.class);
      

@Spy 的使用

  • Spy 跟 Mock 不同之处在于,它是会真正执行方法逻辑的。相同之处是它可以指定方法的返回值。

InjectMocks 的使用

  • @InjectMocks 用来给标注的成员变量填充带有 @Mock 和 @Spy 标签的 bean,可以理解为它会吸取所有 @Mock 和 @Spy 标注的bean 为自己所用;
  • 如果是嵌套的 bean 可以用 ReflectionTestUtils.setFileld() 绑定成员变量。

@MockBean 的使用

  • @MockBean 是 SpringBoot 中增加的,用来支持容器中的 mock 测试。它跟 mock 的使用逻辑是一样,只是它修饰的对象是容器中的对象,也就是 bean 对象。

@SpyBean 的使用

  • @SpyBean 也是 SpringBoot 增加的一个注解,用来支持 Spring 容器的单元测试,它与 Spy 的逻辑基本一致,不同之处就在于它标注的对象是容器对象。具体使用可以参考上面 @MockBean 的使用方法。

方法的校验和断言

  • 通常写单元测试就是要断言方法的执行是否符合预期,除了 junit 提供的 Assert 类中的方法外,Mockito 也提供了几种校验方法
  • 方法1:Mockito.verify() 方法断言方法是否被调用过
    // 格式:
    Mockito.verify(对象).对象的方法;

    // 示例1:校验list对象是否调用了add(“22”)方法
    Mockito.verify(list).add(“22”);
    // 示例2:检查调用 userService 的 getUserById()、且参数为3的次数是否为1次
    Mockito.verify(userService, Mockito.times(1)).getUserById(Mockito.eq(3)) ;
    // 示例3:验证调用顺序,验证 userService 是否先调用 getUserById() 两次,并且第一次的参数是 3、第二次的参数是 5,然后才调用insertUser() 方法
    InOrder inOrder = Mockito.inOrder(userService);
    inOrder.verify(userService).getUserById(3);
    inOrder.verify(userService).getUserById(5);
    inOrder.verify(userService).insertUser(Mockito.any(User.class));
  • 方法2:断言异常
       @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
        // 让方法抛出异常
 		Mockito.when(service01.sendMail(Mockito.anyString(),Mockito.anyString()))
     		.thenThrow(RuntimeException.class);
    }

    // 必须抛出指定的异常才会通过测试
    @Test(expected=RuntimeException.class)
    public void testThrowException(){
        service01.sendMail("张三","李四");
    }
  • 方法3:Assert 类中的断言方法

测试Controller

  • 方式1:使用 @AutoConfigureMockMvc(推荐)
  • @AutoConfigureMockMvc 会自动注入 MockMvc,可以方便的指定入参或者是 header
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @AutoConfigureMockMvc
    public class BookControllerTest2 {
        @Autowired
        public MockMvc mockMvc;

        @Test
        public void testGetBookInfo() throws Exception {
            MvcResult result = mockMvc.perform(
                    MockMvcRequestBuilders.post("/getBookInfo2").param("id","123").header("user","xiaoming"))
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    .andReturn();
            System.out.println(result.getResponse().getContentAsString());
        }
    }


    @RestController
    public class BookController2 {
        @PostMapping("/getBookInfo2")
        public String getBookById(@RequestParam String id, @RequestHeader String user){
            System.out.println(user + "查询书籍信息,bookId=" + id);
            return "《java语言》";
        }
    }

  • 方式2:使用 TestRestTemplate 模板
  @RunWith(SpringRunner.class)
  //指定web环境,随机端口
  @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
  public class BookControllerTest {
      //这个对象是运行在web环境的时候加载到spring容器中
      @Autowired
      private TestRestTemplate testRestTemplate;

      @Test
      public void testGetBookInfo(){
          String result = testRestTemplate.getForObject("/getBookInfo?id=123456", String.class);
          System.out.println(result);
      }
  }


  @RestController
  public class BookController2 {
      @GetMapping("/getBookInfo")
      public String getBookById(@RequestParam String id){
          System.out.println("查询书籍信息,bookId=" + id);
          return "《java语言》";
      }
  }

RunWith使用

  • @RunWith(SpringRunner.class) 或者 @RunWith(SpringJUnit4ClassRunner.class):代表在 Spring 容器中运行单元测试。如果配合 @SpringBootTest 就是在 SpringBoot 容器中运行单元测试。

  • 注:SpringRunner 就是 SpringJUnit4ClassRunner 的别名,它们作用是一样的。

    @SpringBootTest(classes = MyApplication.class)	// classes 加载启动类
    @RunWith(SpringRunner.class)
    public class BaseTest {
        public void runUnitTest(){
        }
    }
    
  • @RunWith(MockitoJUnitRunner.class)
    可以理解为使用 Mockito工作运行单元测试,它会初始化 @Mock 和 @Spy 标注的成员变量

  • @RunWith(Suite.class):代表是一个集合测试类,一般是如下用法,也就是其可一次性测试多个用例

        @RunWith(Suite.class)
        @Suite.SuiteClasses({ServiceTest.class, A.class})
        public class AllTest {
        }
        public class ServiceTest{
            @Test
            public void test01(){ }
    
            @Test
            public void test02(){}
        }
    

加速Spring Boot 单元测试的执行速度

  1. 如果单元测试不涉及到Controller接口调用,可以配置webEnvironment = SpringBootTest.WebEnvironment.NONE不启动web容器
  2. 可以通过classes = {...}手动指定需要注册到容器中的类,如果不设置该属性,默认会注册应用中所有类
     @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,classes = {UserService.class, UserMapper.class})
     public class UserServiceTest {
       @Autowired
       private UserService userService;
    
       @Test
       public void testUserService() {assertEquals("user list", userService.queryUsers());}
     }
    
  3. webEnvironment
    • MOCK 启动一个模拟的Servlet环境 默认值
    • RANDOM_PORT 启动一个Tomact容器,并监听一个随机端口
    • DEFINED_PORT 启动一个Tomcat容器,并监听配置文件中定义的端口(未定义则默认监听8080)
    • NONE 不启动Tomcat容器
  4. @Before @BeforeClass 有啥区别?
    • @Before 和@BeforeClass 都是JUnit测试框架注解
    • @Before 这个注解应用于一个方法上,这个方法会在每一个测试方法执行之前被调用。对应执行一些每个测试都需要准备工作,如初始化变量 打开数据库连接
    • @BeforeClass 这个注解应用于一个静态方法上,这个方法会在测试类中的所有测试方法执行之前被调用一次,而且只会被调用一次。对于执行一些只需要在开始时执行一次的准备工作,如加载配置文件,设置环境变量等,非常有用。

标签:Spring,Mockito,单元测试,public,Boot,userService,方法,class
From: https://www.cnblogs.com/heyanfeng/p/18125441

相关文章

  • 23.Springboot常用的依赖总结_(没死之前)持续更新中~~~~~~
    2.2.5.RELEASE(注意maven对应版本)mybatis:<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version></......
  • spring security 6:放行某些请求-接口
    springboot3.1.10spring-boot-starter-security3.1.10springsecurityweb6.1.8--ben发布于博客园序章自定义了BeanSecurityFilterChain,在http.authorizeHttpRequests中放行了接口“/system/register”,还定义了session管理的invalidSessionStrategy。@Bean......
  • 从零开始学Spring Boot系列-外部化配置
    SpringBoot允许你将配置外部化,以便可以在不同的环境中使用相同的应用程序代码。可以使用属性文件、YAML文件、环境变量和命令行参数将配置外部化。属性值可以通过使用@Value注解直接注入bean,可以通过Spring的Environment抽象访问,也可以通过@ConfigurationProperties。S......
  • Spring Boot集成JavaMailSender发送邮件,支持二进制流
    什么是JavaMailSenderJavaMailSender是SpringFramework中的一个接口,用于发送电子邮件。它是Spring对JavaMailAPI的封装,提供了更简单和更方便的方式来发送邮件。JavaMailSender接口定义了一组发送邮件的方法,包括发送简单文本邮件、发送带附件的邮件、发送HTML格式的邮件等。它隐......
  • 基于Springboot+Vue的Java项目-月度员工绩效考核管理系统(附演示视频+源码+LW)
    大家好!我是程序员一帆,感谢您阅读本文,欢迎一键三连哦。......
  • Java面试题-13Spring
    1、事务管理spring事务分为编程式事务和声明式事务Spring事务管理器的行为的重要组成部分。下面是对这些属性的简要解释:1.**value**:用于指定事务管理器的名称。如果应用中只有一个事务管理器,通常可以省略该属性。2.**propagation**:指定事务的传播机制,即在多个事务方法相互调......
  • 【Spring】-Spring之线程池
    目录Spring中的线程池Spring中的线程池在spring中其实也是有线程池的,一般使用的是ThreadPoolTaskExecutor该类,其实现方法还是java.util.concurrent中的ThreadPoolExecutor线程为了提高自定义化,一般都是自定义配置核心线程数,最大线程数,队列,以及拒绝策略,下面是代码演示,@......
  • 【Spring】-Spring 之AOP注解
    目录Spring之AOP注解AOP思想:AOP的使用场景:@Aspect的使用以及基本概念:1.切面类2.切点@Pointcut3.Advice,4.JoinPoint:5.使用&&、||、!、三种运算符来组合切点表达式,表示与或非的关系;6.@annotation(annotationType)匹配指定注解为切入点的方法;具体代码实现:Spring之AOP注解A......
  • 基于java+springboot+vue实现的农产品智慧物流系统(文末源码+Lw)23-239
    摘 要互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱,出错率高,信息安全性差,劳动强度大,费时费力等问题,采用农产品智慧物流系统可以有效管理,使信息管......
  • 基于java+springboot+vue实现的人事管理系统(文末源码+Lw)23-242
    摘 要使用旧方法对人事管理系统的信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在人事管理系统的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误不能及时纠正等问题。这次开发的人事管理系统对字典管理、公告管理、绩效管理、......