单元测试-SpringBoot Test和Mock

news2024/9/20 0:57:01

单元测试-SpringBoot Test和Mock

“单元测试”

“junit,mock,桩”

1. 什么是单元测试

定义:是指对软件中的最小可测试单元进行检查和验证。

Java里单元指一个方法。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

2. 单元测试与Spring Boot

2.1 引入依赖spring-boot-starter-test

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
spring-boot-starter-test中包含了junit和mockito等依赖

​​在这里插入图片描述

2.2 相关依赖

  • junit – 标准的单元测试Java应用程序
  • Spring Test & Spring Boot Test – 对Spring Boot应用程序的单元测试提供支持
  • Mockito, Java mocking框架,用于模拟任何Spring管理的Bean,比如在单元测试中模拟一个第三方系统Service接口返回的数据,而不会去真正调用第三方系统;
  • AssertJ,一个流畅的assertion库,同时也提供了更多的期望值与测试返回值的比较方式;
  • JSONassert,对JSON对象或者JSON字符串断言的库。
  • …………

2.3 标准的Spring Boot单元测试结构

@DisplayName("AlarmMsgstationController测试类")  //起别名
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class Test {
}


3. SpringBoot Test常用注解

在这里插入图片描述

4. 基本用法

类上添加注解,启动Spring Boot环境

@RunWith(SpringRunner.class)
@SpringBootTest
public class FirstTest {
    @Test
    public void test() {
        int a=1;
        Assertions.assertEquals(1,a);//判断二者是否相等
    }
}

可以使用Assertions类来判断结果是否符合预期;

4.1 直接注入

对数据访问层(Service层同理)

对数据访问层Dao中的方法进行测试
在这里插入图片描述
ps:@Autowired直接注入的方法会真实操作数据库,如果在单元测试中不想改变数据数据库中的值,不能使用直接注入的方法

其实可以在类上再添加这两个注解,通过@Transactional可以知道调用了数据库,对其操作进行回滚

但是如果项目中使用了@Component注解(在SpringBoot项目启动的时候就会跟着实例化/启动),@Component注解的类里有多线程方法,那么在执行单元测试的时候,由于多线程任务的影响,就可能对数据库造成了数据修改,即使使用了事务回滚注解@Transactional。(我在百度上看到的,没找到具体的测试方法,所以没试)

@Component注解:带此注解的类看为组件,当使用基于该注解的配置和类路径扫描的时候,这些类就会被实例化。

@Transactional
@Rollback(true) // 事务自动回滚,默认是true。可以不写

4.2 Mock注入

实现原理:使用Stub(桩)技术动态的替换原程序的功能。

直接跑Java代码,不需要启用Spring及连接数据库,模拟一切操作数据库的步骤,不执行任何SQL,也可以模拟任何返回值

4.2.1 使用Mock的优点:

  1. 可以完全脱离数据库
  2. 只针对某一个小方法(一个小的单元)来测试,测试过程中,不需要启动其他的东西,不免其他因素可能产生的干扰

4.2.2 编写Mock代码

  1. 不再使用@Autowired

    启动Spring会导致运行单元测试的时候的速度变慢(run->Junit Test),单元测试只针对某一个类的方法来测试,不需要启动Spring,只需要对应的实体实例就够了,在需要注入bean的时候直接new

  2. 不再使用@SpringBootTest

  3. 不调用数据库

    @Transactional @Rollback(true)这两个注解也不要

  4. 使用Assert断言

基本应用:

mock 对象的方法的返回值默认都是返回类型的默认值

import org.junit.Assert;
import org.junit.Test;
import java.util.Random;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Test
    public void test() {
        Random mockRandom = mock(Random.class); //mock了一个Random对象
	Assert.assertEquals(0, mockRandom.nextInt());//未进行打桩,每次返回值都是0
   
 	when(mockRandom.nextInt()).thenReturn(100);  // 进行打桩操作,指定调用 nextInt 方法时,永远返回 100
        Assert.assertEquals(100, mockRandom.nextInt());
    }
}

4.2.3 Mock的注解和常用的方法

@Mock

@Mock 注解可以理解为对 mock 方法的一个替代。使用该注解时,要使用MockitoAnnotations.initMocks​ 方法,让注解生效。旧版的是initMocks,新版的是openMocks

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Random;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Mock
    private Random random;

    @Before
    public void before() {
        // 让注解生效
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test() {
        when(random.nextInt()).thenReturn(100);
        Assert.assertEquals(100, random.nextInt());
    }

}

