嵩山少林之单元测试篇
前言
当你看到这份文档时,你会是什么样的心情呢?
想问一下正在食用这份文档的大佬在平时工作中是否遇见过、听到过:
1、我是个很棒的程序员, 我是不是可以不进行单元测试?
2、在每个开发组织中都至少有一个这样的开发人员,他非常擅长于编程,他们开发的软件总是在第一时间就可以正常运行,为什么要写单元测试不是在浪费时间吗?
收起你的小心思和借口,在真实世界里,每个人都会犯错误,总觉得哪里会突然冒出莫名其妙的bug,也怕别人不小心改了自己的代码(被害妄想症),新版本上线提心吊胆......花点时间写单元测试,有事没事跑一下测试,确保原逻辑没问题。
玩笑过后。。。。
那么接下来为了我们每天不至于那么提心吊胆,至少能睡安稳一点,一起来了解下单元测试对我们开发人员的重要性:
什么是单元测试
是针对 程序的最小单元 来进行正确性检验的测试工作。
为什么我们需要写单元测试
我们当然会在开发的时候进行项目功能的测试,常用手段诸如用main对指定的代码块验证,或者使用postman对我们设计的接口进行测试验证,但是这些方法是一定程度上可以完成当期内的功能需求的,
否则也不会有那么多的“单元测试真的有用吗”的这种声音。
那么中间的问题是什么呢?
答案是你无法永远保证“当期的业务测试”就是能覆盖你本期提供的功能点,以及即便是测试同学保存
有以往所有测试用例的自动化测试内容,也无法真正的保证你的系统是完好的,因为业务功能和软件
功能中间是有隔阂的。针对用例设计的功能测试,无法保证你的“系统”正常!!
所以这就是为什么我们需要写单元测试。
单元测试基础知识
什么是断言
在写单元测试之前,我们需要,以及常见用法:
assertEquals
|
判断两个对象或两个原始类型是否相等
|
assertNotEquals
|
判断两个对象或两个原始类型是否相等
|
assertSame
|
判断两个对象是否指向同一个对象
|
assertNotSame
|
判断两个对象引用是否指向不同的对象
|
assertTrue
|
判断给定的布尔值是否为true
|
assertFalse
|
判断给定的布尔值是否为false
|
assertNull
|
判断给定的对象引用是否为null
|
assertNotNull
|
判断给定的对象引用是否不为null
|
assertArrayEquals
|
判断两个对象或者原始类型的数组是否相等
|
assertThrows
|
判断异常 配合函数式编程就可以进行使用
|
assertTimeout
|
测试方法设置了超时时间
|
fail
|
直接使得测试失败
|
assertAll
|
接受多个org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过lambda表达式很容易的提供这些断言。
|
单元测试用例相关概念
正面测试(Positive Testing)
测试被测对象的正确功能实现无误,即正常流程功能。往往需要根据设计说明进行用例导出,严格按照设计说明编写即可,用例划分注意等价类区分等方法。
负面测试(Negative Testing)
测试被测对象的异常功能实现无误,多在异常流程,异常数据中体现。该部分测试需要对被测对象进行错误发散,常依赖于边界值区分等方法。
分支测试
使用流程图,明确可能出现的每条分支,制造响应的数据进行覆盖,实现对被测对象的测试。这个过程对于分支可以进行响应的简化,可以穿插等价类等方法去除同类分支。
边界值分析法
这种方法更偏向于黑盒测试用例设计中使用,对被测输入进行边界分析,从各个角度都会有边界值,例如程序内部依赖之间,已经有一些边界存在,在程序集成展示后,也会有新的边界出现,在设计的时候,需要注意这些细节。例如我们可输入范围是3-6,和输入类型为浮点数。那么边界值为7-8之间
编写单元测试
单元测试基础环境
准备 : JDK -1.8 SpringBoot -2.6.6 H2 -1.4.195
搭建项目
一般项目Demo已经提供 可以忽略,如果想自己进行单元测试,可以按照以下步骤:
pom.xml
我们在搭建完项目之后;因为使用H2数据库来做单元测试数据库所以需要添加H2依赖:
<!--引入h2数据库的依赖 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.195</version> <scope>test</scope> </dependency>
目录结构
当你看到目录结构图时:可能会有疑问?test 中的包也需要自己去创建吗?
答案是:不需要!!!idea 可以替我们去做这些事!!
到此创建基础单元测试模块都已经完成了,是一个什么样子呢?我们一起来看一下:
主要我们看一下 :application.yml
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:test
sql:
init:
schema-locations: classpath:sql/schema.sql
data-locations: classpath:sql/init.sql
单元测试基础示例
到这里相信大家对断言相关知识有了一定的了解。
废话不多说直接上代码,我们一起看一下单元测试如何编写:
示例:
需求:获取单个用户信息。
/** * 异常枚举类 * * @author ykluoh * @since 1.0.0 */ @Getter public enum LykExceptionEnum { /** * 判断对象中属性值是否全为空 */ CHECK_OBJ_ALL_FIELDS_IS_NULL("110001", "根据ID查询 未查到该用户"), private final String code; private final String msg; LykExceptionEnum(String code, String msg) { this.code = code; this.msg = msg; } public static LykExceptionEnum getEnum(String value) { for (LykExceptionEnum e : LykExceptionEnum.values()) { if (e.code.equals(value)) { return e; } } return null; } }
/** * JunitUser接口 * * @author ykluoh * @since 1.0.0 */ public interface JunitUserService { /** * 该方法根据ID来查询单个用户 * * @param id 用户id * @return 返回的是:根据id查询之后的用户表的实体 * @see LykExceptionEnum 该实现类可能会出现 查询为空的异常:CHECK_OBJ_ALL_FIELDS_IS_NULL **/ JunitUser selectById(Long id); }
/** * JunitUserService 接口的实现类 * * @author ykluoh * @since 1.0.0 */ @Slf4j @Service public class JunitUserServiceImpl implements JunitUserService { @Resource private JunitUserDao junitUserDao; @Override public JunitUser selectById(Long id) { JunitUser junitUser = junitUserDao.selectById(id); if (Objects.isNull(junitUser)) { throw new CommonException(LykExceptionEnum.CHECK_OBJ_ALL_FIELDS_IS_NULL.getCode(), LykExceptionEnum.CHECK_OBJ_ALL_FIELDS_IS_NULL.getMsg()); } return junitUser; } }
@Slf4j class JunitUserServiceImplTest extends LykProjectApplicationTests { @Resource private JunitUserService junitUserService; @Test void selectById() { CommonException commonException; Long selectById = 1L; JunitUser junitUser = junitUserService.selectById(selectById); Assertions.assertNotNull(junitUser); Long selectByIdNull = 2L; commonException = Assertions.assertThrows(CommonException.class, () -> { junitUserService.selectById(selectByIdNull); }); Assertions.assertEquals(LykExceptionEnum.CHECK_OBJ_ALL_FIELDS_IS_NULL, LykExceptionEnum.getEnum(commonException.getCode())); } }
单元测试规约
规约第8条提到了:语句覆盖率达到70%;核心模块的语句覆盖率和分支覆盖率都要达到 100% .
那么我们写完单元测试之后如何查看覆盖率呢?
单元测试特殊情况处理
异常处理
1、Assertions.assertThrows() :已经在示例中演示了。