【单例测试】Mockito实战

目录

  • 一、项目介绍
  • 二、业务代码
    • 2.1 导入依赖
    • 2.2 entity
    • 2.3 Dao
    • 2.4 业务代码
  • 三、单元测试
    • 3.1 生成Test方法
    • 3.2 引入测试类
    • 3. 3 测试前准备
    • 3.4 测试
      • 3.4.1 name和phone参数校验
      • 3.4.2 测试数据库访问
    • 3.4.3 数据库反例
  • 总结

前面我们提到了《【单元测试】一文读懂java单元测试》
简单介绍了《【单元测试】单元测试之Mockito的使用》

今天一起来看看项目中mockito怎么用的

项目源码GitHub

一、项目介绍

在这里插入图片描述
这个案例比较简单,模拟了一个数据统计系统,由地推员输入客户的姓名和手机号,系统根据客户的手机号和归属地和所属的运营商将客户群体分组,分配给相应的销售组,最后构建用户对象存入数据表。

二、业务代码

2.1 导入依赖

<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.2</version>
</dependency>
<!--        <dependency>-->
<!--            <groupId>org.mockito</groupId>-->
<!--            <artifactId>mockito-core</artifactId>-->
<!--            <version>4.3.1</version>-->
<!--        </dependency>-->
<dependency><groupId>org.mockito</groupId><artifactId>mockito-inline</artifactId><version>4.3.1</version><scope>test</scope>
</dependency>

注意:mockito-inlinemockito-core不能同时打开

2.2 entity

PhoneNumber :

public class PhoneNumber {private final String number;private final String pattern = "^0?[1-9]{2,3}-?\\d{8}$";public String getNumber() {return number;}//在含参构造器中进行参数校验public PhoneNumber (String number) throws ValidationException {if (number == null) {throw new ValidationException("number 不能为空");} else if (isValid(number)) {throw new ValidationException("number 格式错误");}this.number = number;}private boolean isValid(String number) {return number.matches(pattern);}private static String getAreaCode(String number) {//具体实现逻辑return "a";}private static String getOperatorCode(String number) {//具体实现逻辑return "b";}
}

SalesRep:

public class SalesRep {private String repId;public SalesRep(String repId) {this.repId = repId;}public String getRepId() {return repId;}public void setRepId(String repId) {this.repId = repId;}
}

User:

public class User {private String name;private String phone;private String repId;public User() {}public User(String name, String phone, String repId) {this.name = name;this.phone = phone;this.repId = repId;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public String getRepId() {return repId;}public void setRepId(String repId) {this.repId = repId;}
}

2.3 Dao

UserDao :

public class UserDao {public User save(String name, String phone, String repId) throws SQLException {return new User(name, phone, repId);}
}

SalesDao:

public class SalesDao {public SalesRep findRep(String areaCode, String operatorCode) {if ("a".equals(areaCode) && "b".equals(operatorCode)) {return new SalesRep("Echo");}return null;}
}

2.4 业务代码

RegistrationServiceImpl:

public class RegistrationServiceImpl implements RegistrationService {SalesDao salesDao = new SalesDao();UserDao userDao = new UserDao();@Overridepublic User register(String name, String phone) throws Exception {// 参数校验if (name == null || name.length() == 0) {throw new ValidationException("number 不能为空");}if (phone == null || !isValid(phone)) {throw new ValidationException("phone 格式错误");}// 获取手机号归属地编号和运营商编号 然后通过编号找到区域内是 SalesRepString areaCode = FindUtils.getAreaCode(phone);String operatorCode = FindUtils.getOperatorCode(phone);User user;try {SalesRep rep = salesDao.findRep(areaCode, operatorCode);// 最后创建用户,落盘,然后返回user = userDao.save(name, phone, rep.getRepId());} catch (SQLException e) {throw new DAOException("SQLException thrown " + e.getMessage());}return user;}private boolean isValid(String phone) {String pattern = "^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$";boolean flag = phone.matches(pattern);return flag;}}

其他代码见GitHub

三、单元测试

3.1 生成Test方法

1)点击我们需要生成测试的类,右击鼠标Generate->Test
在这里插入图片描述

2)选择对应的Junit版本和测试方法

在这里插入图片描述
3)这样在test目录下生成测试类
在这里插入图片描述