也可以用MockitoJUnitRunner​来代替MockitoAnnotations.initMocks

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.Random;

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {

    @Mock
    private Random random;

    @Test
    public void test() {
        when(random.nextInt()).thenReturn(100);
        Assert.assertEquals(100, random.nextInt());
    }

}
@Spy

mock()方法与spy()方法的不同:

  1. 被spy的对象会走真实的方法,而mock对象不会
  2. spy方法的参数是对象实例,mock的参数是class
@InjectMocks

mockito 会将 @Mock​、@Spy​ 修饰的对象自动注入到 @InjectMocks​ 修饰的对象中

thenReturn

thenReturn 用来指定特定函数和参数调用的返回值;

thenReturn 中可以指定多个返回值。在调用时返回值依次出现。若调用次数超过返回值的数量,再次调用时返回最后一个返回值。

doReturn 的作用和 thenReturn 相同,但使用方式不同:

when(mockRandom.nextInt()).thenReturn(1);//返回值为1
when(mockRandom.nextInt()).thenReturn(1, 2, 3);

doReturn(1).when(random).nextInt();
thenThrow

thenThrow 用来让函数调用抛出异常。(可搭配try catch使用)

thenThrow 中可以指定多个异常。在调用时异常依次出现。若调用次数超过异常的数量,再次调用时抛出最后一个异常。

when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常"));
when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常1"), new RuntimeException("异常2"));


    @Test
    public void test() {
        Random mockRandom = mock(Random.class);
        when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常1"), new RuntimeException("异常2"));

        try {
            mockRandom.nextInt();
            Assert.fail();//上一行会抛出异常,到catch中去,走不到这里
        } catch (Exception ex) {
            Assert.assertTrue(ex instanceof RuntimeException);
            Assert.assertEquals("异常1", ex.getMessage());
        }

        try {
            mockRandom.nextInt();
            Assert.fail();
        } catch (Exception ex) {
            Assert.assertTrue(ex instanceof RuntimeException);
            Assert.assertEquals("异常2", ex.getMessage());
        }


    }

对应返回类型是 void 的函数,thenThrow 是无效的,要使用 doThrow。也可以用 doThrow 让返回非void的函数抛出异常

doThrow(new RuntimeException("异常")).when(exampleService).hello();

// 下面这句等同于 when(random.nextInt()).thenThrow(new RuntimeException("异常"));
doThrow(new RuntimeException("异常")).when(random).nextInt();
reset

使用 reset 方法,可以重置之前自定义的返回值和异常。

reset(exampleService);
vetify

使用 verify 可以校验 mock 对象是否发生过某些操作,配合 time 方法,可以校验某些操作发生的次数

//判断backOutstockMapper.selectReportCountByMap()方法是否被调用1次
verify(backOutstockMapper, times(1)).selectReportCountByMap(Mockito.any());

//校验backOutstockMapper.selectReportCountByMap()方法是否被调用过
verify(backOutstockMapper).selectReportCountByMap(Mockito.any());

4.2.5 断言

->assertTrue(String message, boolean condition)             要求condition == true
->assertFalse(String message, boolean condition)            要求condition == false
->assertEquals(String message, XXX expected,XXX actual) 要求expected期望的值能够等于actual
->assertArrayEquals(String message, XXX[] expecteds,XXX [] actuals) 要求expected.equalsArray(actual)
->assertNotNull(String message, Object object)          要求object!=null
->assertNull(String message, Object object)             要求object==null
->assertSame(String message, Object expected, Object actual)     要求expected == actual
->assertNotSame(String message, Object unexpected,Object actual) 要求expected != actual
->assertThat(String reason, T actual, Matcher matcher)  要求matcher.matches(actual) == true
->fail(String message) 要求执行的目标结构必然失败,同样要求代码不可达,即是这个方法在程序运行后不会成功返回,如果成功返回了则报错

4.3 Tips

  1. 对待类中私有的方法,可以用反射的方式进行测试

  2. 打包时跳过test

    mvn deploy -f pom_http.xml-jar -Dmaven.test.skip=true

  3. Mockito 默认是不支持静态方法,可使用 PowerMock 让 Mockito 支持静态方法(新增依赖)

5. 总结

单元测试测试的不是整条业务线,而是类中的单个方法单元。

按照单一性原则的话,一个方法只做一件事,那么针对这个方法的单元测试就简单了。

当多个方法单元测试的结果都没问题的时候,多个方法聚合成的业务链照理说也是没问题的,一个方法中依赖了其他方法的处理结果或返回结果,那么这个结果应当是可预测的,所以也是可以mock出所有场景的,而单元测试也应该覆盖到不同结果对应的场景。

