当前位置: 首页 > news >正文

使用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 {@Autowiredprivate PeopleInfoMapper peopleInfoMapper;@Overridepublic 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,不能修饰接口)@InjectMocksprivate PeopleInfoServiceImpl peopleInfoService;@Mockprivate PeopleInfoServiceImpl peopleInfoServiceMock;//Mock不会调用实际方法,因此需设定其返回值。Mockito.when(调用的方法).thenReturn(你给定的返回值)@Mockprivate PeopleInfoMapper peopleInfoMapperMock;@Autowiredprivate PeopleInfoService peopleInfoServiceAutowired;@BeforeEachvoid initBean() {//让注解生效MockitoAnnotations.initMocks(this);}@Testvoid 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为nullPeopleInfoDto 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));//校验身份证号不能为nullp2.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));//校验手机号不能为nullp3.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));//校验邮箱不能为nullp4.setEmail(null);Assertions.assertEquals("邮箱不能为null", peopleInfoService.registerPerson(p4));//校验密码为nullPeopleInfoDto 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)

相关文章:

使用Mockito进行单元测试

1、单元测试介绍 Mockito和Junit是用于单元测试的常用框架。单元测试即&#xff1a;从最小的可测试单元&#xff08;如函数、方法或类&#xff09;开始&#xff0c;确保每个单元都能按预期工作。单元测试是白盒测试的核心部分&#xff0c;它有助于发现单元内部的错误。 单元测试…...

CSS 布局三大样式简单学习

目录 1. css 浮动 1.1 效果1 1.2 效果2 1.3 效果3 1.4 效果4 2. css 定位 2.1 absolute 2.2 relative 2.3 fixed 3. css 盒子模型 3.1 效果1 3.2 效果2 3.3 效果3 3.4 效果4 1. css 浮动 1.1 效果1 1.2 效果2 1.3 效果3 1.4 效果4 2. css 定位 2.1 absolute 2.2 …...

集成运放UA741的原理与应用的探索

我们发现TI公司提供了UA741的内部电路&#xff0c;此电路包括22个晶体管&#xff0c;11个电阻&#xff0c;1个二极管&#xff0c;1个电容。 1UA741设计需求 1.1有短路保护 UA741的短路保护功能‌是指当输出端发生短路时&#xff0c;该器件能够自动保护自身&#xff0c;防止因…...

LeetCode337. 打家劫舍III

// 很好的一道题目&#xff0c;既考察递归又考察动归 // 这个版本超时了&#xff0c;原因是暴搜 // 很显然这里使用的是前序&#xff0c;那是不是应该考虑后序&#xff1f;public int rob(TreeNode root) {if (root null) {return 0;}if (root.left null && root.rig…...

python基础(二) 包和import

包的创建 文件创建命令 在 Django 中&#xff0c;python manage.py startapp first_app 这一行命令的作用是创建一个新的应用&#xff08;app&#xff09;&#xff0c;名为 first_app。在 Django 项目中&#xff0c;"app" 是实现某些功能模块的单独部分&#xff0c…...

选址模型 | 基于混沌模拟退火粒子群优化算法的电动汽车充电站选址与定容(Matlab)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于混沌模拟退火粒子群优化算法的电动汽车充电站选址与定容&#xff08;Matlab&#xff09; 问题建模&#xff1a;首先&#xff0c;需要将电动汽车充电站选址与定容问题进行数学建模&#xff0c;确定目标函数和约束…...

WPF入门教学十 资源与字典

在WPF&#xff08;Windows Presentation Foundation&#xff09;中&#xff0c;资源与字典是用于管理和重用UI元素的重要机制。它们不仅有助于保持XAML代码的整洁&#xff0c;还能提升应用程序的性能和可维护性。以下是关于WPF资源与字典的详细说明&#xff1a; 静态资源与动态…...

Ubuntu20.04配置NVIDIA+CUDA12.2+CUDNN【附所有下载资源】【亲测有效】【非常详细】

Ubuntu20.04 安装 cudatookit 12.2 cudnn 安装_ubuntu安装cuda toolkit-CSDN博客【最新】cuDNN在CUDA11.7Ubuntu20.04下的安装及卸载_cuda11.7对应的cudnn-CSDN博客...

