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

Spock单元测试框架使用介绍和实践

背景

单元测试是保证我们写的代码是我们想要的结果的最有效的办法。根据下面的数据图统计,单元测试从长期来看也有很大的收益。
bug在单元测试阶段发小修复耗时最少
85%的缺陷都在代码设计阶段产生,而发现bug的阶段越靠后,耗费成本就越高,指数级别的增高

单元测试收益:

  • 它是最容易保证代码覆盖率达到100%的测试。
  • 可以⼤幅降低上线时的紧张指数。
  • 单元测试能更快地发现问题。
  • 单元测试的性价比最高,因为错误发现的越晚,修复它的成本就越高,而且难度呈指数式增长,所以我们要尽早地进行测试。
  • 编码人员,一般也是单元测试的主要执行者,是唯一能够做到生产出无缺陷程序的人,其他任何人都无法做到这一点。
  • 有助于源码的优化,使之更加规范,快速反馈,可以放心进行重构。
    我们都知道单元测试的好处,但是我们遇到的项目还是很多都缺乏单测,一方面是因为单测耗时,一方面也是因为业务的复杂性导致单测难以进行,最长见还是任务重、工期紧,或者干脆就不写了。

为什么要用Spock,和JUnit有什么区别

Spock是一款国外优秀的测试框架,基于BDD(行为驱动开发)思想实现,功能非常强大。Spock结合Groovy动态语言的特点,提供了各种标签,并采用简单、通用、结构化的描述语言,让编写测试代码更加简洁、高效。官方介绍:https://spockframework.org/
在这里插入图片描述

Spock的主要特点

  • 让测试代码更规范,内置多种标签来规范单元测试代码的语义,测试代码结构清晰,更具可读性,降低后期维护难度。
  • 提供多种标签,比如:given、when、then、expect、where、with、thrown……帮助我们应对复杂的测试场景。
  • 使用Groovy这种动态语言来编写测试代码,可以让我们编写的测试代码更简洁,适合敏捷开发,提高编写单元测试代码的效率。
  • 遵从BDD(行为驱动开发)模式,有助于提升代码的质量。
  • IDE兼容性好,自带Mock功能。
    现有的单测框架比如junit、jmock、mockito都是相对独立的工具,针对不同的业务场景提供特定的解决方案。在业务场景中有很多第三方服务的依赖,比如微服务的调用、数据库、redis等的存储,我们需要将这些依赖mock掉去验证我们代码的逻辑是否正确。JMock或Mockito虽然提供了mock功能,可以把接口等依赖屏蔽掉,但不提供对静态类静态方法的mock,PowerMock或Jmockit虽然提供静态类和方法的mock,但它们之间需要整合(junit+mockito+powermock),语法繁琐,而且这些工具并没有告诉你**“单元测试代码到底应该怎么写?”**

Spock通过提供规范描述,定义多种标签(given、when、then、where等)去描述代码“应该做什么”,输入条件是什么,输出是否符合预期,从语义层面规范代码的编写。

Spock使用简单介绍

基本概念

Spock定义了几个构造块,一段单测也是由这几个构造块组合完成的。
Spock主要提供了如下基本构造块:

  • given: mock单测中指定mock数据
  • when: 触发行为,比如调用指定方法或函数
  • then: 做出断言表达式
  • expect: 期望的行为,when-then的精简版
  • where: 以表格的形式提供测试数据集合
  • thrown: 如果在when方法中抛出了异常,则在这个子句中会捕获到异常并返回
  • def setup() {} :每个测试运行前的启动方法
  • def cleanup() {} : 每个测试运行后的清理方法
  • def setupSpec() {} : 第一个测试运行前的启动方法
  • def cleanupSpec() {} : 最后一个测试运行后的清理方法

通过这些标签从行为上规范单测代码,每一种标签对应一种语义,让我们的单测代码结构具有层次感,功能模块划分清晰,便于后期维护
IntelliJ IDEA支持format(opt+cmd+L)格式化快捷键,因为表格列的长度不一样,手动对齐比较麻烦。

实际案例

/*** 身份证号码工具类<p>* 15位:6位地址码+6位出生年月日(900101代表1990年1月1日出生)+3位顺序码* 18位:6位地址码+8位出生年月日(19900101代表1990年1月1日出生)+3位顺序码+1位校验码* 顺序码奇数分给男性,偶数分给女性。* @author 公众号:Java老K* 个人博客:www.javakk.com*/
public class IDNumberUtils {/*** 通过身份证号码获取出生日期、性别、年龄* @param certificateNo* @return 返回的出生日期格式:1990-01-01   性别格式:F-女,M-男*/public static Map<String, String> getBirAgeSex(String certificateNo) {String birthday = "";String age = "";String sex = "";int year = Calendar.getInstance().get(Calendar.YEAR);char[] number = certificateNo.toCharArray();boolean flag = true;if (number.length == 15) {for (int x = 0; x < number.length; x++) {if (!flag) return new HashMap<>();flag = Character.isDigit(number[x]);}} else if (number.length == 18) {for (int x = 0; x < number.length - 1; x++) {if (!flag) return new HashMap<>();flag = Character.isDigit(number[x]);}}if (flag && certificateNo.length() == 15) {birthday = "19" + certificateNo.substring(6, 8) + "-"+ certificateNo.substring(8, 10) + "-"+ certificateNo.substring(10, 12);sex = Integer.parseInt(certificateNo.substring(certificateNo.length() - 3,certificateNo.length())) % 2 == 0 ? "女" : "男";age = (year - Integer.parseInt("19" + certificateNo.substring(6, 8))) + "";} else if (flag && certificateNo.length() == 18) {birthday = certificateNo.substring(6, 10) + "-"+ certificateNo.substring(10, 12) + "-"+ certificateNo.substring(12, 14);sex = Integer.parseInt(certificateNo.substring(certificateNo.length() - 4,certificateNo.length() - 1)) % 2 == 0 ? "女" : "男";age = (year - Integer.parseInt(certificateNo.substring(6, 10))) + "";}Map<String, String> map = new HashMap<>();map.put("birthday", birthday);map.put("age", age);map.put("sex", sex);return map;}
}