单元测试除了测试代码逻辑外,最大的好处是可以检验整体设计是否合理。一个方法做了太多事的话,就会导致单元测试很难覆盖,比如service层的方法,如果入参的校验,业务逻辑的处理,不同数据表DB的操作,DB返回结果的校验处理全部在单一方法中实现,那对于后期业务的扩展、维护、问题的排查都不好进行;如果把以上说的那些全部分离出来,封装成一个个独立的方法,最后只在一个方法中总调,这样不仅单元测试比较好实现,而且后期的维护,扩展都会很容易。

ps:上面总结这段话不是我说的,是我在学习的过程中看到一位老哥写在评论区的。领导让我总结一下,我就抄过来敷衍领导了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/139235.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

玻纤效应对skew的影响(三)

玻纤效应对skew的影响&#xff08;一&#xff09;玻纤效应对skew的影响&#xff08;二&#xff09;对内skew对32Gbps NRZ和64Gbps PAM-4的影响这一篇中&#xff0c;玻纤效应造成的对内skew将会加入到32Gbps NRZ和64Gbps PAM-4 SerDes全链路分析中。PCIe 5.0代表32Gbps NRZ&…

C++GUI之wxWidgets(11)-编写应用涉及的类和方法(6)-事件处理(5)

目录自定义事件wxPostEvent()wxQueueEvent()PopEventHandler()Bind()GetEventUserData()Connect()Unbind()定义自己的事件类事件处理程序与虚拟方法自定义事件 wxPostEvent() void wxPostEvent ( wxEvtHandler * dest,const wxEvent & event ) 在GUI应用程序中&am…

云开发项目中如何管理用户和管理授权?

管理用户 在项目中添加用户后&#xff0c;才能为用户授予对应的资产管理权限。支持修改已创建用户的密码和删除用户。 本文中的 用户 是指在云项目下创建的 B 端子账号&#xff0c;可以和资产授权配合使用&#xff0c;管理 B 端设备和资产。这些 B 端用户账号可以在 智慧行业…

IOS开发基础 · SwiftUI · CS193p Lecture1-2