Golang | Leetcode Golang题解之第424题替换后的最长重复字符

题目&#xff1a; 题解&#xff1a; func characterReplacement(s string, k int) int {cnt : [26]int{}maxCnt, left : 0, 0for right, ch : range s {cnt[ch-A]maxCnt max(maxCnt, cnt[ch-A])if right-left1-maxCnt > k {cnt[s[left]-A]--left}}return len(s) - left }f…...

软考高级:系统安全 -区块链特点:去中心化、开放性、自治性、安全性、匿名性

讲解 生活化例子 想象一下&#xff0c;你和朋友们玩一个共享账本的游戏。每个人都可以在账本上记账&#xff0c;没人可以单独改动账本&#xff0c;大家都可以随时查看账本内容&#xff0c;也不用再信任某个单独的人来管理账本。这就类似于区块链的工作原理。 概念讲解 去中…...

Pandas 数据分析入门详解

今日内容大纲介绍 DataFrame读写文件 DataFrame加载部分数据 DataFrame分组聚合计算 DataFrame常用排序方式 1.DataFrame-保存数据到文件 格式 df对象.to_数据格式(路径) ​ # 例如: df.to_csv(data/abc.csv) 代码演示 如要保存的对象是计算的中间结果&#xff0c;或者以…...

【网络】高级IO——epoll版本TCP服务器初阶

目录 前言 一&#xff0c;epoll的三个系统调用接口 1.1.epoll_create函数 1.1.1.epoll_create函数干了什么 1.2. epoll_ctl函数 1.2.1.epoll_ctl函数函数干了什么 1.3.epoll_wait函数 1.3.1.epoll_wait到底干了什么 1.4.epoll的工作过程中内核在干什么 二&#xff0c;…...

xml中的转义字符

文章目录 xml中的转义字符 xml中的转义字符 &amp;对应的字符是& <对应的字符是< >对应的字符是> &quot;对应的字符是" &apos;对应的字符是转义的实体引用虽然简单易用&#xff0c;但是需要记忆&#xff0c;而且如果字符串中包含大量的特殊字…...

Webpack:现代前端项目的强大打包工具

在现代前端开发中&#xff0c;随着应用的复杂性不断提高&#xff0c;我们需要一种工具来管理项目的依赖、优化代码结构并打包资源文件。Webpack 就是这样一个强大的打包工具&#xff0c;它为前端开发者提供了灵活、强大且可扩展的功能。本文将介绍 Webpack 的基本概念、安装与使…...

以root用户登陆ubuntu的桌面环境

去我的个人博客观看&#xff0c;观感更佳哦&#xff0c;&#x1f619;&#x1f619; 前言 在学习Linux的时候&#xff0c;经常都需要使用sudo权限来对配置文件进行修改&#xff0c;常用的方法就是用vim编辑器在命令行界面进行修改&#xff0c;比如sudo vim /etc/profile&#…...

《系统架构设计师教程(第2版)》第17章-通信系统架构设计理论与实践-04-其他网络架构(存储网络架构、软件定义网络架构)

文章目录 1. 存储网络架构1.1 网络连接存储 (NAS)1.2 存储区域网络&#xff08;SAN&#xff09; 2. 软件定义网络架构2.1 软件定义网络&#xff08;SDN&#xff09;2.2 SDN架构2.3 相关技术2.3.1 控制平面技术2.3.2 数据平面技术1&#xff09; 硬件处理方式4&#xff09; 软件处…...

大话Python|基础语法(上)

一、单行注释 以下代码输出一个Hello World&#xff01;字符串 在Python代码中&#xff0c;注释会自动被Python解析器忽略 print(Hello World) 二、多行注释 在Python代码中&#xff0c;注释一共有两种形式&#xff1b; 1、单行注释&#xff1a;注释的内容只有一行 2、多行…...

crosscrossover24支持的游戏有那些

CrossOver刚刚更新了24版本&#xff0c;支持《地平线零之曙光》、《以撒的结合&#xff1a;重生》等游戏。一起来看看它有哪些更新吧&#xff01;之前买过23版的用户可以在1年之内免费升级哦&#xff0c;点击这里查看升级教程。 一、功能优化 - 更新 Wine 至最新的稳定版 Wine …...

