【单例测试】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();

    @Override
    public 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 格式错误");
        }
        // 获取手机号归属地编号和运营商编号 然后通过编号找到区域内是 SalesRep
        String 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的使用》
觉得有用的话还请来个三连!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Redis教程0x04】详解Redis的4个高级数据类型

引言 在【Redis教程0x03】中&#xff0c;我们介绍了Redis中常用的5种基础数据类型&#xff0c;我们再来回顾一下它们的使用场景&#xff1a; String&#xff1a;存储对象、url、计数、分布式锁&#xff1b;List&#xff1a;消息队列&#xff1b;Hash&#xff1a;存储对象、购…

【Arxml专题】-29-使用Cantools将CAN Matrix Arxml自动生成C语言代码

目录 1 安装Python和Cantools 1.1 查看Python已安装的Package包 1.2 在Python中安装Cantools插件包 1.3 获取更多Cantools工具的更新动态 2 CAN Matrix Arxml自动生成C语言代码 2.1 批处理文件CAN_Matrix_Arxml_To_C.bat内容说明 2.2 CAN Matrix Arxml文件要求 2.3 如何…

JAVA 学习记录(1)

1.函数 (1)String.join(";", messages); ";" 表示分隔符&#xff0c;输出的结果&#xff1a; message; (2) Double.parseDouble(valueString); 它返回由字符串参数表示的双精度值。 (3) Double.valueOf((Float) value; float 类型的数值转化为double类…

