使用Mockito进行单元测试

news/2024/11/15 2:02:43/

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/news/1528255.html

相关文章

半导体器件制造5G智能工厂数字孪生物联平台,推进制造业数字化转型

半导体器件制造行业作为高科技领域的核心驱动力&#xff0c;正积极探索和实践以5G智能工厂数字孪生平台为核心的新型制造模式。这一创新不仅极大地提升了生产效率与质量&#xff0c;更为制造业的未来发展绘制了一幅智能化、网络化的宏伟蓝图。 在半导体器件制造5G智能工厂中&a…

Maven笔记(一):基础使用【记录】

Maven笔记&#xff08;一&#xff09;-基础使用 Maven是专门用于管理和构建Java项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化的项目结构 Maven提供了一套标准化的项目结构&#xff0c;所有IDE(eclipse、myeclipse、IntelliJ IDEA 等 项目开发工具) 使…

mat (Eclipse Memory Analyzer Tool)使用以及详解

前言 在Java开发中&#xff0c;内存问题往往不易被发现&#xff0c;但它们可能导致应用性能下降甚至崩溃。Eclipse Memory Analyzer Tool&#xff08;MAT&#xff09;是一个强大的开源工具&#xff0c;专门用于分析Java堆转储&#xff08;heap dumps&#xff09;文件&#xff…

小程序服务零工市场

零工市场小程序有着信息发布、岗位匹配、线上接单、零工人员保障险参保、技能培训、费用结算、完工确认、服务评价、纠纷调解等功能&#xff0c;为求职者和雇主搭建一座高效、便捷、精准的对接桥梁。 用工单位通过小程序的“雇主找人”&#xff0c;发布招聘信息&#xff0c;找到…

拓扑排序专题篇

目录 前言 课程表 课程表II 课程表IV 火星词典 前言 拓扑排序是指对一个有向无环图的节点进行排序之后得到的序列&#xff0c;如果存在一条从节点A指向节点B的边&#xff0c;那么在拓扑排序的序列中节点A出现在节点B的前面。一个有向无环图可以有一个或多个拓扑排序序列&a…

JWT跨域认证

Session认证 用户认证的流程&#xff1a; 用户向服务器发送用户名和密码。 服务器验证通过后&#xff0c;在当前对话(session)里面保存相关数据&#xff0c;比如用户角色、登录时间等。 服务器向用户返回一个session_id,写入用户的Cookie。 用户随后的每一次请求&#xff0c;…

导电滑环在工业设备中的作用分析

导电滑环作为现代工业设备中的关键组件&#xff0c;广泛应用于各种机械和电子系统中。本文将探讨导电滑环的工作原理及其在不同应用领域中的重要作用。 导电滑环的工作原理主要基于电气传导与机械旋转的结合。导电滑环通常由环形导体和刷子组成&#xff0c;刷子紧贴在滑环表面…

前缀和与差分(二维)

二维前缀和 下面是一个二维数组&#xff0c;我们要求&#xff08;1&#xff0c;1&#xff09;到&#xff08;2&#xff0c;2&#xff09;区间内的所有元素的和&#xff0c;最原始的方法就是遍历每个元素然后一个一个加起来&#xff0c;此时时间复杂度为O(n*m)。 我们之前学过…