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

07-21 1142阅读

背景

单元测试是保证我们写的代码是我们想要的结果的最有效的办法。根据下面的数据图统计,单元测试从长期来看也有很大的收益。

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

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

单元测试收益:

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

    我们都知道单元测试的好处,但是我们遇到的项目还是很多都缺乏单测,一方面是因为单测耗时,一方面也是因为业务的复杂性导致单测难以进行,最长见还是任务重、工期紧,或者干脆就不写了。

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

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

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

    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)格式化快捷键,因为表格列的长度不一样,手动对齐比较麻烦。

        实际案例

        /**
         * 身份证号码工具类

        * 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 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

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

        下面案例是测试断言的比较器的单测

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

        运行结果:

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

        代码简洁清晰

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

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

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

        添加依赖

        
            org.spockframework
            spock-core
            2.4-M1-groovy-4.0
            test
        
        
            org.spockframework
            spock-spring
            2.4-M1-groovy-4.0
            test
        
        

        If Esle 多分支场景测试

        使用expect+where

        expect相当于when+then的精简版

        @Unroll注解表示展开where标签下面的每一行测试,作为单独的case跑

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

        异常测试

        使用thrown

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

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

        Void方法测试

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

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

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

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

          静态方法测试

          https://javakk.com/302.html

          动态Mock

          具体场景介绍

          使用Spock简化测试代码

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

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

          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

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]