当前位置: 首页 > 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

相关文章:

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

背景 单元测试是保证我们写的代码是我们想要的结果的最有效的办法。根据下面的数据图统计&#xff0c;单元测试从长期来看也有很大的收益。 单元测试收益: 它是最容易保证代码覆盖率达到100%的测试。可以⼤幅降低上线时的紧张指数。单元测试能更快地发现问题。单元测试的性…...

web安全之跨站脚本攻击xss

定义: 后果 比如黑客可以通过恶意代码,拿到用户的cookie就可以去登陆了 分类 存储型 攻击者把恶意脚本存储在目标网站的数据库中(没有过滤直接保存)&#xff0c;当用户访问这个页面时&#xff0c;恶意脚本会从数据库中被读取并在用户浏览器中执行。比如在那些允许用户评论的…...

TCP与UDP的理解

文章目录 UDP协议UDP协议的特点UDP的应用以及杂项 TCP协议TCP协议段格式解释和TCP过程详解确认应答机制 -- 序号和确认序号以及6位标志位中的ACK超时重传机制连接管理机制 与标志位SYN,FIN,ACK滑动窗口与16位窗口大小流量控制拥塞控制延迟应答捎带应答和面向字节流粘包问题TCP异…...

有效应对服务器遭受CC攻击的策略与实践

分布式拒绝服务&#xff08;DDoS&#xff09;攻击&#xff0c;尤其是其中的HTTP洪水攻击或称为CC攻击&#xff08;Challenge Collapsar&#xff09;&#xff0c;是当今互联网安全领域的一大挑战。这种攻击通过大量合法的请求占用大量网络资源&#xff0c;导致服务器无法正常响应…...

STM32判断休眠

STM32是否进入休眠模式(或称为睡眠模式)的判断主要基于其功耗状态、内部时钟的关闭情况以及唤醒后的行为。以下是根据参考文章提供的信息,判断STM32是否进入休眠模式的方法: 功耗状态: STM32在休眠模式下,功耗会显著降低。这是因为休眠模式仅关闭了内核时钟,但外设仍然保…...

TikTok内嵌跨境商城全开源_搭建教程/前端uniapp+后端源码

多语言跨境电商外贸商城 TikTok内嵌商城&#xff0c;商家入驻一键铺货一键提货 全开源完美运营&#xff0c;接在tiktok里面的商城内嵌&#xff0c;也可单独分开出来当独立站运营 二十一种语言&#xff0c;可以做很多国家的市场&#xff0c;支持商家入驻&#xff0c;多店铺等等…...

前端学习(二)

这篇文章是紧接着前一篇前端学习写的&#xff0c;主要要写的是js剩下的基础知识 事件的绑定 什么是事件&#xff1f; HTML 事件可以是浏览器行为&#xff0c;也可以是用户行为。 当这些一些行为发生时,可以自动触发对应的JS函数的运行,我们称之为事件发生.JS的事件驱动指的就是…...

链接追踪系列-10.mall-swarm微服务运行并整合elk-上一篇的番外

因为上一篇没对微服务代码很详细地说明&#xff0c;所以在此借花献佛&#xff0c;使用开源的微服务代码去说明如何去做链路追踪。 项目是开源项目&#xff0c;fork到github以及gitee中&#xff0c;然后拉取到本地 后端代码&#xff1a; https://gitee.com/jelex/mall-swarm.gi…...

用Agent大模型,我发现了Prompt工程师的10大必备技能

随着 AI 如此快速的发展&#xff0c;目前求职市场上已经出现了 AI提示词 岗位。 大家应该跟我一样&#xff0c;对这种新兴岗位充满好奇心&#xff0c;比如&#xff1a;想知道这类岗位目前的需求量、技能要求、薪资情况等等。 这两天我用 Agent 大模型&#xff0c;对AI提示词岗…...

【GraphRAG】微软 graphrag 效果实测

GraphRAG 本文将基于以下来源&#xff0c;对Microsoft GraphRAG分析优缺点、以及示例实测分析。 1. Source 代码仓库&#xff1a; Welcome to GraphRAGhttps://microsoft.github.io/graphrag/ 微软文章1&#xff08;2024.2.13&#xff09;&#xff1a;GraphRAG: Unlocking…...

