lsekfe 发表于 2022-7-7 09:51:52

单元测试:优雅的Spock框架(上)

1、背景简介
  目前,公司的一些业务系统代码中,普遍缺少单元测试这一块,根据公司的安排,下面有计划要加上必要的单元测试,因为单元测试对软件开发很重要。我们小组近期对单元测试的这块进行了调研和实践,发现Spock很好用,所以才有机会对单元测试框架Spock进行学习,并做一次总结和分享。文章最后为大家准备了单元测试相关的资料和案例代码项目。
2、关于单元测试
  据我了解,可能大多数程序员,平时不经常主动去写单元测试代码,而且可能会吐槽说:写单元测试很浪费时间,又没有什么显而易见的成效。那我们为什么还要写单元测试呢?
  2.1、为什么要进行单元测试
  平时我们写代码,免不了要进行一些测试,如果没有使用单元测试,手动测试的方式有很多种,对于简单的程序,我们可以写一个main方法,调式查看指定的方法是否符合预期;对于一个服务系统,我们可以使用PostMan等工具来模拟一下真实请求,查看输入输出是否符合预期。无论那种测试,都有他的关注点,比如测试功能是否完备和正确,性能是否符合要求等。
  以上测试方法有哪些问题?
  ·很难覆盖所有业务逻辑代码,而且无法统计覆盖率
  · 无法自动化重复测试,每次都需要人工调用,或者依赖外部工具
  · 如果测试的功能依赖过多其他模块或者依赖外部系统,而正好依赖的部分还没有准备好怎么办?
  · 对于代码中的大多数Bug问题,单元测试阶段是最容易被发现的,如果没有进行单元测试,那么后期发现这些Bug的周期会越来越长,修复的成本越来越高。......
  单纯的面对手动测试中这样或者那样的问题,是否就是我们使用单元测试的动力呢?
  大家应该有意无意间听到过这样的吐槽:
  · 写单元测试太浪费时间了,增加程序员的工作量
  · 我写代码很严谨,水平很高,我是不是可以不进行单元测试?
  · 我把测试都写了,那么测试人员做什么呢?......
  不可否认,写单元测试确实会占用编码的时间,甚至有些情况下写单元测试的时间会比写业务逻辑代码的时间还要长。
  但是:
  · 单元测试保证测试的高覆盖率
  单元测试是所有测试中最底层的一类测试,是第一个环节,也是最重要的一个环节,是唯一一次可以实现代码覆盖率达到100%的测试环节,覆盖率代表了绝大多数数Bug会在这里被发现。
  · 单元测试可以降低软件开发的成本
  来自微软的统计数据:bug在单元测试阶段被发现,平均耗时3.25小时,如果漏到系统测试阶段,要花费11.5小时。而且85%的缺陷都在代码设计阶段产生,而发现bug的阶段越靠后,耗费成本就越高,指数级别的增高。
  · 单元测试可以自动化地重复进行测试
  这在代码重构时很重要,因为代码重构可能会难免涉及到代码的改动,导致代码逻辑可能和最终需求不一致的问题,但是如果有自动化且可以快速的重复进行测试,并发现问题,将会大大提高重构代码的安全性。
  · 提升代码质量,规范代码
  想要写出更容易测试的业务代码,需要在满足业务需求的基础上,合理的设计代码结构和规范。,换句话说,单元测试促进了代码质量的提高。
  一个案例:
  来自《单元测试的艺术》这本书提到一个案例:找了开发能力相近的两个团队,同时开发相近的需求。进行单测的团队在编码阶段时长增长了一倍,从7天到14天,但是,这个团队在集成测试阶段的表现非常顺畅,bug量小,定位bug迅速等。换句话说,看似单元测试会占用编码时间,但是一旦从一个完整的需求交付周期来看,编码阶段的投入,换来了集成测试阶段的顺畅,且整体看来,交付总时间是减少的。

如上图的案例最终的效果,整体交付时间和缺陷数,均是单元测试团队最少。
  缺陷数大大减少,意味着什么呢?
  程序员改Bug的时间会减少整个团队为一个Bug付出的沟通成本会减少用户的体验会更好。
  既然单元测试有很多优点,那么到底什么是单元测试呢?
  2.2、什么是单元测试
  从软件开发周期的时间维度看,除了单元测试还有那些测试,我在网上找到一个软件开发中一个方法论模型——V模型。

