使用Mockito进行单元测试

1、单元测试介绍

        Mockito和Junit是用于单元测试的常用框架。单元测试即:从最小的可测试单元(如函数、方法或类)开始,确保每个单元都能按预期工作。单元测试是白盒测试的核心部分,它有助于发现单元内部的错误。

        单元测试是目前常用的白盒测试方法之一。

2、白盒测试介绍

        白盒测试,也称为结构测试、逻辑驱动测试或基于代码的测试,是一种针对被测单元内部工作原理进行测试的方法。它要求测试者完全了解被测软件的结构和内部工作原理,通过程序内部的代码和结构信息来设计测试用例,以确保软件的内部质量,减少软件的缺陷和漏洞,提高软件的可靠性和稳定性。

        白盒测试总体上可以分为静态分析和动态分析两大类,具体包括以下几种方法:

2.1、静态分析

        静态分析是在不执行程序的情况下进行的测试,主要关注代码本身的质量。

  1. 代码审查:对代码进行人工审查,以发现潜在的bug、漏洞或不符合编码规范的地方。结对编程就是属于这种。
  2. 代码扫描:使用自动化工具对代码进行扫描,以发现潜在的代码质量问题。SonarLint和SonarQube就是流行的代码自动化扫描工具。

2.2、动态分析

        动态分析需要执行程序,并观察程序的运行行为和输出结果。

  1. 单元测试:从最小的可测试单元(如函数、方法或类)开始测试,确保每个单元都能按预期工作。
  2. 覆盖测试:包括语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、条件组合覆盖和路径覆盖等多种逻辑覆盖方法。这些方法旨在通过设计测试用例来覆盖程序中的所有逻辑路径和条件,以发现潜在的错误。

        覆盖测试通常包括:

  • 语句覆盖:确保程序中的每个可执行语句至少被执行一次。
  • 判定覆盖:确保程序中的每个判定(分支)的每个分支至少被执行一次。
  • 条件覆盖:确保判定中的每个条件至少取到一次真值和一次假值。
  • 判断/条件覆盖:同时满足判定覆盖和条件覆盖的要求。
  • 条件组合覆盖:确保判定中所有条件的每一种组合至少出现一次。
  • 路径覆盖:确保程序中每一条可能的路径至少被执行一次,是最强的覆盖准则。

2.3、其他白盒测试

  1. 测试驱动开发(TDD):在某些情况下,白盒测试可能会与测试驱动开发结合使用,这意味着在编写实际代码之前首先编写测试用例。
  2. 持续集成:将白盒测试集成到持续集成流程中,确保每次代码更新后都能够自动运行测试,及时发现问题。gitlabCICD通常会集成test阶段,每次代码更新后会自动去跑代码中的测试用例。如果测试用例跑失败,则代码不会更新。

        从白盒测试的介绍可以看出,白盒测试可以检测代码中的每条分支和路径,揭示隐藏在代码中的错误,对代码的测试比较彻底,有助于软件最优化。但相应的要想进行充分的白盒测试需要投入大量的时间人力,投入成本高昂,覆盖所有代码路径难度大,不能替代集成测试,不适用于快节奏的敏捷开发。

3、Mockito的使用

        代码可以参考这个:【免费】一个Mockito的Demo资源-CSDN文库

        引入mockito依赖

        pom.xml

<dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-inline</artifactId>
   <version>4.3.1</version>
   <scope>test</scope>
</dependency>
PeopleInfoServiceImpl.java
package com.mockitoTest.service.impl;

import com.mockitoTest.entity.PeopleInfoDto;
import com.mockitoTest.mapper.PeopleInfoMapper;
import com.mockitoTest.service.PeopleInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.validation.ValidationException;
import java.util.List;

@Service
public class PeopleInfoServiceImpl implements PeopleInfoService {
    @Autowired
    private PeopleInfoMapper peopleInfoMapper;

