【单例测试】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.mzph.cn/news/768474.shtml

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

相关文章

IDEA Android新建项目基础

title: IDEA Android基础开发 search: 2024-03-16 tags: “#JavaAndroid开发” 一、构建基本项目 在使用 IDEA 进行基础的Android 开发时&#xff0c;我们可以通过IDEA自带的新建项目功能进行Android应用开发基础架构的搭建&#xff0c;可以直接找到 File --> New --> …

vue的history路由实现形式

vue的路由实现形式 SPA single page web application&#xff0c;单页Web应用 简单的说SPA就是一个WEB项目只有一个HTML页面&#xff0c;一旦页面加载完成&#xff0c;SPA不会因为用户的操作而进行页面的重新加载和跳转。取而代之的是利用JS动态的改变HTML的内容&#xff0c…

代码随想录算法训练营day19 | 二叉树阶段性总结

各个部分题目的代码题解都在我往日的二叉树的博客中。 (day14到day22) 目录 二叉树理论基础二叉树的遍历方式深度优先遍历广度优先遍历 求二叉树的属性二叉树的修改与制造求二叉搜索树的属性二叉树公共最先问题二叉搜索树的修改与构造总结 二叉树理论基础 二叉树的理论基础参…

基于nodejs+vue学生作业管理系统python-flask-django-php

他们不仅希望页面简单大方&#xff0c;还希望操作方便&#xff0c;可以快速锁定他们需要的线上管理方式。基于这种情况&#xff0c;我们需要这样一个界面简单大方、功能齐全的系统来解决用户问题&#xff0c;满足用户需求。 课题主要分为三大模块&#xff1a;即管理员模块和学生…

平台介绍-搭建赛事运营平台(1)

平台的一个很重要的市场方向是为企业搭建各类运营平台。运营平台是这类企业的核心系统&#xff0c;例如对银行而言就是柜台系统&#xff0c;对于电商而言就是电子商城。运营平台和内部信息平台的显著区别是要面向外部C端客户。内部信息平台的受众只是企业内部人员。 最近签约开…

HAL STM32G4 +ADC手动触发采集+各种滤波算法实现

HAL STM32G4 ADC手动触发采集各种滤波算法实现 &#x1f4cd;相关篇《HAL STM32G4 TIM1 3路PWM互补输出VOFA波形演示》 ✨本篇内容也是继欧拉电子相关无刷电机驱动控制学习的相关基础内容。仅作为个人笔记记录使用。 &#x1f4cd;感谢网友提供的相关内容《基于STM32的ADC采样及…

上位机图像处理和嵌入式模块部署(qmacvisual轮廓查找)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们说过&#xff0c;图像的处理流程一般都是这样的&#xff0c;即灰度化-》降噪-》边缘检测-》二值化-》开闭运算-》轮廓检测。虽然前面的几个…

LeetCode 面试经典150题 14.最长公共前缀

题目&#xff1a; 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 ""。 思路&#xff1a; 代码&#xff1a; class Solution {public String longestCommonPrefix(String[] strs) {if (strs.length 0) {return &…

知攻善防应急靶场-Linux(2)

前言&#xff1a; 堕落了三个月&#xff0c;现在因为被找实习而困扰&#xff0c;着实自己能力不足&#xff0c;从今天开始 每天沉淀一点点 &#xff0c;准备秋招 加油 注意&#xff1a; 本文章参考qax的网络安全应急响应和知攻善防实验室靶场&#xff0c;记录自己的学习过程&am…

python绘图matplotlib——使用记录1

本博文来自于网络收集&#xff0c;如有侵权请联系删除 使用matplotlib绘图 1 常用函数汇总1.1 plot1.2 legend1.3 scatter1.4 xlim1.5 xlabel1.6 grid1.7 axhline1.7 axvspan1.8 annotate1.9 text1.10 title 2 常见图形绘制2.1 bar——柱状图2.2 barh——条形图2.3 hist——直…

