简述spock以及使用

news2024/10/5 14:23:26

1. 介绍

1.1 Spock是什么?

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

Spock是一个Java和Groovy`应用的测试和规范框架。之所以能够在众多测试框架中脱颖而出,是因为它优美而富有表现力的规范语言。Spock的灵感来自JUnit、RSpec、jMock、Mockito、Groovy、Scala、Vulcans。

简单来讲,Spock主要特点如下:

  • 让测试代码更规范,内置多种标签来规范单元测试代码的语义,测试代码结构清晰,更具可读性,降低后期维护难度。

  • 提供多种标签,比如:givenwhenthenexpectwherewiththrown……帮助我们应对复杂的测试场景。

  • 使用Groovy这种动态语言来编写测试代码,可以让我们编写的测试代码更简洁,适合敏捷开发,提高编写单元测试代码的效率。

  • 遵从BDD(行为驱动开发)模式,有助于提升代码的质量。

  • IDE兼容性好,自带Mock功能。

Spock和JUnit、jMock、Mockito的区别在哪里?

总的来说,JUnit、jMock、Mockito都是相对独立的工具,只是针对不同的业务场景提供特定的解决方案。其中JUnit单纯用于测试,并不提供Mock功能。

我们的服务大部分是分布式微服务架构。服务与服务之间通常都是通过接口的方式进行交互。即使在同一个服务内也会分为多个模块,业务功能需要依赖下游接口的返回数据,才能继续后面的处理流程。这里的下游不限于接口,还包括中间件数据存储比如Squirrel、DB、MCC配置中心等等,所以如果想要测试自己的代码逻辑,就必须把这些依赖项Mock掉。因为如果下游接口不稳定可能会影响我们代码的测试结果,让下游接口返回指定的结果集(事先准备好的数据),这样才能验证我们的代码是否正确,是否符合逻辑结果的预期。

尽管jMock、Mockito提供了Mock功能,可以把接口等依赖屏蔽掉,但不能对静态方法Mock。虽然PowerMock、jMockit能够提供静态方法的Mock,但它们之间也需要配合(JUnit + Mockito PowerMock)使用,并且语法上比较繁琐。工具多了就会导致不同的人写出的单元测试代码“五花八门”,风格相差较大。

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

Spock自带Mock功能,使用简单方便(也支持扩展其他Mock框架,比如PowerMock),再加上Groovy动态语言的强大语法,能写出简洁高效的测试代码,同时能方便直观地验证业务代码的行为流转,增强工程师对代码执行逻辑的可控性。

1.2 使用Spock解决单元测试开发中的痛点

如果在(if/else)分支很多的复杂场景下,编写单元测试代码的成本会变得非常高,正常的业务代码可能只有几十行,但为了测试这个功能覆盖大部分的分支场景,编写的测试代码可能远不止几十行。

尽管使用JUnit的@Parametered参数化注解或者DataProvider方式可以解决多数据分支问题,但不够直观,而且如果其中某一次分支测试Case出错了,它的报错信息也不够详尽。

这就需要一种编写测试用例高效、可读性强、占用工时少、维护成本低的测试框架。首先不能让业务人员排斥编写单元测试,更不能让工程师觉得写单元测试是在浪费时间。而且使用JUnit做测试工作量不算小。据初步统计,采用JUnit的话,它的测试代码行和业务代码行能到3:1。如果采用Spock作为测试框架的话,它的比例可缩减到1:1,能够大大提高编写测试用例的效率。

举个例子:

public double calc(double income) {
        BigDecimal tax;
        BigDecimal salary = BigDecimal.valueOf(income);
        if (income <= 0) {
            return 0;
        }
        if (income > 0 && income <= 3000) {
            BigDecimal taxLevel = BigDecimal.valueOf(0.03);
            tax = salary.multiply(taxLevel);
        } else if (income > 3000 && income <= 12000) {
            BigDecimal taxLevel = BigDecimal.valueOf(0.1);
            BigDecimal base = BigDecimal.valueOf(210);
            tax = salary.multiply(taxLevel).subtract(base);
        } else if (income > 12000 && income <= 25000) {
            BigDecimal taxLevel = BigDecimal.valueOf(0.2);
            BigDecimal base = BigDecimal.valueOf(1410);
            tax = salary.multiply(taxLevel).subtract(base);
        } else if (income > 25000 && income <= 35000) {
            BigDecimal taxLevel = BigDecimal.valueOf(0.25);
            BigDecimal base = BigDecimal.valueOf(2660);
            tax = salary.multiply(taxLevel).subtract(base);
        } else if (income > 35000 && income <= 55000) {
            BigDecimal taxLevel = BigDecimal.valueOf(0.3);
            BigDecimal base = BigDecimal.valueOf(4410);
            tax = salary.multiply(taxLevel).subtract(base);
        } else if (income > 55000 && income <= 80000) {
            BigDecimal taxLevel = BigDecimal.valueOf(0.35);
            BigDecimal base = BigDecimal.valueOf(7160);
            tax = salary.multiply(taxLevel).subtract(base);
        } else {
            BigDecimal taxLevel = BigDecimal.valueOf(0.45);
            BigDecimal base = BigDecimal.valueOf(15160);
            tax = salary.multiply(taxLevel).subtract(base);
        }
        return tax.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

能够看到上面的代码中有大量的if-else语句,Spock提供了where标签,可以让我们通过表格的方式来测试多种分支。

@Unroll
def "个税计算,收入:#income, 个税:#result"() {
  expect: "when + then 的组合"
  CalculateTaxUtils.calc(income) == result

  where: "表格方式测试不同的分支逻辑"
  income || result
  -1     || 0
  0      || 0
  2999   || 89.97
  3000   || 90.0
  3001   || 90.1
  11999  || 989.9
  12000  || 990.0
  12001  || 990.2
  24999  || 3589.8
  25000  || 3590.0
  25001  || 3590.25
  34999  || 6089.75
  35000  || 6090.0
  35001  || 6090.3
  54999  || 12089.7
  55000  || 12090
  55001  || 12090.35
  79999  || 20839.65
  80000  || 20840.0
  80001  || 20840.45
}

在这里插入图片描述

由此可见, 使用spock框架比junit更能节省代码, 但是spock是使用的是Groovy语法, 所以需要学习一下它的语法, 因为Groovy是基于java虚拟机的语言, 所以也能直接写java代码(不过java语法繁琐, 用java的话和用junit差不多了)

2. 使用篇

2.1 引入pom

				<!--引入 groovy 依赖-->
		<dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.15</version>
            <scope>test</scope>
        </dependency>
				<!--引入spock 与 spring 集成包-->
        <dependency>
            <groupId>org.spockframework</groupId>
            <artifactId>spock-spring</artifactId>
            <version>1.2-groovy-2.4</version>
            <scope>test</scope>
        </dependency>
<!-- Spock自带Mock功能,所以我们可以来Mock非静态方法。但是遇到静态方法时,我们需要导入powermock -->
				<!--powermock -->
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>2.0.0</version>
            <scope>test</scope>
        </dependency>

2.2 使用案例

import org.mockito.InjectMocks
import org.mockito.Matchers
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.slf4j.Logger
import spock.lang.Specification
import spock.lang.Unroll

import java.time.LocalDate
import java.time.LocalDateTime
import java.time.Month
import java.time.temporal.ChronoUnit

import static org.mockito.Mockito.when
class QueryDateTest extends Specification {
    @Unroll("#testName")
    def "测试特定时间tess"() {
        when: "执行以及结果验证"
        def queryDate = new QueryDate()
        queryDate.setSpecialDateType(sepecial)
  // 赋值, << 表示(集合类型)右边赋值给左边   (Groovy语法)     
//	    queryDate.getCustomDateTimes() << [time1, time2]
            
        then:
        def actual = queryDate.getCustomDateTimes()
        def collect = actual.collect { arr -> "[${arr*.toString().join(',')}]" }.join(',')
        rStr == collect

        where: "测试用例覆盖"
        testName                            | sepecial         || rStr
        "1-分钟粒度-近12小时-最后一天"      | Arrays.asList(5) || "{2024-01-19T05:00,2024-01-19T17:00}"
        "2-10分钟粒度-近12小时-中间天"      | Arrays.asList(4) || "{2024-01-18T00:00,2024-01-19T00:00}"
        "3-10分钟粒度-绝对时间-中间天-跨天" | Arrays.asList(1) || "{2024-01-15T00:00,2024-01-16T00:00}"
    }
}

结果如下:

在这里插入图片描述

解释:

  1. @Unroll , 可以让where中的每一个案例当成一个单测, 如此可以在输出结果时,分开查看当前用例执行情况 (如果不加, 则合并一起展示)

  2. #testName 是一个变量, 在where中复制, 表示, 当前用例的名称 (参见输出结果)

  3. 测试特定时间tess 表示这个单测方法的名称 (其实java也允许中文函数名称)

  4. when, then, where 是spock的关键字

    1. when: 表示当如何时, 可以做一些赋值,定义之类的工作
    2. then: 表示然后如何, 可以做业务逻辑的操作
    3. where: 表示条件如何, 这里就是写大量条件的地方

    当然, 你也可以把 whenthen合并, 也可以不要 where, 把输入条件全部写在then中 (这就和juint一样了), 后面再介绍一下还有其他关键字和组合用法

  5. defcollect{} 是 Groovy语法

  6. where 后面是一个类似表格的东西, 里面就是入参, 第一行是定义变量名, 这里的变量名可以再whenthen中使用, 多个列使用|单竖线隔开,||双竖线区分输入和输出变量,即左边是输入值,右边是输出值 (其实全部写成 || 也可以)(此处的用法是, 入参和期望结果都是变量形式)

    格式: 输入参数1 | 输入参数2 || 输出结果1 | 输出结果2

2.3 分块(关键字)

分块替换功能说明
givensetup初始化函数、MOCK非必要
whenexpect执行待测试的函数when 和 then 必须成对出现
thenexpect验证函数结果when 和 then 可以被 expect 替换
where多套测试数据的检测spock的特性功能
and对其余块进行分隔说明非必要

Spock单元测试框架介绍以及在美团优选的实践 - 美团技术团队 (meituan.com)

吃透单元测试:Spock单元测试框架的应用与实践-CSDN博客

Spock-高质量单元测试实操篇 - My_blog_s - 博客园 (cnblogs.com)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【软件测试】软件测试入门

软件测试入门 一、什么是软件测试二、软件测试和软件开发的区别三、软件测试在不同类型公司的定位1. 无组织性2. 专职 OR 兼职3. 项目性VS.职能性4.综合型 四、一个优秀的软件测试人员具备的素质1. 技能相关2. 非技能相关 一、什么是软件测试 最常见的理解是&#xff1a;软件测…

设备保养计划不再是纸上谈兵,智能系统让执行更到位!

在物业管理的日常工作中&#xff0c;我们常常听到“设备保养台账”“设备保养计划”“设备保养记录”等等这些词&#xff0c;但你是否真正了解它们的含义&#xff1f;是否知道一个完善的设备保养计划、记录、台账对于物业运营的重要性&#xff1f;今天&#xff0c;我们就来深入…

AI产品经理,应掌握哪些技术?

美国的麻省理工学院&#xff08;Massachusetts Institute of Technology&#xff09;专门负责科技成果转化商用的部门研究表明&#xff1a; 每一块钱的科研投入&#xff0c;需要100块钱与之配套的投资&#xff08;人、财、物&#xff09;&#xff0c;才能把思想转化为产品&…

《纪元 1800》好玩吗? 苹果电脑能玩《纪元 1800》吗?

《纪元1800》是一款不错的策略游戏&#xff0c;这款游戏因为画面和玩法独特深受玩家们的喜爱。下面我们来看看《纪元 1800》好玩吗&#xff0c;苹果电脑能玩《纪元 1800》吗的相关内容。 一、《纪元1800》好玩吗 《纪元1800》是一款备受瞩目的策略游戏。下面让我们来看看这款…

mysql [Err] 1118 - Row size too large (> 8126).

1.找到my.ini文件 1.1 控制台输入以下指令&#xff0c;打开服务 services.msc1.2 查看mysql服务的属性 2.停止mysql服务&#xff0c;修改my.ini文件并且保存 innodb_strict_mode03.重启mysql服务 4.验证是否关闭成功 show variables like %innodb_strict_mode%; show vari…

SaaS产品运营|一文讲清楚为什么ToB产品更适合采用PLG模式?

在数字化时代&#xff0c;ToB&#xff08;面向企业&#xff09;产品市场的竞争愈发激烈。为了在市场中脱颖而出&#xff0c;许多企业开始转向PLG&#xff08;产品驱动增长&#xff09;模式。这种模式以产品为核心&#xff0c;通过不断优化产品体验来驱动用户增长和业务发展。本…

学本领、争奖金! 由和鲸支持的“数据蜂杯”全国大学生暑期面访调查大赛火热报名中

随着数字时代的到来&#xff0c;社会调查能力、数据分析能力成为当代大学生不可或缺的核心素养。为了进一步提升当代大学生深入田野、以团队的方式采集高质量数据的能力&#xff0c;中国人民大学中国调查与数据中心&#xff08;NSRC&#xff09;举办“数据蜂杯”全国大学生暑期…

头歌资源库(9)丢失的数字

一、 问题描述 二、算法思想 输入n和nums数组。初始化一个大小为n1的数组counts&#xff0c;初始值都为0。遍历nums数组&#xff0c;将counts[nums[i]]的值加1。遍历counts数组&#xff0c;找到第一个值为0的索引&#xff0c;即为没有出现在数组中的那个数。输出结果。 三、…

LabVIEW电表改装与校准仿真系统

LabVIEW开发的电表改装与校准仿真实验平台不仅简化了传统的物理实验流程&#xff0c;而且通过虚拟仿真提高了实验的效率和安全性。该平台通过模拟电表改装与校准的各个步骤&#xff0c;允许学生在没有实际硬件的情况下完成实验&#xff0c;有效地结合了理论学习和实践操作。 项…

51单片机宏定义的例子

代码 demo.c #include "hardware.h"void delay() {volatile unsigned int n;for(n 0; n < 50000; n); }int main(void) {IO_init();while(1){PINSET(LED);delay();PINCLR(LED);delay();}return 0; }cfg.h #ifndef _CFG_H_ #define _CFG_H_// #define F_CPU …

Ubuntu 20.04 LTS WslRegisterDistribution failed with error: 0x800701bc

1.以管理员身份运行powershell&#xff0c;输入&#xff1a;wsl --update&#xff0c; 2.重新打开ubuntu即可。

泛微开发修炼之旅--17基于Ecology短信平台,实现后端自定义二开短信发送方案及代码示例

文章链接&#xff1a;17基于Ecology短信平台&#xff0c;实现后端自定义二开短信发送方案及代码示例

【产品经理】ERP订单处理3-解密促销策略

由于订单到电商ERP系统中&#xff0c;订单金额已经不能支持改动&#xff0c;故电商前台端的优惠券活动、满减活动、支付优惠活动无法使用&#xff0c;故电商ERP只涉及赠品的活动。 一、订单金额阶梯送 顾名思义&#xff1a;订单金额在某个或者某几个金额范围内赠送商品。 字…

十进制、二进制、十六进制之间的相互转换

实验目的 实现int 、float 转换为字符串并显示 实现数字字符以二进制、十进制、十六进制显示 实现十进制、二进制、十六进制之间的相互转换 #include "numconvert.h" #include "ui_numconvert.h"NumConvert::NumConvert(QWidget *parent): QWidget(parent)…

Android平台如何实现多路低延迟RTSP|RTMP播放?

技术背景 实际上&#xff0c;我们在2015年做Android平台RTSP、RTMP播放模块的时候&#xff0c;第一版就支持了多实例播放&#xff0c;因为SDK设计比较灵活&#xff0c;做个简单的player实例封装即可实现多实例播放&#xff08;Android Unity的就有多路demo&#xff09;&#x…

arsetryhtehrwgefwadasdadasd

48b91400000080f7ffff48b8bd427ae5d594bfd6488b0948f7e148b8cdcccccccccccccc48c1ea1748f7e24c8bea49c1ed02 直接在windbg中把执行内存修改为上面这一串字节序列&#xff0c;运行完成后r13中将包含当前时间戳&#xff0c;可使用如下代码转换成人类可阅读时间格式 /*代码BEGIN*…

Codesys 编程实现随机数字+仿照rand()原理+代码下载

目录 一、C语言中rand&#xff08;&#xff09;随机数的演示及问题 二、同样的原理&#xff0c;在Codesys中实现随机数 三、codesys在线仿真验证功能 四、代码下载 一、C语言中rand&#xff08;&#xff09;随机数的演示及问题 &#xff08;1&#xff09;只用rand&#xff08;…

WebGIS如何加载微件

本篇文章以加载切换底图微件做示范 首先&#xff0c;添加require "esri/widgets/ScaleBar",//比例尺"esri/widgets/Legend",//图例"esri/widgets/basemapGallery" 然后添加加载切换底图的组件代码 const basemapGallery new BasemapGallery(…

探索CSS clip-path: polygon():塑造元素的无限可能

在CSS的世界里&#xff0c;clip-path 属性赋予了开发者前所未有的能力&#xff0c;让他们能够以非传统的方式裁剪页面元素&#xff0c;创造出独特的视觉效果。其中&#xff0c;polygon() 函数尤其强大&#xff0c;它允许你使用多边形来定义裁剪区域的形状&#xff0c;从而实现各…

为什么idea总是提示将内部类设置为static

在写一些内部类的时候&#xff0c;Idea总是提示要设置为static&#xff0c;你知道为什么吗 在Java中&#xff0c;内部类可以被声明为static&#xff0c;这种内部类称为静态内部类&#xff08;Static Nested Class&#xff09;。静态内部类和非静态内部类有显著的区别&#xf…