IOS开发Lecture 1TextRoundedRectangleZstackLecture 2HStackstruct整合组件ContentViewstruct 中创建变量var&letSwiftUI刷新重建点击效果ArrayForeachButtonSpacervar整合小组件SF-symbol上下界限制简化ButtonLecture 1 Text import SwiftUIstruct ContentView: View {…

Node.js 中 cookie的验证登录

认识 cookie 在讲cookie的登录验证之前&#xff0c;先来了解一下cookie是什么&#xff1f;cookie本质是存储在浏览器中的一小段文本信息&#xff08;不超过4kb&#xff09;&#xff0c;是由服务器生成发送到浏览器&#xff08;客户端&#xff09;&#xff0c;浏览器将其保存在…

虚拟化技术学习笔记2

1、虚拟机与容器对比&#xff1a; 2、Hypervisor管理工具对比&#xff1a; 3、QEMU&#xff1a; 软件模拟虚拟化、可以模拟多种硬件&#xff0c;包括X86架构处理器、AMD64架构处理器、ARM、SPARC与PowerPC、AIX架构等&#xff0c;效率低、一般用于研究测试场景。QEMU可以模拟一…

偏微分题目的解法

介绍偏微分是考研数学里的小重点&#xff0c;通常在题干中就能很明显看到偏导数。这种题目一般会有两个小题&#xff0c;且第一题往往送分题&#xff0c;通常是求某个复合函数的偏导&#xff0c;直接用复合函数的求导法则即可得到答案。第二题通常是求原函数&#xff0c;一般来…

NVIDIA 在 WeNet 中开源 Noisy Student Training 方案

为了改进 Noisy Student Training 在非目标领域 ASR 上的性能&#xff0c;英伟达提出新型数据筛选方法 LM Filter。其利用不同解码方式的识别文本之间的差异来作为数据筛选条件&#xff0c;是一个完全无监督的筛选过程。在 AIShell-1 上与无数据筛选的基线相比可以有 10.4% 的性…

PYNQ-Z2 开发板

1. 官方手册写的挺全&#xff0c;了解一下PYNQ-Z2 设置指南 https://pynq.readthedocs.io/en/latest/getting_started/pynq_z2_setup.htmlPYNQ-Z2 Reference Manual v1.0 https://www.mouser.com/datasheet/2/744/pynqz2_user_manual_v1_0-1525725.pdfpynq&#xff08;Python O…

单分散PEG之Amino-PEG24-acid;CAS:196936-04-6氨基-二十四聚乙二醇-羧酸

Amino-PEG24-acid氨基-二十四聚乙二醇-羧酸196936-04-6 中文名称&#xff1a;氨基-二十四聚乙二醇-羧酸 英文名称&#xff1a;Amino-PEG24-acid 分子式&#xff1a;C51H103NO26 分子量&#xff1a;1146.35 CAS&#xff1a;196936-04-6 外观&#xff1a;粘稠液体或者固体粉末&a…

数据的存储(3)浮点数的存储

tips 1. 2. 浮点数内存存储方式与整型是截然不同&#xff0c;不可被整型思维带偏了 我用一个例子来理解浮点数在内存当中的表示方法&#xff0c;先上一个十进制浮点数13.5 1. 利用二进制的权重化为二进制浮点数 二进制权重表小数部分如下&#xff1a; 那么13.5&…

Vue好难理解怎么办?

Vue学习笔记分享给你&#xff0c;希望对你有些帮助&#xff0c;另外推荐2个安装 VScode 中的 Vue 插件 Vue 3 Snippets Vue 3 Snippets - Visual Studio Marketplace 这个插件包含了所有的 Vue.js 2 和 Vue.js 3 的 api 对应的代码片段。插件的代码片段如下表格所示&#xff0…

Jetson nano 入手系列之4—外围设备:开机键+PWM风扇

Jetson nano 入手系列之4—外围设备&#xff1a;开机键PWM风扇1.外接按键开机2.PWM可调速风扇2.1 jtop工具2.2 PWM风扇2.3 PWM风扇的控制2.3.1 手动控制2.3.2 自动控制参考文献本系列针对亚博科技jetson nano开发板。 Jetson nano 入手系列&#xff1a; Jetson nano 入手系列之…

《MySQL系列-InnoDB引擎11》InnoDB关键特性-刷新邻接页

InnoDB 关键特性 InnoDB存储引擎的关键特性包括&#xff1a; Insert Buffer (插入缓冲)Double Write (两次写)Adaptive Hash Index (自适应哈希索引)Async IO (异步IO)Flush Neighbor Page (刷新领接页) 这些特性为InnoDB存储引擎带来了更好的性能以及更高的可靠性。 刷新邻接…

极光笔记 | 当前最佳实践:Header Bidding 与瀑布流混合请求技术

通过这篇文章您讲将了解&#xff1a;Header Bidding 的发展史Waterfall、Header Bidding 的逻辑及优劣势为什么说 Header Bidding 与瀑布流混合请求技术是当前最佳实践PART 01、Header Bidding 的起源Header Bidding&#xff08;头部竞价&#xff0c;又称 Pre-Bidding 或 Advan…

PaddleSports:“AI+体育”端到端开发套件及落地实践

本系列根据WAVE SUMMIT2022深度学习开发者峰会「开源开放 生态共建」论坛嘉宾分享整理。本文整理自「开源开放 生态共建」百度研究院的资深研究员卢飞翔的主题演讲——PaddleSports&#xff1a;“AI体育”端到端开发套件及落地实践。百度3DAI智慧体育团队针对数据、算法、产品三…

C++语法基础课 习题5 —— 字符串

文章目录例题1. 760.字符串的长度(fgets函数)重点&#xff01;2. 761.字符串中数字的个数3. 763.循环相克令4. 765.字符串加空格(getline函数auto用法)重点&#xff01;5. 769.替换字符 重点&#xff01;6. 773.字符串的插入(substr函数)重点!7. 772.只出现一次的字符(难题)习题…

Python逆向进阶教程笔记(1)

视频地址&#xff1a;Day1初识JS逆向 混淆与无混淆数据加密方案解析实战-36Kr数据加密解析 (AES数据逆向)_哔哩哔哩_bilibili 未完待续 一、排错 1.1&#xff09; 目的&#xff1a;抓页面借口&#xff0c;简单请求会被拦截 1.2&#xff09; 网址&#xff1a;乌海市公共资源…

OSPF-MGRE实验(1.3)

要求&#xff1a; 1、首先为每个路由器配置接口ip和环回ip实现第一个目标 r1&#xff1a; [r1]interface GigabitEthernet 0/0/0 [r1-GigabitEthernet0/0/0]ip add 16.1.1.1 24 [r1-GigabitEthernet0/0/0]int gi0/0/1 [r1-GigabitEthernet0/0/1]ip add 61.1.1.1 24 [r1-Gigab…

java File类详细

目录 File创建 1.File&#xff08;String pathname&#xff09; 2. File&#xff08;String parent,String child&#xff09; 3.File(File f,String child) file类的使用 基本操作&#xff1a; 文件夹的操作 File创建 1.File&#xff08;String pathname&#xff09; …