3.2 引入测试类

使用@Spy注解引入RegistrationServiceImpl这个待测试的类

在这里插入图片描述

这里有两个数据库操作salesDao.findRep和userDao.save,现在模拟salesDao正例操作,userDao反例操作,抛出异常

在这里插入图片描述

首先对userDao进行mock,模拟出对象,进行打桩

在这里插入图片描述
@InjectMocks注解的作用是将Mock出来的UserDao对象注入到RegistrationServiceImpl类当中

3. 3 测试前准备

引入@BeforeEach前置处理器,开启@Spy和@Mock

@BeforeEach
void setUp(){MockitoAnnotations.openMocks(this);
}

3.4 测试

3.4.1 name和phone参数校验

在这里插入图片描述
如果是空就或者0抛出异常

@Test
void register() throws Exception {String name = null;String phone = "15012345678";try {registrationService.register(name, phone);Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位} catch (Exception e) {Assertions.assertTrue(e instanceof ValidationException);}
}

这里name为null,会抛出异常,catch会捕捉到异常,通过 Assertions.assertTrue去接收异常,Assertions.fail(“这里说明程序挂了”);用于定位异常;

在这里插入图片描述
鼠标右击第三个,以单元测试覆盖路运行

在这里插入图片描述
运行结果,单元测试行覆盖率只有27%,我们只测试了26、27行,绿色代表我们覆盖了的地方,红色代表我们没有覆盖的地方。

接下来,我们对phone这个参数进行测试,

@Test
void register() throws Exception {String name = null;String phone = "15012345678";try {registrationService.register(name, phone);Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位} catch (Exception e) {Assertions.assertTrue(e instanceof ValidationException);}name ="Hanson";phone = null;try {registrationService.register(name, phone);Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位} catch (Exception e) {Assertions.assertTrue(e instanceof ValidationException);}
}

测试:

在这里插入图片描述

单元测试行覆盖率变成了38%,

3.4.2 测试数据库访问

@Test
void register() throws Exception {String name = null;String phone = "15012345678";try {registrationService.register(name, phone);Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位} catch (Exception e) {Assertions.assertTrue(e instanceof ValidationException);}name ="Hanson";phone = null;try {registrationService.register(name, phone);Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位} catch (Exception e) {Assertions.assertTrue(e instanceof ValidationException);}phone = "15012345678";MockedStatic<FindUtils> staticService = Mockito.mockStatic(FindUtils.class);staticService.when(() -> FindUtils.getAreaCode("15012345678")).thenReturn("a");// 可以返回具体的操作staticService.when(() -> FindUtils.getOperatorCode("15012345678")).thenReturn("b");// 可以返回具体的操作// 数据库操作正常Mockito.when(salesDao.findRep("a","b")).thenCallRealMethod();Mockito.when(userDao.save(name,phone,"Hanson")).thenCallRealMethod();User user = registrationService.register(name, phone);Assertions.assertEquals("Hanson",user.getRepId());
}

测试结果:
在这里插入图片描述
单元测试行覆盖率从38%变成了88%,为什么不是100%呢?那是因为这些都是正例,所有catcah没有捕获到异常

3.4.3 数据库反例

我们通过thenThrow()方法返回SQLException模拟异常,通过catch捕捉异常进行断言

