当前位置: 首页 > news >正文

如何写好单测

1、为什么要写单测?

单测即单元测试(Unit Test),是对软件的基本组成单元进行的测试,比如函数、过程或者类的方法。其意义是:

  • 功能自测,发现功能缺陷
  • 自我Code Review
  • 测试驱动开发
  • 促进代码重构并提升代码质量

1.1、代码覆盖率

单测质量最直接表现的指标就是代码覆盖率,分为语句覆盖(Statement coverage)、分支覆盖(Branch coverage)、条件覆盖(Condition converage)、路径覆盖(Path coverage)

1.2、单元测试 VS 集成测试

系统上线前都会做回归测试和集成测试,但为什么还要加单元测试呢?

指标对象单元测试集成测试
测试对象程序单元模块组合
测试方法白盒测试黑盒测试
测试时间开发阶段集成阶段
测试内容代码逻辑接口功能
测试粒度较细粒度较粗粒度

2、如何写好单测?

2.1、单测规约

可以参考阿里巴巴 的Java开发规范,以下几点在单测中要特别关注:

  • 【强制】好的单测必须遵守AIR原则。说明:单元测试在线上运行时,像空气一样感觉不到,但在测试的质量保证上,却是非常关键的。好的单元测试宏观上说,具体有自动化(Automatic)、独立性(Idependent)、可重复执行(Repeatable)的特点。
  • 【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用System.Out来进行人肉验证,必须使用Assert来验证
  • 【强制】单元测试是可以重复执行的,不能受到外界环境的影响。
  • 【推荐】编写单元测试代码遵守BCDE原则,以保证被测试模块的交付质量。
    • B: Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
    • C: Correct,正确的输入,并得到预期的结果。
    • D: Design,与设计文档相结合,来编写单元测试。
    • E: Error,强制错误信息输入(如:非法数据、 异常流程、业务允许外等),并得到预期的结果

2.2、一把好工具

写单侧首先要有好的单测工具,常用工具: Mockito、PowerMock、 EasyMock、JMockito等,Mock可以解决:

  • 解除对外部服务依赖
  • 减少全链路测试的数据准备
  • 模拟一些非正常的流程
  • 不用加载项目环境配置
  • 实现模块之间的并行开发

2.3、编写单元测试

在这里插入图片描述
可以把单元测试编写流程分为四大步骤,八大操作。

定义对象阶段

定义测试对象

在编写单元测试时,首先需要定义被测对象,或直接初始化、或通过Spy包装…实例化。

  • 直接构建对象
    UserService userService = new UserService();
  • 利用Mockito.spy方法
    UserService userService = Mockito.spy(new UserService());
    UserService userService = Mockito.spy(UserService.class);
  • 利用@Spy注解
@RunWith(PowerMockRunner.class) 
public class CompanyServiceTest {@Spyprivate UserService userService = new UserService();
}
  • 利用@InjectMocks注解
@RunWith(PowerMockRunner.class)
public class UserServiceTest {@InjectMocksprivate UserService userService;
}

模拟依赖对象

在编写单元测试用例时,需要模拟各种依赖对象——类成员、方法参数和方法返回值。

  • 直接构建对象
UserDO user = new User(1L, “test”);
List<Long> userIdList = Arrays.asList(1L, 2L, 3L);
  • 反序列化对象
UserDO user = JSON.parseObject(text, UserDO.class);
List<UserDO> userList = JSON.parseArray(text, UserDO.class);
Map<Long, UserDO> userMap = JSON.parseObject(text, new TypeReference<Map<Long, UserDO>>() {});
  • 利用Mockito.mock方法
MockClass mockClass = Mockito.mock(MockClass.class);
List<Long> userIdList = (List<Long>)Mockito.mock(List.class);
  • 利用@Mock注解 @Mock
    private UserDAO userDAO;
  • 利用Mockito.spy方法
    UserService userService = Mockito.spy(new UserService());
  • 利用@Spy注解
    @Spy
    private UserService userService = new UserService(); // 必须初始化

注入依赖对象

在编写单元测试用例时,需要模拟各种依赖对象——类成员、方法参数和方法返回值。

  • 利用Setter方法注入
    userService.setMaxCount(100);
    userService.setUserDAO(userDAO);
  • 利用ReflectionTestUtils.setField方法注入
    ReflectionTestUtils.setField(userService, “maxCount”, 100);
    ReflectionTestUtils.setField(userService, “userDAO”, userDAO);
  • 利用Whitebox.setInternalState方法注入
    Whitebox.setInternalState(userService, “maxCount”, 100);
    Whitebox.setInternalState(userService, “userDAO”, userDAO);
  • 利用@InjectMocks注解注入
    @Mock
    private UserDAO userDAO; @InjectMocks
    private UserService userService;
  • 设置静态常量字段值
    FieldHelper.setStaticFinalField(UserService.class, “log”, log);