flutter3_douyin:基于flutter3+dart3短视频直播实例|Flutter3.x仿抖音

flutter3-dylive 跨平台仿抖音短视频直播app实战项目。 全新原创基于flutter3.19.2dart3.3.0getx等技术开发仿抖音app实战项目。实现了类似抖音整屏丝滑式上下滑动视频、左右滑动切换页面模块&#xff0c;直播间进场/礼物动效&#xff0c;聊天等模块。 运用技术 编辑器&#x…

git标签的简单操作

创建标签 git tag v1.0 # 对head指向的commit创建标签 git tag v1.1 commit_id # 对指定的commit创建标签 git tag v2.0 -a -m "标签注释" commit_id # 创建注释标签查看标签 git tag -l v1* # 查看标签&#xff0c;匹配v1开头的 git show v2.0 # 查看标签详细信息…

Qt如何重写closeEvent

在 Qt 中&#xff0c;重写 closeEvent 函数是处理窗口关闭事件的一种方式。当你关闭一个 Qt 窗口时&#xff0c;该窗口会接收到一个 QCloseEvent 对象。通过重写窗口类的 closeEvent 函数&#xff0c;你可以自定义窗口关闭时的行为。 下面是一个简单的例子&#xff0c;展示了如…

Netty剖析 - Why Netty

文章目录 Why NettyI/O 请求的两个阶段I/O 模型Netty 如何实现自己的 I/O 模型线程模型 - 事件分发器&#xff08;Event Dispather&#xff09;弥补 Java NIO 的缺陷更低的资源消耗网络框架的选型Netty 发展现状Netty 的使用 Why Netty I/O 模型、线程模型和事件处理机制优化&a…

php搭建websocket

1.项目终端执行命令&#xff1a;composer require topthink/think-worker 2.0.x 2.config多出三个配置文件&#xff1a; 3.当使用php think worker:gateway命令时&#xff0c;提示不支持Windows。 4.打包项目为zip格式 5.打包数据库 6.阿里云创建记录 7.宝塔面板新增站点…

Vue3 上手笔记

1. Vue3简介 2020年9月18日&#xff0c;Vue.js发布版3.0版本&#xff0c;代号&#xff1a;One Piece&#xff08;n 经历了&#xff1a;4800次提交、40个RFC、600次PR、300贡献者 官方发版地址&#xff1a;Release v3.0.0 One Piece vuejs/core 截止2023年10月&#xff0c;最…

网盘——数据库操作

关于网盘的数据库模块&#xff0c;主要有以下几个内容&#xff1a;定义数据库操作类、将数据库操作类定义成单例模式、数据库操作 数据库是在Qt里面&#xff0c;定义成操作类&#xff0c;专门用这个类产生对象&#xff0c;对数据库实现操作&#xff0c;那么我们在产生对象的时…

BMS设计中的短路保护和MOSFET选型(下)

二、MOSFET参数 1、电气参数 (1)VGS :加在栅源两极之间的最大电压,一般为:-20V-+20V。 VGS额定电压是栅源两极间可以施加的最大电压。设定该额定电压的主要目的是防止电压过高导致的栅氧化层损伤。实际栅氧化层可承受的电压远高于额定电压,但是会随制造工艺的不同而改变…

01-机器学习概述

机器学习的定义 机器学习是一门从数据中研究算法的科学学科。 机器学习直白来讲&#xff0c; 就是根据已有的数据&#xff0c;进行算法选择&#xff0c;并基于算法和数据 构建模型&#xff0c;最终对未来进行预测。 机器学习就是一个模拟人决策过程的一种程序结构。 机器学…

Django——forms组件

Django——forms组件 forms组件&#xff1a;通过后端在Django中定义forms类&#xff0c;可以在 html 中动态的生成一个表单&#xff1b;检验用户提交的数据。 在应用中创建一个名为 &#xff1a;forms 的py文件 # 导入 forms 组件 from django import forms# 自定义 forms …