    @Override
    public String registerPerson(PeopleInfoDto peopleInfoDto) {
        if (peopleInfoDto.getPeopleId() == null) {
//            throw new ValidationException("PeopleId不能为null");
            return "PeopleId不能为null";
        } else {
            List<String> peopleIdList = peopleInfoMapper.listAllPeopleId();
            if (peopleIdList.contains(peopleInfoDto.getPeopleId())) {
//                throw new ValidationException("PeopleId已经存在了");
                return "PeopleId已经存在了";
            }
        }
        if (peopleInfoDto.getIdCardNo() != null) {
            String regex = "(^\\d{15}$)|(^\\d{17}([0-9]|X)$)";
            if (!peopleInfoDto.getIdCardNo().matches(regex)) {
//                throw new ValidationException("身份证号不合法");
                return "身份证号不合法";
            }
        } else {
            return "身份证号不能为null";
        }
        if (peopleInfoDto.getPhone() != null) {
            String regex = "^(13|14|15|17|18)\\d{9}$";
            if (!peopleInfoDto.getPhone().matches(regex)) {
//                throw new ValidationException("手机号不合法");
                return "手机号不合法";
            }
        } else {
            return "手机号不能为null";
        }
        if (peopleInfoDto.getEmail() != null) {
            String regex = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$";
            if (!peopleInfoDto.getEmail().matches(regex)) {
//                throw new ValidationException("邮箱不合法");
                return "邮箱不合法";
            }
        } else {
            return "邮箱不能为null";
        }
        if (peopleInfoDto.getPwd() == null) {
//            throw new ValidationException("密码不能为null");
            return "密码不能为null";
        }
        //System.out.println(peopleInfoMapper.addPeopleInfo(peopleInfoDto));
        if (peopleInfoMapper.addPeopleInfo(peopleInfoDto) == 1) {
            return "注册成功";
        }
        return "未知错误";
    }
}

PeopleInfoServiceImpl.registerPerson()方法的测试方法(覆盖了所有分支)

package com.mockitoTest;

import com.mockitoTest.entity.PeopleInfoDto;
import com.mockitoTest.mapper.PeopleInfoMapper;
import com.mockitoTest.service.PeopleInfoService;
import com.mockitoTest.service.impl.PeopleInfoServiceImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;


import java.util.Arrays;

/**
 * @Author: Wulc
 * @CreateTime: 2024-09-15
 * @Description: 单元测试
 * @Version: 1.0
 */
class PeopleInfoServiceMockitoTest {
    //Mockito测试类不能是pubilc
    //InjectMocks会调用实际的方法(InjectMocks只能修饰具体的class,不能修饰接口)
    @InjectMocks
    private PeopleInfoServiceImpl peopleInfoService;

    @Mock
    private PeopleInfoServiceImpl peopleInfoServiceMock;

    //Mock不会调用实际方法,因此需设定其返回值。Mockito.when(调用的方法).thenReturn(你给定的返回值)
    @Mock
    private PeopleInfoMapper peopleInfoMapperMock;

    @Autowired
    private PeopleInfoService peopleInfoServiceAutowired;

    @BeforeEach
    void initBean() {
        //让注解生效
        MockitoAnnotations.initMocks(this);
    }