@Test
void register() throws Exception {String name = null;String phone = "15012345678";try {registrationService.register(name, phone);Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位} catch (Exception e) {Assertions.assertTrue(e instanceof ValidationException);}name ="Hanson";phone = null;try {registrationService.register(name, phone);Assertions.fail("这里说明程序挂了");// 如果执行代码能快速定位} catch (Exception e) {Assertions.assertTrue(e instanceof ValidationException);}phone = "15012345678";MockedStatic<FindUtils> staticService = Mockito.mockStatic(FindUtils.class);staticService.when(() -> FindUtils.getAreaCode("15012345678")).thenReturn("a");// 可以返回具体的操作staticService.when(() -> FindUtils.getOperatorCode("15012345678")).thenReturn("b");// 可以返回具体的操作// 数据库操作正常Mockito.when(salesDao.findRep("a","b")).thenCallRealMethod();Mockito.when(userDao.save(name,phone,"Hanson")).thenCallRealMethod();User user = registrationService.register(name, phone);Assertions.assertEquals("Hanson",user.getRepId());// 数据库操作异常Mockito.when(userDao.save(name,phone,"Hanson")).thenThrow(new SQLException());try {registrationService.register(name,phone);} catch (Exception e) {Assertions.assertTrue(e instanceof DAOException);}

测试:

在这里插入图片描述
单元测试方法覆盖率和行覆盖率都是100%,完毕

总结

首先,确定测试类,对测试类进行有引入,其次确定是否有其他对象的注入,例如demo中的salesDao和userDao两个对象,还有可能是一些Service,我们需要用@Spy@Mock两个注解将对象注入进来,并在测试类对象是添加@InjectMocks将对象注入到测试类对象中;

其次我们要开启mock和spy方法(使用前置处理器,MockitoAnnotations.openMocks(this);

测试方法正反例写,静态方法使用Mockito.mockStatic方法将静态方法所属的类mock出来,在对这个方法进行打桩;

对于try-catch,对结果进行分类,以异常捕获和未捕获进行校验;

Jnuit文章见《【单元测试】一文读懂java单元测试》
单元测试之Mockito见文章《【单元测试】单元测试之Mockito的使用》
觉得有用的话还请来个三连!!!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python小案例:数字炸弹游戏(优化版)

优化内容 上次所写的 数字炸弹案例 中所留了的bug&#xff1a;   a. 两次死循环&#xff0c;其实可以只用一次的&#xff1b;☑   b. 如果其中一个人输入的数据是无效的后游戏将会重新开始&#xff0c;规则上来讲是直接淘汰该玩家 ☑ 本次利用列表坐标 name_Nub 叠加&#…

【Linux】线程预备知识{远程拷贝/重入函数与volatile关键字/认识SIGCHILD信号/普通信号/实时信号}

文章目录 0.远程拷贝1.重入函数与volatile关键字2.认识SIGCHILD信号3.普通信号/实时信号 0.远程拷贝 打包资源&#xff1a;tar czf code.tgz *远程传输&#xff1a;scp code.tgz usr服务器ip:/home/usr/路径解压&#xff1a;tar xzf code.tgz 1.重入函数与volatile关键字 先看…

LLM - 大语言模型的分布式训练 概述

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/136924304 大语言模型的分布式训练是一个复杂的过程&#xff0c;涉及到将大规模的计算任务分散到多个计算节点上。这样做的目的是为了处…

10-shell编程-辅助功能

一、字体颜色设置 第一种: \E[1:色号m需要变色的字符串\E[0m 第二种: \033[1:色号m需要变色的字符串\033[0m ########################### \E或者\033 #开启颜色功能 [1: #效果 31m #颜色色号 \E[0m #结束符 1&#xff0c;颜色案例 2&#xff0c;效果案例 二、gui&am…

C语言内存函数之 memcmp函数

memcmp函数的记忆&#xff1a;mem表示内存&#xff0c;单位是字节&#xff0c;表示以单位字节来进行操作&#xff1b;头文件是string.h&#xff0c;cmp是compare的缩写&#xff0c;表示比较。总的意思就是在规定的内存下以字节为单位一个字节一个字节的进行比较。 memcmp函数的…

CI/CD实战-jenkins部署 3

安装 软件下载地址&#xff1a;Index of /jenkins/redhat/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 启动服务 安装推荐插件 不新建用户&#xff0c;使用admin账号登录 修改一下初始密码 新建项目测试 安装git命令 生成密钥 在gitlab中上传公钥 修改ssh 创建中…

【双指针】Leetcode 202.快乐数

题目解析 Leetcode 202.快乐数 看完题目描述相信大家已经知晓题目的含义&#xff0c;我们通过一张图再剖析一下题目含义 快乐数或者非快乐数都是可以成环的&#xff0c;这个是数学上已经证明了的。所以这道题的最后含义就是分辨出环中全部是1或者全部没有1的 双指针 成环问…

【SpringBoot】如何定义接口

定义get接口 使用GetMapping定义一个基本get接口 RestController //表示定义一个json格式返回给前端 public class test {private Map<String,Object> map new HashMap<>();GetMapping(value "/test") //定义接口路径public Object userInfo(Strin…

【CVPR2024】PEM: Prototype-based Efficient MaskFormer for Image Segmentation

研究挑战&#xff1a;基于Transformer的架构在图像分割领域取得了显著的成果&#xff0c;但这些架构通常需要大量的计算资源&#xff0c;特别是在边缘设备上。 为了解决这个问题&#xff0c;作者提出了 PEM&#xff08;prototype-based efficient MaskFormer&#xff09;&…

在Linux/Debian/Ubuntu上通过 Azure Data Studio 管理 SQL Server 2019

Microsoft 提供 Azure Data Studio&#xff0c;这是一种可在 Linux、macOS 和 Windows 上运行的跨平台数据库工具。 它提供与 SSMS 类似的功能&#xff0c;包括查询、脚本编写和可视化数据。 要在 Ubuntu 上安装 Azure Data Studio&#xff0c;可以按照以下步骤操作&#xff1…

YZ系列工具之YZ09: VBA_Excel之读心术

我给VBA下的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套一部VBA手册&#xff0c;教程分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的…

2024年3月26日 十二生肖 今日运势

小运播报&#xff1a;2024年3月26日&#xff0c;星期二&#xff0c;农历二月十七 &#xff08;甲辰年丁卯月己丑日&#xff09;&#xff0c;法定工作日。 红榜生肖&#xff1a;鸡、鼠、猴 需要注意&#xff1a;马、狗、羊 喜神方位&#xff1a;东北方 财神方位&#xff1a;…

C语言----strcpy和strcat的使用和模拟实现

一&#xff0c;strcpy()函数 strcpy() 函数是 C语言中一个非常重要的字符串处理函数&#xff0c;其功能是将一个字符串复制到另一个字符串中。该函数原型如下&#xff1a; char*strcpy(char*dest,const char*src) 其中&#xff0c;dest 表示目标字符串&#xff0c;即将被复制到…

C语言动态内存的管理

前言 本篇博客就来探讨一下动态内存&#xff0c;说到内存&#xff0c;我们以前开辟空间大小都是固定的&#xff0c;不能调整这个空间大小&#xff0c;于是就有动态内存&#xff0c;可以让我们自己选择开辟多少空间&#xff0c;更加方便&#xff0c;让我们一起来看看动态内存的有…

使用html做一个2048小游戏

下载地址: https://pan.xunlei.com/s/VNtiF13HxmmE4gglflvS1BUhA1?pwdvjrt# 提取码&#xff1a;vjrt”

前端基础 Vue -组件化基础

1.全局组件 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><script src&…

React Developer Tools安装

问题描述 在react开发中&#xff0c;需要插件来帮助我们开发&#xff0c;例如&#xff1a; 方法 &#xff08;可能需要魔法 进去后搜索&#xff1a; 点击下载即可

【OpenModelica】1 OpenModelica项目架构

1 OpenModelica项目架构 文章目录 1 OpenModelica项目架构一、 架构总览图二、OpenModelica各部分作用 一、 架构总览图 OpenModelica 环境由几个相互连接的子系统组成&#xff0c;如图 1.1 所示。 其中包括&#xff1a; MDT Eclipse 插件图形模型编辑器/浏览器文本模型编辑器…

一篇复现Docker镜像操作与容器操作

华子目录 Docker镜像操作创建镜像方式1docker commit示例 方式2docker import示例1&#xff1a;从本地文件系统导入示例2&#xff1a;从远程URL导入注意事项 方式3docker build示例1&#xff1a;构建镜像并指定名称和标签示例2&#xff1a;使用自定义的 Dockerfile 路径构建镜像…

瑞_Redis_商户查询缓存_什么是缓存

文章目录 项目介绍1 短信登录2 商户查询缓存2.1 什么是缓存2.1.1 缓存的应用场景2.1.2 为什么要使用缓存2.1.3 Web应用中缓存的作用2.1.4 Web应用中缓存的成本 附&#xff1a;缓存封装工具类 &#x1f64a; 前言&#xff1a;本文章为瑞_系列专栏之《Redis》的实战篇的商户查询缓…