使用Mockito进行单元测试

ops/2024/11/13 9:13:45/

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)


http://www.ppmy.cn/ops/112062.html

相关文章

93、k8s之hpa+helm

一、HPA HPA: pod的数量进行扩缩容 针对对控制器创建的pod deployment&#xff1a; replicas: 静态&#xff1a;edit yaml&#xff1a;apply -f HPA&#xff1a;基于cpu的利用率来实现pod的数量大的自动伸缩。 Horizontal Pod Autoscaling yaml 文件------主流------…

基于ssm+vue+uniapp的新生报到系统小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

Arduino IDE离线配置第三方库文件-ESP32开发板

简洁版可以使用uget等&#xff0c;将文件下载到对应文件夹下&#xff0c;然后安装。 esp32之arduino配置下载提速 录屏 Arduino IDE离线配置第三方库文件ESP32 资源 Linux https://download.csdn.net/download/ZhangRelay/89749063 第三方开发板 非默认支持的开发板 linu…

鸿蒙NEXT生态应用核心技术理念:一次开发,多端部署

在万物互联时代&#xff0c;应用开发者也面临设备底座从手机单设备到全场景多设备的转变&#xff0c;通过全场景多设备作为全新的底座&#xff0c;为消费者带来万物互联时代更为高效、便捷的体验。 在万物智联时代重要机遇期&#xff0c;鸿蒙结合移动生态发展的趋势&#xff0…

HarmonyOS NEXT 封装实现好用的网络模块(基于最新5.0的API12)

在 HarmonyOS-NEXT 开发中&#xff0c;网络请求是应用开发中不可或缺的一部分。为了提高开发效率和代码复用性&#xff0c;我们可以封装一个好用的网络模块组件。本文将介绍如何在 HarmonyOS-NEXT 中封装一个功能强大且易于使用的网络模块组件。 封装目的 网络模块使用的频率最…

并发容器(Map、List、Set)实战及其原理分析

1. JUC包下的并发容器 Java的集合容器框架中&#xff0c;主要有四大类别&#xff1a;List、Set、Queue、Map&#xff0c;大家熟知的这些集合类ArrayList、LinkedList、HashMap这些容器都是非线程安全的。 所以&#xff0c;Java先提供了同步容器供用户使用。同步容器可以简单地…

【玩转贪心算法专题】452. 用最少数量的箭引爆气球是【中等】

【玩转贪心算法专题】452. 用最少数量的箭引爆气球是【中等】 1、力扣链接 https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/ 2、题目描述 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中p…

【医药行业】实施SAP有哪些医药行业GXP的合规要求和注意事项

作为实施过辉瑞和赛诺菲医药行业的项目&#xff0c;总结了如下&#xff1a; 在医药行业中&#xff0c;GxP&#xff08;Good Practices&#xff0c;良好规范&#xff09;是一系列标准与指南&#xff0c;旨在确保制药、医疗设备和生物制品的质量与合规性。GxP包括多个领域&#x…