    @Test
    void registerPerson() {
        PeopleInfoDto peopleInfoDto = new PeopleInfoDto("qianqi", "钱七", "男", "420606198510233062", "18006588532", "18006588532@163.com", "www!@qw123456");
        PeopleInfoDto peopleInfoDto1 = new PeopleInfoDto("error", "钱七", "男", "420606198510233062", "18006588532", "18006588532@163.com", "www!@qw123456");
        //因为不想涉及实际数据库,所以所有PeopleInfoMapper类的方法,都自定一个调用此方法的返回值。
        //这样在测试过程中如果有调用到PeopleInfoMapper类中的方法,就不会调用实际方法了,而是调用一个模拟方法,不会操作数据库。
        Mockito.when(peopleInfoMapperMock.listAllPeopleId()).thenReturn(Arrays.asList("zhangsan12", "wangwu34"));
        Mockito.when(peopleInfoMapperMock.addPeopleInfo(peopleInfoDto)).thenReturn(1);
        Mockito.when(peopleInfoMapperMock.addPeopleInfo(peopleInfoDto1)).thenReturn(0);

        //校验peopleId为null
        PeopleInfoDto p0 = new PeopleInfoDto(null, "张三", "男", "420606198510233062", "15580703373", "15580703373@163.com", "www!@qw123456");
        Assertions.assertEquals("PeopleId不能为null", peopleInfoService.registerPerson(p0));

        //校验peopleId是否已存在
        PeopleInfoDto p1 = new PeopleInfoDto("zhangsan12", "张三", "男", "420606198510233062", "15580703373", "15580703373@163.com", "www!@qw123456");
        Assertions.assertEquals("PeopleId已经存在了", peopleInfoService.registerPerson(p1));

        //校验身份证号是否合法
        PeopleInfoDto p2 = new PeopleInfoDto("zhaoliu", "赵六", "男", "310107sasa196901033214", "13822297249", "13822297249@163.com", "www!@qw123456");
        Assertions.assertEquals("身份证号不合法", peopleInfoService.registerPerson(p2));

        //校验身份证号不能为null
        p2.setIdCardNo(null);
        Assertions.assertEquals("身份证号不能为null", peopleInfoService.registerPerson(p2));

        //校验手机号是否合法
        PeopleInfoDto p3 = new PeopleInfoDto("zhaoliu", "赵六", "男", "420606198510233062", "138222rr97249", "13822297249@163.com", "www!@qw123456");
        Assertions.assertEquals("手机号不合法", peopleInfoService.registerPerson(p3));

        //校验手机号不能为null
        p3.setPhone(null);
        Assertions.assertEquals("手机号不能为null", peopleInfoService.registerPerson(p3));

        //校验邮箱是否合法
        PeopleInfoDto p4 = new PeopleInfoDto("zhaoliu", "赵六", "男", "420606198510233062", "13822297249", "13822297249@##16323.com", "www!@qw123456");
        Assertions.assertEquals("邮箱不合法", peopleInfoService.registerPerson(p4));

        //校验邮箱不能为null
        p4.setEmail(null);
        Assertions.assertEquals("邮箱不能为null", peopleInfoService.registerPerson(p4));

        //校验密码为null
        PeopleInfoDto p5 = new PeopleInfoDto("zhaoliu", "赵六", "男", "420606198510233062", "13822297249", "13822297249@163.com", null);
        Assertions.assertEquals("密码不能为null", peopleInfoService.registerPerson(p5));

        //全部条件通过
        Assertions.assertEquals("注册成功", peopleInfoService.registerPerson(peopleInfoDto));

        //未知错误
        Assertions.assertEquals("未知错误", peopleInfoService.registerPerson(peopleInfoDto1));
    }
}

注意:代码中涉及的身份证号码和手机号是我从网上找的:

2023最新游戏防沉迷实名认证有效身份证号大全 - 游戏攻略 - UU站长网 (uuzzw.com)

 在线手机号码生成器 - 随机生成手机号码 (bmcx.com)

        运行一下:

        所有测试用例都通过了

         

         如果有测试用例失败,则会通过断言assert报错,这样程序就不会继续执行下去。

         Intel Idea支持生成代码覆盖率报告

4、总结

        因为工作中有被强制要求写单元测试,并且还对覆盖率有要求,所以我就学了一下Mockito。提交的代码如果单元测试覆盖率不够,会被SonarQube退回来了。

        最近在看我导师推荐的《领域驱动设计软件核心复杂性应对之道》,里面有这么一句话:“影片的剪辑人员专注于准确完成自己的工作。他担心其他看到这部电影的剪辑人员会给他挑错。在这个过程中,镜头的核心作用被忽略了”,我觉得这句话很有道理。

        哈哈,其实也就同行会挑你的错了。比如你代码写的好不好,设计是否合理。测试、产品、PM、用户根本不会关心。测试只关心你的代码能否顺利通过测试,产品只关心功能能否实现,PM只关心能否及时交付,用户只关心什么时候上线?好不好用?
       所以敏捷开发大行其道是有原因的!!!

5、参考资料 

http://gitlab.is.eccom.com.cn/erpdev/erp-microservice/eccom-ts/ts-presale/ts-presale-clue

Mockito单元测试&Mockito对Service层的测试案例_mockito注入service-CSDN博客

3.pom.xml文件 - maven中scope标签和optional标签详解_pom scope-CSDN博客

断言(编程术语)_百度百科