这个模型中说到了各种各样的测试,这里不讨论每一种测试的具体概念,只观察一下单元测试在其中的位置,位于产品编码的后面,位于其他测试的前面,相当于最底层的和最先进行的测试环节,举个例子来理解这个阶段进行单元测试的意义:螺丝钉与飞机的故事——没有完备的单元测试的代码所构成的系统,就像组装一架飞机,但各个零部件出厂时并没有经过严格检验,只在会后组装好以后,通过试飞来检验飞机质量一样,结果只能是冒着极大的坠毁风险。尽管软件开发不会“坠毁”,但是严重的问题如果不尽量在早期发现并解决的话,影响线上使用效率甚至影响产品的客户流失,其损失同样不亚于飞机的坠毁。
  这个例子主要说明,单元测试的是:通过软件开发早期对整个系统的最小可测试单元(比如具体某个方法)的正确性检测,从而将低软件开发的整体风险和成本的一种工作。以下是比较正式的定义:
  单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
  ——摘自维基百科
  网上也有人根据经验来说,广义的讲,单元可以理解成一个相对的概念,单元的定义取决于自己所处的开发语言环境。如果正在使用函数式编程,一个单元最有可能指的是一个函数。你的单元测试将使用不同的参数调用这个函数,并断言它返回了期待的结果;在面向对象语言里,从一个单一的方法到一整个的类都可以是一个单元,单元测试可以使用不同的测试用例来测试这个方法或者类的行为是否符合预期。
  3、测试框架
  3.1、Spock是什么?和JUnit、jMock有什么区别?
  Spock是一款国外优秀的测试框架,基于BDD(行为驱动开发)思想实现,功能非常强大。Spock结合Groovy动态语言的特点,提供了各种标签,并采用简单、通用、结构化的描述语言,让编写测试代码更加简洁、高效。
  其他一些框架,比如JUnit、jMock、Mockito都是相对独立的工具,只是针对不同的业务场景提供特定的解决方案。其中JUnit单纯用于测试,并不提供Mock功能。尽管jMock、Mockito提供了Mock功能,可以把接口等依赖屏蔽掉,但不能对静态方法Mock。要想进行静态方法的Mock,需要使用PowerMock、jMockit。这些独立的框架之间也需要配合才能适用实际的测试场景,并且语法上比较繁琐,没有固定且有效的规范可寻。工具多了就会导致不同的人写出的单元测试代码“五花八门”,风格相差较大。相对Spock来说是不易管理和维护的。
  3.2、Spock的优势
  Spock之所以能够在众多测试框架中脱颖而出,是因为Spock与其他框架相比,具有以下优势:
  ·使用Groovy这种动态语言来编写测试代码,可以让我们编写的测试代码更简洁,适合敏捷开发,提高编写单元测试代码的效率。
  Groovy 是 Apache 旗下的一门基于 JVM 平台的动态/敏捷编程语言,在语言的设计上它吸纳了 Python、Ruby 和 Smalltalk 语言的优秀特性,语法非常简练和优美,开发效率也非常高。并且,Groovy 可以与 Java 语言无缝对接,在写 Groovy 的时候如果忘记了语法可以直接按Java的语法继续写,也可以在 Java 中调用 Groovy 脚本,都可以很好的工作(这是因为Groovy源码文件最终也会被编译成符合JAVA class规范的.class文件,在JVM中运行),这有效的降低了 Java 开发者学习 Groovy 的成本。
  · 规范化测试代码,内置多种标签来规范单元测试代码的语义,测试代码结构清晰,更具可读性,降低后期管理和维护难度。
  内置标签比如:given、and、when、then、expect、where……帮助我们应对复杂的测试场景。
  · 自带Mock功能。
  Mock的英文本意是“模拟的,虚假的”,顾名思义,在软件开发的单元测试中,它的作用是模拟一些接口依赖,方便测试方法可以顺利执行。
  · 遵从BDD(行为驱动开发)模式,有助于提升代码的质量。
  TDD:Test-driven development (测试驱动开发)BDD:Behavior-Driven Development (行为驱动开发)。



页: [1]
查看完整版本: 单元测试:优雅的Spock框架(上)