UG NX二次开发(C#)-通过曲线组生成NURBS曲面

文章目录 1、前言2、UG NX中通过曲线组生成NURBS曲面的操作3、采用NXOpen C#方法的源代码1、前言 在UG NX中,曲线、曲面的操作使用比较多,对于创建NURBS曲面,可以通过曲线组来生成,本文以NXOpen C#的方法实现通过曲线组生成NURBS曲面的功能。对于UG NX二次开发感兴趣或者有…

-bash: ./1.sh: /bin/bash^M: bad interpreter: No such file or directory解决方法

1、执行脚本 ./1.sh时报如下错误 -bash: ./1.sh: /bin/bash^M: bad interpreter: No such file or directory 2、在Windows编辑的脚本导入Linux系统中&#xff0c;执行报错问题 yum install -y dos2unix 3、或者本地安装 rpm -ivh /mnt/Packages/dos...... 4、然…

springboot 中Aop注解切面实现收集日志与统计耗时2

一 Aop注解实现切面 1.1 工程结构 Before&#xff1a;前置通知, 在方法执行之前执行 Aroud&#xff1a;环绕通知, 围绕着方法执行 After&#xff1a;后置通知, 在方法执行之后执行 AfterReturning&#xff1a;返回通知, 在方法返回结果之后执行 AfterThrowing&#xff1a;异…

【软考高项】十七、项目管理概论之项目基本要素

1、项目基础 项目具备的一些要素&#xff1a; 1&#xff09;独特的产品、服务或成果 开展项目是为了通过可交付成果达成目标。 ◆ 目标 是所指向的结果、要取得的战略地位、要达到的目的、要获得的成果、要生产 的产品或者要提供的服务 ◆ 可交付成果 是指在某一过程、阶…

【STK】手把手教你利用STK进行导弹和反导仿真01 - STK/MMT模块安装部署

【STK】手把手教你利用STK进行导弹和反导仿真01 - STK/MMT模块安装部署 MMT模块与STK的版本是一一对应的,比如我现在手上的版本是MMT9的,那么我使用的STK的版本也必须是9版本的,如果你现在正在使用的是更高版本的STK,比如说10、11.2、11.6、12.2,那么该怎么办呢? 这个经本…

基于ssm的学生选课管理系统的设计与实现

一、功能介绍 管理员功能分析 1、管理员用户可以查询所有学生信息&#xff0c;也可以根据学生的学号、学院、专业、班级查询学生信息。可以修改学生的姓名、年龄、身份证号、性别、密码、专业、学院、班级&#xff0c;可以增加、删除学生 2、管理员用户可以查询所有教师信息&…

使用python实现布丰投针法

对于π的值&#xff0c;直到1946年的时候&#xff0c;人类才能将π的值精确计算到小数点后2037位&#xff0c;而现在的超级计算机的能力可以精确的计算到小数点后几十亿位&#xff0c;然而在计算机发明之前&#xff0c;还是使用这里的布丰投针法来计算π值&#xff0c;是最实用…

React antd中下拉框联动没有清除上一次选中的内容

bug&#xff1a; 第一次&#xff1a; 第二次&#xff1a; 解决方法&#xff1a; <Fotm.item> <SelectshowSearchplaceholder"请输入单位名称"filterOption{selectFilterOption}options{bmSelectOptions}onChange{handleDwmcChange}/></F…

非平坦地形下运动规划相关理论

1.SVD平面拟合方法 空间中的离散点得到拟合平面&#xff0c;其实就是一个最优化的过程。即求这些点到某个平面距离和最小的问题。我们知道一个先验消息&#xff0c;那就是该平面一定会过众散点的平均值。接着我们需要做的工作就是求这个平面的法向量。 根据协方差矩阵的SVD变换…

WiFi已连接却不可上网是什么原因?

很多使用wifi上网的用户都遇到过这样的问题,就是电脑已经连接了wifi,但就是上不了网。着到底是怎么回事呢?今天,极客狗带大家一起来找找WiFi已连接却不可上网是什么原因,并给出对应的解决方。 原因分析: 可能是ip地址冲突所导致,也有可能是宽带出先故障,不妨试试下面的…

MySQL:数据类型

文章目录 数据类型分类数值类型越界访问bit类型小数类型floatdecimal 字符串类型charvarchar 日期enum和set 数据类型分类 在MySQL数据库中&#xff0c;存在各种各样的数据类型&#xff1a; 针对于上述的这么多类型&#xff0c;本篇就对于这些类型的数据进行一一解释&#xff…

五分钟快速搭建个人游戏网站(1Panel)

五分钟快速搭建个人游戏网站&#xff08;1Panel&#xff09; 环境要求&#xff1a;主流 Linux 发行版本&#xff08;基于 Debian / RedHat&#xff0c;包括国产操作系统&#xff09;&#xff1b; 如果是Windows OS的可以通过WSL来实现安装。 1 介绍 1Panel 是一个基于 Web 的 L…

SSR910Q系列高性价比NVR解决方案

一、方案描述 SSR910Q&#xff0c;主芯片内核为A53双核64位最高主频为1.2Ghz处理器&#xff0c;内置2Gb DDR3&#xff0c;最高速率可支持2133Mb/s。高性能H.265/H.264/MJPEG视频编解码&#xff0c;智能处理单元&#xff08;IPU&#xff09;。支持高速I/O接口&#xff0c;如USB…

包含多个段的程序

文章目录 包含多个段的程序在代码段中使用数据在代码段中使用栈将数据、代码、栈放入不同的段 包含多个段的程序 在代码段中使用数据 考虑这样一个问题&#xff0c;编程计算以下8个数据的和&#xff0c;结果存在ax 寄存器中&#xff1a;0123H&#xff0c;0456H&#xff0c;07…

FaceBook广告账号验证教程

1.登录facebook账号,点击左边的ads manager。 2.点击Create ad创建广告。 3.选择广告投放意向。 4.填写广告信息。 5.创建广告后选择付款方式&#xff0c;这里我是使用信用卡付款。这里我是使用Fomepay的虚拟卡进行绑定的。 6.填写信用卡的持卡人姓名 卡号 有效期 安全码 7.填写…

Negative Sampling with Adaptive DenoisingMixup for Knowledge Graph Embedding

摘要 知识图嵌入(Knowledge graph embedding, KGE)的目的是通过对比正负三元组&#xff0c;将知识图中的实体和关系映射到一个低维、密集的向量空间中。在kge的训练过程中&#xff0c;由于kge只包含正三元组&#xff0c;因此负采样对于找到高质量的负三元组至关重要。大多数现…

【Redis】Redis特性

Redis 认识redisRedis特性在内存中存储数据可编程可扩展性持久化Clustering高可用性 认识redis Redis&#xff0c;英文全称是Remote Dictionary Server&#xff08;远程字典服务&#xff09;&#xff0c;是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志…