举个例子

@RunWith(PowerMockRunner.class)
public class UserSericeTest {// 模拟依赖对象(类成员)@Mockprivate UserDAO userDAO;// 定义测试对象@InjectMocksprivate UserService userService;@Beforepublic void before() {// 输入依赖对象(类成员)Whitebox.setInternalState(userService, "canModify", true);}
}

模拟方法阶段

在编写单元测试用例时,需要模拟方法指定参数并返回指定值。
在这里插入图片描述

举个例子

模拟依赖对象的数据可以自己构建、Mock或者可以从资源文件里读取。

@Test
public void testCreateUserWithCreate {// 模拟依赖对象方法:getIdByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());Long mockUserId = 2L;// 从资源文件加载String jsonData = ResourceHelper.getResouceAsString(getClass(), path + "/data.json")UserDO userDO = JSON.parseObject(jsonData, UserDO.class);// Long userId = userService.createUser(userDO);Assert.assertEquals("用户标识不一致", mockUserId, userId);// 验证依赖方法Mockito.verify(userDAO).getIdByName(userDO.getUserName());
}

调用方法阶段

在这里插入图片描述

验证方法阶段

  • 验证依赖方法
    在这里插入图片描述
  • 验证数据对象
    在这里插入图片描述
  • 验证依赖对象
    在这里插入图片描述

3、 如何做的更好?

写代码不只是乱写一通,覆盖率上去了就可以了,它本质也是代码,也要符合代码规约。一个好的单测命名可以帮助理清单测Case 也可以便于他人Review。

3.1、规范命名

  • 测试类命名
    按照行业惯例,测试类的命名应以被测试类名开头并以Test结尾。 比如:UserServiceTest(用户服务测试类)
  • 测试方法命名
    按照行业规范,测试方法命名应以test开头并以被测试方法结尾。 a) 按照结果命名
    • testBatchCreateWithSuccess(测试:批量创建-成功)
    • testBatchCreateWithFailure(测试:批量创建-失败)
    • testBatchCreateWithException(测试:批量创建-异常)
    b) 按照参数命名
    • testBatchCreateWithListNull(测试:批量创建-列表为NULL)
    • testBatchCreateWithListEmpty(测试:批量创建-列表为空)
    • testBatchCreateWithListNotEmpty(测试:批量创建-列表不为空)
    c) 按照意图命名
    • testBatchCreateWithNormal(测试:批量创建-正常)
    • testBatchCreateWithGray(测试:批量创建-灰度)
    • testBatchCreateWithException(测试:批量创建-异常)
  • 测试资源命名-语义化 建议优先使用这些参数和变量的名称,并加后缀“.json”标识文件格式。 比如:userCreateList.json

3.2、各环节做好验证

  • 不验证返回值 不验证返回值,怎么能保证方法返回了正确值?
  • 不验证方法调用 不验证方法调用,怎么能保方法被正确的调用?
    Ø 不验证方法参数 不验证方法参数,怎么能保证传递数据的正确性?
    Ø 不验证异常信息 不验证异常信息,怎么能保证抛出异常的正确性?

4、常见单测问题

在编写单元测试用例时,或多或少会遇到一些问题,大多数是由于对测试框架特性不熟悉导致,比如:

  • Mockito不支持对静态方法、构造方法、final方法、私有方法的模拟,应该使用PowerMock功能;
  • Mockito的any相关的参数匹配方法并不支持可空参数和空参数,应该使用nullable方法;
  • 未Mock方法或Mock方法参数不匹配时,会返回默认值(基础类型为0,对象类型为null);
  • 采用Mockito的参数匹配方法时,其它参数不能直接用常量或变量,应该使用Mockito的eq方法;
  • 采用Argument的captor方法时,其它参数不能直接用常量或变量,应该使用Mockito的eq方法;
  • 使用when-then语句模拟Spy对象方法会先执行真实方法,应该使用do-when语句;
  • PowerMock对静态方法、构造方法、final方法、私有方法的模拟需要把对应的类添加到
    @PrepareForTest注解中;
  • PowerMock模拟JDK的静态方法、构造方法、final方法、私有方法时,需要把使用这些方法的类
    加入到@PrepareForTest注解中,但会导致单元测试覆盖率不被统计;
  • PowerMock使用自定义的类加载器来加载类,可能导致系统类加载器认为有类型转化问题;需要加上@PowerMockIgnore({“javax.crypto.*”})注解。