如何免费调用GPT API进行自然语言处理

在当今这个信息爆炸的时代&#xff0c;自然语言处理&#xff08;NLP&#xff09;技术正逐步渗透到我们生活的各个方面&#xff0c;从智能客服到内容创作&#xff0c;无一不彰显着其强大的应用价值。而GPT&#xff08;Generative Pre-trained Transformer&#xff09;作为NLP领域…...

vue无感刷新Token并重新请求

vue 拦截器拦截401重新请求Token 无感刷新Token 之后重新请求报401的接口 instance.interceptors.response.use(async (response) > {let { data } response;if (data.code 401 || data.code 403) {return await handleExpiredToken(response.config);}if (data.code ! …...

C++和OpenGL实现3D游戏编程【连载10】——纹理的半透明显示

1、本节实现的内容 上一节课我们讲到了图片的镂空显示,它能在显示图片时去除指定颜色的背景,那么这节课我们来说一下图片的半透明显示效果,半透明效果能给画面带来更高质量的提升,使图片显示的更自然,产生更真实的效果。下面是一个气泡向上漂浮的效果。 气泡效果 2、非纹…...

50页PPT麦肯锡精益运营转型五步法

读者朋友大家好&#xff0c;最近有会员朋友咨询晓雯&#xff0c;需要《 50页PPT麦肯锡精益运营转型五步法》资料&#xff0c;欢迎大家下载学习。 知识星球已上传的资料链接&#xff1a; 企业架构 企业架构 (EA) 设计咨询项目-企业架构治理(EAM)现状诊断 105页PPTHW企业架构设…...

Fyne ( go跨平台GUI )中文文档-小部件 (五)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16 及更高版本, ide为goland2021.2 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne ( go跨平台GUI…...

GUI编程19:贪吃蛇小游戏及GUI总结

视频链接&#xff1a;21、贪吃蛇之界面绘制_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p21&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.游戏中用的的图片素材 1.贪吃蛇游戏的主启动类StartGame&#xff1b; package com.yundait.snake;import j…...

linux StarRocks 安装

一、检查服务器是否支持avx2&#xff0c;如果执行命令显示空&#xff0c;则不支持&#xff0c;那么安装后无法启动BE cat /proc/cpuinfo |grep avx2我的支持显示如下&#xff1a; 二、安装 docker run -p 9030:9030 -p 8030:8030 -p 8040:8040 -p 9001:9000 --privilegedtrue…...

解决RabbitMQ设置x-max-length队列最大长度后不进入死信队列

解决RabbitMQ设置x-max-length队列最大长度后不进入死信队列 问题发现问题解决方法一&#xff1a;只监听死信队列&#xff0c;在死信队列里面处理业务逻辑方法二&#xff1a;修改预取值 问题发现 最近再学习RabbitMQ过程中&#xff0c;看到关于死信队列内容&#xff1a; 来自队…...

【解决】chrome 谷歌浏览器,鼠标点击任何区域都是 Input 输入框的状态,能看到输入的光标

chrome 谷歌浏览器&#xff0c;鼠标点击任何区域都是 Input 输入框的状态&#xff0c;能看到输入的光标 今天打开电脑的时候&#xff0c;网页中任何文本的地方&#xff0c;只要鼠标点击&#xff0c;就会出现一个输入的光标&#xff0c;无论在哪个站点哪个页面都是如此。 我知道…...

使用python操作数据库

文章目录 一、问题背景二、安装python三、代码示例四、总结 一、问题背景 在日常开发过程中&#xff0c;随着项目进展和业务功能的迭代&#xff0c;我们需要对数据库的表结构进行修改&#xff0c;向部分表中追加字段&#xff0c;并对追加后的字段进行数据填充。但是如果需要追加…...

[Redis] 渐进式遍历+使用jedis操作Redis+使用Spring操作Redis

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...

排序----数据结构

Comparable Integer Double 默认情况下都是按照升序排列的 string 按照字母再ASCII码表中对应的数字升序进行排列 冒泡排序 时间复杂度O(x^2) 选择排序 时间复杂度O(x^2) 插入排序 时间复杂度O(x^2) 希尔排序 时间复杂度O(x) 归并排序 时间复杂度O(nlogn) 快速排序...