在这里插入图片描述

下面案例是测试断言的比较器的单测
在这里插入图片描述

运行结果:
在这里插入图片描述

代码简洁清晰
在这里插入图片描述

在这里插入图片描述

安装环境配置&第三方依赖

添加依赖

<!-- spock -->
<dependency><groupId>org.spockframework</groupId><artifactId>spock-core</artifactId><version>2.4-M1-groovy-4.0</version><scope>test</scope>
</dependency>
<dependency><groupId>org.spockframework</groupId><artifactId>spock-spring</artifactId><version>2.4-M1-groovy-4.0</version><scope>test</scope>
</dependency>

If Esle 多分支场景测试

使用expect+where
expect相当于when+then的精简版
@Unroll注解表示展开where标签下面的每一行测试,作为单独的case跑
[图片]

异常测试

使用thrown
有些方法会根据不同的场景抛出不同的异常,Spock内置thrown()方法,可以捕获调用业务代码抛出的预期异常并验证,再结合where表格的功能,可以很方便的覆盖多种自定义业务异常,代码如下:
在这里插入图片描述

Void方法测试

没有返回值的方法该怎么测试呢,一种有效的测试方式,就是验证方法内部逻辑和流程是否符合预期,比如:

  • 应该走到哪个分支逻辑?
  • 是否执行了这一行代码?
  • for循环中的代码执行了几次?
  • 变量在方法内部的变化情况?
    在这里插入图片描述
    在这里插入图片描述

静态方法测试

https://javakk.com/302.html

动态Mock

具体场景介绍

使用Spock简化测试代码

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

基本构造块####

Spock主要提供了如下基本构造块:

  • where: 以表格的形式提供测试数据集合
  • when: 触发行为,比如调用指定方法或函数
  • then: 做出断言表达式
  • expect: 期望的行为,when-then的精简版
  • given: mock单测中指定mock数据
  • thrown: 如果在when方法中抛出了异常,则在这个子句中会捕获到异常并返回
  • def setup() {} :每个测试运行前的启动方法
  • def cleanup() {} : 每个测试运行后的清理方法
  • def setupSpec() {} : 第一个测试运行前的启动方法
  • def cleanupSpec() {} : 最后一个测试运行后的清理方法
    了解基本构造块的用途后,可以组合它们来编写单测。

参考资料

美团 https://tech.meituan.com/2021/08/06/spock-practice-in-meituan.html
老K的Java博客:https://javakk.com/category/spock
Spring mock: https://spockframework.org/spock/docs/2.2-SNAPSHOT/module_spring.html

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • linux中当前目录、上级目录、上上级目录表示方法
  • python—爬虫爬取电影页面实例
  • AI 绘画|Midjourney设计Logo提示词
  • Unity | AssetBundle
  • 【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 卢小姐的生日礼物(200分) - 三语言AC题解(Python/Java/Cpp)
  • 哪些企业适合做ISO27001信息安全管理体系?
  • 定制QCustomPlot 带有ListView的QCustomPlot 全网唯一份
  • SpringAI简单使用(本地模型+自定义知识库)
  • Linux处理文件sed
  • Java 新手学习线路,Java 学习路线是怎样的?
  • uniapp自定义tabBar
  • unity2022 il2cpp 源码编译
  • 信息检索(39):Condenser: a Pre-training Architecture for Dense Retrieval
  • SpringBoot源码深度解析
  • # Redis 入门到精通(九)-- 主从复制(1)
  • 【个人向】《HTTP图解》阅后小结
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • github从入门到放弃(1)
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • Java教程_软件开发基础
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • PermissionScope Swift4 兼容问题
  • SOFAMosn配置模型
  • 码农张的Bug人生 - 初来乍到
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 你对linux中grep命令知道多少?
  • ​如何使用QGIS制作三维建筑
  • ###C语言程序设计-----C语言学习(3)#
  • #70结构体案例1(导师,学生,成绩)
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (2024,LoRA,全量微调,低秩,强正则化,缓解遗忘,多样性)LoRA 学习更少,遗忘更少
  • (Demo分享)利用原生JavaScript-随机数-实现做一个烟花案例
  • (javascript)再说document.body.scrollTop的使用问题
  • (附源码)ssm高校志愿者服务系统 毕业设计 011648
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (三)docker:Dockerfile构建容器运行jar包
  • (贪心 + 双指针) LeetCode 455. 分发饼干
  • (转)scrum常见工具列表
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • (转)负载均衡,回话保持,cookie
  • (转)关于pipe()的详细解析
  • .gitignore文件_Git:.gitignore
  • .NET Core 发展历程和版本迭代
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .NET NPOI导出Excel详解
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .Net Web窗口页属性
  • .NET 使用 XPath 来读写 XML 文件
  • .Net(C#)常用转换byte转uint32、byte转float等
  • .w文件怎么转成html文件,使用pandoc进行Word与Markdown文件转化
  • .xml 下拉列表_RecyclerView嵌套recyclerview实现二级下拉列表,包含自定义IOS对话框...
  • @Autowired 与@Resource的区别