十大常用加密软件排行榜|2024企业常用加密软件推荐

在2024年的市场环境中&#xff0c;随着数字化转型的深入和网络威胁的日益复杂&#xff0c;企业对数据安全的重视达到了新高度。加密软件作为保护信息免遭未授权访问和恶意攻击的关键工具&#xff0c;其重要性日益凸显。以下是根据市场反馈和专业评测整理的2024年度十大常用加密…...

lua 游戏架构 之 资源加载 LoaderManager (一)

定义一个 LoaderManager class&#xff0c;用于管理各种资源加载器。它使用了对象池&#xff08;Object Pool&#xff09;来优化资源加载器的创建和销毁&#xff0c;从而提高性能 举例定义一个 PrefabLoader --[[Desc: 封装AAS的接口&#xff0c;加载Prefab --]]---alias Pre…...

【人工智能】-- 迁移学习

个人主页&#xff1a;欢迎来到 Papicatch的博客 课设专栏 &#xff1a;学生成绩管理系统 专业知识专栏&#xff1a; 专业知识 文章目录 &#x1f349;引言 &#x1f349;迁移学习 &#x1f348;基本概念 &#x1f34d;定义 &#x1f34c;归纳迁移学习&#xff08;Induct…...

Flink源码学习资料

Flink系列文档脑图 由于源码分析系列文档较多&#xff0c;本人绘制了Flink文档脑图。和下面的文档目录对应。各位读者可以选择自己感兴趣的模块阅读并参与讨论。 此脑图不定期更新中…… 文章目录 以下是本人Flink 源码分析系列文档目录&#xff0c;欢迎大家查阅和参与讨论。…...

HarmonyOS4.0开发-环境配置

鸿蒙应⽤开发快速体验 1.1. 准备开发环境 1.1.1 安装IDE 鸿蒙应⽤开发需要使⽤配套的IDE——HUAWEI DevEco Studio。 DevEco Studio基于IntelliJIDEA Community&#xff08;IDEA社区版&#xff09;构建&#xff0c;为鸿蒙应⽤提供了⼀站式开发环境&#xff0c;集成了开发、运⾏…...

GESP CCF C++ 三级认证真题 2024年6月

第 1 题 小杨父母带他到某培训机构给他报名参加CCF组织的GESP认证考试的第1级&#xff0c;那他可以选择的认证语言有&#xff08;&#xff09;种。 A. 1 B. 2 C. 3 D. 4 第 2 题 下面流程图在yr输入2024时&#xff0c;可以判定yr代表闰年&#xff0c;并输出 2月是29天 &#x…...

华为的热机备份和流量限制

要求&#xff1a; 12&#xff0c;对现有网络进行改造升级&#xff0c;将当个防火墙组网改成双机热备的组网形式&#xff0c;做负载分担模式&#xff0c;游客区和DMZ区走FW4&#xff0c;生产区和办公区的流量走FW5 13&#xff0c;办公区上网用户限制流量不超过100M&#xff0c;…...

Vite的WebSocket

Vite的webSocket SocketJava Socket概述工作原理优势劣势 Java WebSocket概述工作原理代码示例nodeJS WebSocket优势劣势 vite中的WebSocket&#x1f4ab;代码示例使用vite进行创建服务器并对Vue实行HMR 总结 Socket Java 中的 Socket 与 WebSocket 都用于网络通信&#xff0c…...

Vue3项目基于Axios封装request请求

在 Vue 3 的项目开发中&#xff0c;使用 Axios 进行 HTTP 请求是非常常见的作法&#xff0c;为了更方便开发者更高效的进行代码编写和项目的维护&#xff0c;可以通过再次封装 Axios 来实现。 在本文中&#xff0c;博主将详细指导你如何在自己的 Vue 3 项目中使用 Axios 二次封…...

html(抽奖设计)

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>抽奖</title><style type"text/css">* {margin: 0;padding: 0;}.container {width: 800px;height: 800px;border: 1px dashed red;position: absolut…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...