自从用了Mockito,单元测试全是绿的 | Java 单元测试框架 | 高效开发小技巧_哔哩哔哩_bilibili

【Mockito】单元测试如何提升代码覆盖率_哔哩哔哩_bilibili

https://zhuanlan.zhihu.com/p/478920970

文心一言 (baidu.com)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【AcWing 158周赛】

5842. 拆树 主要思路&#xff1a; 如何分割&#xff1f; 深度遍历&#xff0c;当遍历的子数的sum为总sum的三分之一即是一个分割方法。 1其中要注意节点范围为 [ − 100 &#xff0c; 100 ] [-100&#xff0c;100] [−100&#xff0c;100],所以当 总树 s u m 0 总树sum0 总树…

算法:30.串联所有单词的子串

题目 链接&#xff1a;leetcode链接 思路分析&#xff08;滑动窗口&#xff09; 这道题目类似寻找异位词的题目&#xff0c;我认为是寻找异位词的升级版 传送门:寻找异位词 为什么说像呢&#xff1f; 注意&#xff1a;这道题目中words数组里面的字符串长度都是相同的&…

C语言-数据结构 弗洛伊德算法(Floyd)邻接矩阵存储

弗洛伊德算法相比迪杰斯特拉相似的地方都是遍历邻接矩阵不断调整最短路径的信息&#xff0c;并且两种算法面对多源最短路径的时间复杂度都是O(n^3)&#xff0c;Floyd采用的是动态规划而Dijkstra是采用贪心的思想。在Floyd中我们将创建两个数组进行辅助&#xff0c;一个path二维…

sqlgun靶场训练

1.看到php&#xff1f;id &#xff0c;然后刚好有个框&#xff0c;直接测试sql注入 2.发现输入1 union select 1,2,3#的时候在2处有回显 3.查看表名 -1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()# 4.查看列名…

磁盘写入缓存区太大,如何清理C盘缓存

针对“磁盘写入缓存区太大&#xff0c;如何清理C盘缓存”的问题&#xff0c;我们可以从多个角度进行专业解答。首先&#xff0c;需要明确的是&#xff0c;“磁盘写入缓存区太大”这一表述可能涉及硬盘缓存的设置或系统缓存管理&#xff0c;但通常用户面对的问题更多是关于C盘空…

极速上云2.0范式:一键智连阿里云

在传统上云的现状与挑战&#xff1a; 专线上云太重&#xff0c;VPN上云不稳&#xff0c;云上VPC&#xff0c;云下物理网络&#xff0c;多段最后一公里...... 层层对接&#xff0c;跳跳延迟&#xff0c;好生复杂! 当你试图理解SD-WAN供应商和阿里云的文档&#xff0c;以协调路由…

【23-24年】年度总结与迎新引荐

文章目录 相关连接前言1 忙碌的备研与本科毕设2 暑期阿里之旅3 团队荣誉与迎新引荐4 项目合作意向 相关连接 个人博客&#xff1a;issey的博客 - 愿无岁月可回首 前言 自从2023年4月更新了两篇关于NLP的文章后&#xff0c;我便消失了一年半的时间。如今&#xff0c;随着学业…

[000-01-008].第05节:OpenFeign特性-重试机制

我的后端学习大纲 SpringCloud学习大纲 1.1.重试机制的默认值&#xff1a; 1.重试机制默认是关闭的&#xff0c;给了默认值 1.2.测试重试机制的默认值&#xff1a; 1.3.开启Retryer功能&#xff1a; 1.修改配置文件YML的配置&#xff1a; 2.新增配置类&#xff1a; packa…

STM32启用FPU浮点运算

这篇文章产生背景&#xff1a;其他人的文章太杂了&#xff0c;对我这种菜鸡无法接受&#xff1b; 参考文章&#xff1a; stm32h743单片机嵌入式学习笔记7-FPU_stmh743vit4-CSDN博客 stm32F407 打开 FPU(浮点运算处理器)_stm32f407开启fpu-CSDN博客 STM32F4CubeMXHal库下使能…

视频格式转为mp4(使用ffmpeg)