相关文章:

如何写好单测

1、为什么要写单测&#xff1f; 单测即单元测试&#xff08;Unit Test&#xff09;&#xff0c;是对软件的基本组成单元进行的测试&#xff0c;比如函数、过程或者类的方法。其意义是&#xff1a; 功能自测&#xff0c;发现功能缺陷自我Code Review测试驱动开发促进代码重构并…...

CDH-6.3.2内置spark-2.4.0的BUG

1. 背景 公司最近在新建集群&#xff0c;全部采用开源的大数据框架&#xff0c;并且将之前使用的阿里云的所有服务进行下线&#xff0c;其中就涉及到了旧任务的迁移。 2. 任务 2.1. 简述 我接手到一个之前的 spark 任务&#xff0c;是读取阿里 LogStore 数据&#xff0c;然…...

SpringCloud之ElasticSearch笔记

ElasticSearch 初识ElasticSearch ElasticSearch是什么 ElasticSearch一个基于Lucene的底层的开源的分布式搜索引擎&#xff0c;可用来实现搜索&#xff0c;日志统计&#xff0c;分析&#xff0c;系统监控 正向索引和倒排索引 正向索引&#xff1a;逐条扫描&#xff08;my…...

数字图像学笔记 —— 17. 图像退化与复原(自适应滤波之「最小二乘方滤波」)

文章目录维纳滤波的缺点约束最小二乘方滤波给一个实际例子吧维纳滤波的缺点 维纳滤波&#xff08;Wiener Filter&#xff09;&#xff0c;虽然是一种非常强大的退化图像还原算法&#xff0c;但是从实验过程我们也发现它存在着致命的缺陷&#xff0c;那就是要求输入退化系统的 …...

2023-03-05:ffmpeg推送本地视频至lal流媒体服务器(以RTMP为例),请用go语言编写。

2023-03-05&#xff1a;ffmpeg推送本地视频至lal流媒体服务器&#xff08;以RTMP为例&#xff09;&#xff0c;请用go语言编写。 答案2023-03-05&#xff1a; 使用 github.com/moonfdd/ffmpeg-go 库。 先启动lal流媒体服务器软件&#xff0c;然后再执行命令&#xff1a; go…...

MathType7最新版免费数学公式编辑器

话说我也算是 MathType准资深(DB)用户了,当然自从感觉用DB不好之后,我基本上已经抛弃它了,只是前不久因为个别原因又捡起来用了用,30天试用期间又比较深入的折腾了下,也算是变成半个MathType砖家,coco玛奇朵简单介绍一下这款软件:在很可能看到这儿的你还没有出生的某个年月&…...

一文带你入门angular(中)

