当前位置: 首页 > 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)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【Linux】查看操作系统开机时初始化的驱动模块列表的一个方法
  • AI教你学Python 第10天 :参数与返回值
  • HarmonyOS 速记
  • 18、Python如何读写csv文件
  • Netty笔记09-网络协议设计与解析
  • 佛山网站制作与设计
  • [掌握API速率限制:如何高效管理请求频率]
  • Simulink仿真理想二极管模型
  • 【C#生态园】虚拟现实与增强现实:C#开发库全面评估
  • 【Linux】Linux项目自动化构建工具-make/Makefile
  • Go语言并发编程之Channels详解
  • 【Java】基础语法介绍
  • 力扣刷题之2398.预算内的最多机器人数目
  • 【Windows】使用 WMI 获取系统版本信息
  • Errorresponsefromdaemon:toomanyrequests:Youhavereachedyourpullratelimit.
  • HTTP 简介
  • HTTP中的ETag在移动客户端的应用
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • leetcode386. Lexicographical Numbers
  • LeetCode刷题——29. Divide Two Integers(Part 1靠自己)
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • Python连接Oracle
  • Redash本地开发环境搭建
  • Redis中的lru算法实现
  • Terraform入门 - 3. 变更基础设施
  • Tornado学习笔记(1)
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • windows下mongoDB的环境配置
  • Yii源码解读-服务定位器(Service Locator)
  • 半理解系列--Promise的进化史
  • 老板让我十分钟上手nx-admin
  • 爬虫模拟登陆 SegmentFault
  • 排序(1):冒泡排序
  • 嵌入式文件系统
  • 如何设计一个比特币钱包服务
  • 如何实现 font-size 的响应式
  • 如何使用 OAuth 2.0 将 LinkedIn 集成入 iOS 应用
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • ​ssh免密码登录设置及问题总结
  • ‌分布式计算技术与复杂算法优化:‌现代数据处理的基石
  • # AI产品经理的自我修养:既懂用户,更懂技术!
  • # Maven错误Error executing Maven
  • #java学习笔记(面向对象)----(未完结)
  • #数学建模# 线性规划问题的Matlab求解
  • (done) NLP “bag-of-words“ 方法 (带有二元分类和多元分类两个例子)词袋模型、BoW
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (STM32笔记)九、RCC时钟树与时钟 第一部分
  • (vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束
  • (笔试题)分解质因式
  • (二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真
  • (二刷)代码随想录第16天|104.二叉树的最大深度 559.n叉树的最大深度● 111.二叉树的最小深度● 222.完全二叉树的节点个数
  • (过滤器)Filter和(监听器)listener
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (南京观海微电子)——I3C协议介绍