1、首先安装ffmpeg&#xff0c;下载链接如下 https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-6.1.1-full_build.7z 安装后确保ffmpeg程序加到PATH路径里&#xff0c;cmd执行ffmpeg -version出现下图内容表示安装成功。 2、粘贴下面的脚本到文本文件中&#xff0c;文件后缀…

CVTE-嵌入式面经一面面经准备(已通过)

目录&#xff1a; 目录&#xff1a; 一、9.9面经 1.1 什么是嵌入式&#xff1f; 1. 特点&#xff1a; 2. 应用领域&#xff1a; 3. 示例&#xff1a; 1.2 嵌入式设备开发与电脑上软件开发的区别 1. 硬件资源限制&#xff1a; 2. 操作系统&#xff1a; 3. 开发工具&#xff1a; …

K1计划100%收购 MariaDB; TDSQL成为腾讯云核心战略产品; Oracle@AWS/Google/Azure发布

重要更新 1. 腾讯全球数字生态大会与9月5日-6日举行&#xff0c;发布“5T”战略&#xff0c;包括TDSQL、TencentOS、TCE&#xff08;专有云 &#xff09;、TBDS&#xff08;大数据&#xff09;、TI &#xff08;人工智能开发平台&#xff09;等 ( [2] ) ; 并正式向原子开源基金…

学习笔记(一)

前言 一、对象 1、由类建模而成&#xff0c;是消息、数据和行为的组合 2、可以接收和发送消息&#xff0c;并利用消息进行彼此的交互。消息要包含传送给对象接收的信息 3、类的实例化&#xff1a;把类转换为对象的过程叫类的实例化。 4、对象的特性 (1) 对象有状态&#…

深度揭秘:日志打印的艺术与实战技巧,让你的代码会说话!

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 &#x1f341;日志&#x1f342;日志分模块实现讲解&#x1f343;日志等级的实现&#x1f965;日志时间*时间的获取* &#x1f308;文…

[数据集][目标检测]汽车头部尾部检测数据集VOC+YOLO格式5319张3类别

数据集制作单位&#xff1a;未来自主研究中心(FIRC) 版权单位&#xff1a;未来自主研究中心(FIRC) 版权声明&#xff1a;数据集仅仅供个人使用&#xff0c;不得在未授权情况下挂淘宝、咸鱼等交易网站公开售卖,由此引发的法律责任需自行承担 数据集格式&#xff1a;Pascal VOC格…

[数据集][目标检测]烟叶病害检测数据集VOC+YOLO格式612张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;612 标注数量(xml文件个数)&#xff1a;612 标注数量(txt文件个数)&#xff1a;612 标注类别…

如何在 Vue 3 + Element Plus 项目中实现动态设置主题色以及深色模式切换

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、项目依赖和环境配置1. VueUse2. use-element-plus-theme3. 安装依赖 三、实现深色模式切换1. 设置深色模式状态2. 模板中的深色模式切换按钮3. 深色模式的效果展示 四、动态切换主题色五、总结 一、引言 在现代…

C# 比较对象新思路,利用反射技术打造更灵活的比较工具

前言 嘿&#xff0c;大家好&#xff01;如果你之前看过我分享的文章《C# 7个方法比较两个对象是否相等》&#xff0c;你可能会意识到对象比较在实际业务中经常出现的场景。今天&#xff0c;我想继续与大家分享一个在实际项目中遇到的问题。 有一次&#xff0c;我接手了一个别…

Qt多元素控件——QTreeWidget

文章目录 QTreeWidgetQTreeWidget核心方法及信号QTreeWidgetItem核心属性及方法 QTreeWidget使用示例 QTreeWidget QTreeWidget表示树形控件&#xff0c;里面每个元素都是一个QTreeWidgetItem&#xff0c;每个QTreeWidgetItem可以包含多个文本和图标&#xff0c;每个文本/图标…

SEGGERS实时系统embOS推出Linux端模拟器

SEGGER 发布了两个新的 embOS 仿真模拟器&#xff1a;embOS Sim Linux 和 embOS-MPU Sim Linux。 通过模拟 Linux 主机系统上的硬件&#xff0c;取代物理硬件&#xff0c;为开发人员提供了一种无缝的方式来构建原型和测试应用程序。 embOS Sim Linux 端口支持 32 位和 64 位系…