一、angular中的dom操作原生和ViewChild两种方式以及css3动画 1.原生操作 import { Component } from angular/core;Component({selector: app-footer,templateUrl: ./footer.component.html,styleUrls: [./footer.component.scss] }) export class FooterComponent {flag: b…...

单例设计模式共享数据问题分析、解决(c++11)设计多线程。

系列文章目录 单例设计模式共享数据问题分析、解决; 文章目录系列文章目录前言一、单例模式1.1 基本概念1.2 单例设计模式共享数据问题分析、解决1.3 std::call_once()介绍二、代码案例1.代码示例总结前言 关键内容&#xff1a;c11、多线程、共享数据、单例类 本章内容参考git…...

Embedding-based Retrieval in Facebook Search

facebook的社交网络检索与传统的搜索检索的差异是&#xff0c;除了考虑文本&#xff0c;还要考虑搜索者的背景。通用搜索主要考虑的是文本匹配&#xff0c;并没有涉及到个性化。像淘宝&#xff0c;youtube这些其实都是涉及到了用户自身行为的&#xff0c;除了搜索还有推荐&…...

xmu 离散数学 卢杨班作业详解【8-12章】

文章目录第八章 树23456810第九章46811第十章24567第十一章14571116第十二章131317第八章 树 2 (2) 设有k片树叶 2∗m2∗43∗3k2*m2*43*3k2∗m2∗43∗3k n23kn23kn23k mn−1mn-1mn−1 联立解得k9 T中有9片树叶 3 有三颗非同构的生成树 4 (1) c --abc e–abed f–dgf…...

Linux入门篇-权限管理

简介 用户管理也是和权限相关的知识点。权限的作用 权限对于普通文件和目录文件作用是不一样的 。[kioskfoundation0 ~]$ ls -l total 264 -rw-rw-r--. 2 kiosk kiosk 31943 May 29 2019 ClassPrep.txt -rw-rw-r--. 2 kiosk kiosk 7605 Jun 14 2019 ClassRHAPrep.txt -rw-rw-r…...

Linux(基于 Centos7) 常用操作

1.Linux 简介Linux 是一种 免费使用、自由传播的类 Unix 操作系统Linux操作系统内核&#xff0c;由林纳斯托瓦兹在1991年10月5日首次发布...Linux 是一套开源操作系统&#xff0c;它有稳定、消耗资源小、安全性高等特点大多数人都是直接使用 Linux 发行版&#xff08;就是将 Li…...

Math类详解与Random类、三种随机数生成方式(java)

文章目录&#x1f4d6;前言&#xff1a;&#x1f380;认识Random类&#x1f380;三种随机数生成方式&#x1f380;Math类的用途&#x1f380;Math类的方法&#x1f4d6;前言&#xff1a; 本篇博客主要以介绍Math类的常用方法及认识Random类&#xff0c;及三种随机数生成方式 …...

Mac编译QT程序出现Undefined symbols for architecture x86_64

在Mac编写日志服务类, Logging_d.h内容如下 #pragma once #include <QLoggingCategory> Q_DECLARE_LOGGING_CATEGORY(hovering) Q_DECLARE_LOGGING_CATEGORY(creation) Q_DECLARE_LOGGING_CATEGORY(mouseevents) Q_DECLARE_LOGGING_CATEGORY(state) Q_DECLARE_LOGGING_C…...

蓝桥杯-李白打酒加强版

蓝桥杯-李白打酒加强版1、问题描述2、解题思路3、代码实现1、问题描述 话说大诗人李白, 一生好饮。幸好他从不开车。 一天, 他提着酒显, 从家里出来, 酒显中有酒 2 斗。他边走边唱: 无事街上走&#xff0c;提显去打酒。 逢店加一倍, 遇花喝一斗。 这一路上, 他一共遇到店 N 次…...

AtCoder Beginner Contest 292 (A - E) 记录第一场ABC

AtCoder Beginner Contest 292 A - E前言Q1 A - CAPS LOCKQ2 Yellow and Red CardQ3 Four VariablesQ4 D - Unicyclic ComponentsQ5 E - Transitivity前言 本来晚上在打Acwing周赛&#xff0c;最后一题Trie想不出来咋写&#xff0c;看群里有人说ABC要开始了&#xff0c;想着没…...

ubuntu安装使用putty

一、安装 安装虚拟机串口 sudo apt-get install putty sudo apt install -y setserial 二、使用 虚拟机连接串口 sudo setserial -g /dev/ttyS* 查看硬件对应串口 找到不是unknown的串口 sudo putty...

【CS144】Lab5与Lab6总结

Lab5与Lab6Lab汇总Lab5概述Lab6概述由于Lab5和Lab6相对比较简单&#xff08;跟着文档一步一步写就行&#xff09;&#xff0c;于是放在一起做一个简单概述&#xff08;主要是懒得写了…&#xff09; Lab汇总 Lab5概述 lab5要求实现一个IP与Ethernet&#xff08;以太网&#x…...

GDScript 导出变量 (Godot4.0)

概述 导出变量的功能在3.x版本中也是有的&#xff0c;但是4.0版本对其进行了语法上的改进。 导出变量在日常的游戏制作中提供节点的自定义参数化调节功能时非常有用&#xff0c;除此之外还用于自定义资源。 本文是&#xff08;Bilibili巽星石&#xff09;在4.0官方文档《GDScr…...

shell:#!/usr/bin/env python作用是什么

我们经常会在别人的脚本文件里看到第一行是下面这样 #!/usr/bin/python或者 #!/usr/bin/env python 那么他们有什么用呢&#xff1f; 要理解它&#xff0c;得把这一行语句拆成两部分。 第一部分是 #! 第二部分是 /usr/bin/python 或者 /usr/bin/env python 关于 #! 这个…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

DAY 26 函数专题1

函数定义与参数知识点回顾&#xff1a;1. 函数的定义2. 变量作用域&#xff1a;局部变量和全局变量3. 函数的参数类型&#xff1a;位置参数、默认参数、不定参数4. 传递参数的手段&#xff1a;关键词参数5 题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一…...

数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 原创笔记&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 上一篇&#xff1a;《数据结构第4章 数组和广义表》…...

Python 高级应用10:在python 大型项目中 FastAPI 和 Django 的相互配合

无论是python&#xff0c;或者java 的大型项目中&#xff0c;都会涉及到 自身平台微服务之间的相互调用&#xff0c;以及和第三发平台的 接口对接&#xff0c;那在python 中是怎么实现的呢&#xff1f; 在 Python Web 开发中&#xff0c;FastAPI 和 Django 是两个重要但定位不…...