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

Spock Unit Test in Java

优质博文:IT-BLOG-CN
在这里插入图片描述

一、简介

Spock是一个基于Groovy语言的测试和规范框架,使得测试代码更简介,得益于JUnit RunnerSpock兼容大部分IDE和测试框架JUnit/JMock/Powermock等。基于BDD行为驱动开发,功能非常强大。提供了各种标签,并采用简单、通用、结构话描述语言,让编写测试代码更加简介、高效。官方文档

为什么要使用Spock:因为它优美而富有表现力的规范语言。Spock的灵感来自JUnitRSpecjMockMockitoGroovyScalaVulcans

主流单元测试框架比较

特性/框架JUnitMockitoPowerMockJMockEasyMockSpock
语言JavaJavaJavaJavaJavaGroovy(兼容Java)
主要用途单元测试、集成测试Mock对象的创建和验证Mock静态方法、构造函数和私有方法Mock对象的创建和验证Mock对象的创建和验证行为驱动开发(BDD)、单元测试、集成测试
BDD支持部分支持(通过扩展)不支持不支持不支持不支持原生支持
Mocking需要与Mockito或其他库结合原生支持Mock扩展Mockito,支持Mock静态方法和私有方法原生支持Mock原生支持Mock原生支持Mock|
数据驱动测试支持(需要额外的库,如JUnitParams不支持不支持不支持不支持原生支持
语言简洁性较为简洁,但需要一定的配置非常简介复杂(需要结合Mockito使用,配置较多)较为复杂(需要配置和学习JMock特有API)较为简洁,API清晰非常简介(DSL语法)
错误报告标准错误报告标准错误报告标准错误报告标准错误报告标准错误报告详细的错误和断言失败信息
IDE支持优秀(广泛支持)优秀(广泛支持)较好(但需要与Mockito结合)较好(但使用者较少)较好(广泛支持)良好(主流IDE支持,但部分功能可能不如JUnit完善
社区和生态系统非常成熟和广泛成熟和广泛较小(依赖Mockito的社区和生态系统)相对较少相对成熟相对较少,但在增长中
学习曲线低(大多数Java开发者熟悉)低(与JUnit结合使用时)高(需要学习Mockito和PowerMock的结合使用)中等(需要学习JMock API和配置)低(简单易学)中等(需要学习Groovy语法,但DSL使测试更直观
性能高性能高性能较高性能开销(复杂的Mock和字节码操作)高性能高性能较好的性能(但Groovy可能带来一些开销

二、优点

【1】简介且可读性强的语法: Spock使用Groovy语言编写测试脚本,其DSL领域特定语言使得测试代码非常简洁和可读。描述性的方法名:setup()/when()/then()使得测试逻辑一目了然。

内置多种标签来规范单元测试代码,使测试代码更规范、结构更清晰、可读性高、降低后续维护成本。

Spock是基于Groovy的测试框架,Groovy是一种在Java平台上的强大动态语言,简洁、灵活。相比PowerMock/JMock等框架需要在Java中编写,语法相对更加繁琐。Groovy的动态语言特性使得编写测试代码更加简洁和易读。

【2】数据驱动测试: Spock非常强大地支持数据驱动测试,通过where块可以方便地定义多组输入输出,减少重复代码,增加单元测试覆盖率。

【3】强加的错误抱错: Spock提供了详细的错误报告和断言失败信息,使得调试和修复问题更加容易。错误信息通常包含详细的上下文,使得定位问题更加直接。

【4】内置Mocking支持: Spock内置了MockingStubbing的功能,无需依赖第三方(如Mockito)。

【5】BDD风格支持: Spock天然支持行为驱动开发(BDD)风格的测试、这使得编写和维护测试变得更加直观。BDD风格的测试不仅描述了测试的行为,还能更好地表达业务逻辑,有助于提升代码质量。

三、 缺点

【1】IDE支持: 主流的IDE都支持Spock,但对于断点调试和代码补全等支持没有JUnit那样成熟。

【2】性能开销: 由于Spock使用Groovy作为脚本语言,可能会带来一定的性能开销。尽管开销大多数可以忽略,但是对性能极为敏感的场景下需要注意。

【3】社区生态较小: 虽然Spock功能强大,但相比JUnit/Spock的社区和生态系统相对较小。这意味着在遇到问题时,可用的资源和支持较少。

【4】学习曲线: 对于没有Groovy背景的开发者而言,学习和掌握Spock的语法需要一些时间。

四、环境配置

【1】pom依赖配置

<dependencyManagement><dependencies><dependency><groupId>org.spockframework</groupId><artifactId>spock-bom</artifactId><version>2.3-groovy-4.0</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement><dependencies><dependency><groupId>org.spockframework</groupId><artifactId>spock-core</artifactId><scope>test</scope></dependency><dependency><groupId>org.spockframework</groupId><artifactId>spock-junit4</artifactId><scope>test</scope></dependency>
</dependencies><plugins><!-- Mandatory plugins for using Spock --><plugin><!-- The gmavenplus plugin is used to compile Groovy code. To learn more about this plugin,visit https://github.com/groovy/GMavenPlus/wiki --><groupId>org.codehaus.gmavenplus</groupId><artifactId>gmavenplus-plugin</artifactId><version>3.0.2</version><executions><execution><goals><goal>compile</goal><goal>compileTests</goal></goals></execution></executions></plugin><!-- Optional plugins for using Spock --><!-- Only required if names of spec classes don't match default Surefire patterns (`*Test` etc.) --><plugin><artifactId>maven-surefire-plugin</artifactId><version>3.2.5</version><configuration><useFile>false</useFile><includes><include>**/*Test</include><include>**/*Spec</include></includes></configuration></plugin>
</plugins>

【2】Spock测试结构:测试类与规范。目录结构如下:

--src--main--test--groovy--com.flight.xxx(包名)--XXXSpec.groovy--XXXTest.groovy

五、测试方法的生命周期

junit使用时,主要用以下注解来标记测试类的方法:

@Test :标记需要运行的测试方法,一个测试类中可以有多个@Test方法;
@Before/@After :标记的方法,会在每个测试方法运行之前/之后运行一次;
@BeforeClass/@AfterClass :标记的方法会在测试类初始化时/销毁时运行;

spock没有使用以上的注解形式,而是测试类需要继承 Specification 父类,重写父类中的以下方法,就可以自定义测试方法的生命周期:

def setup() {}         // run before every feature method
def cleanup() {}       // run after every feature method
def setupSpec() {}     // run before the first feature method
def cleanupSpec() {}   // run after the last feature method

测试代码

package com.yawn.spockimport spock.lang.Shared
import spock.lang.Specification/*** spock 测试*/
class CalculateSpec extends Specification {// 初始化def setupSpec() {calculateService = new CalculateService()println ">>>>>>   setupSpec"}def setup() {println ">>>>>>   setup"}def cleanup() {println ">>>>>>   cleanup"}def cleanupSpec() {println ">>>>>>   cleanupSpec"}def "test life cycle"() {given:def a = 1def b = 2expect:a < bprintln "test method finished!"}
}

六、普通方法

【1】创建mock对象

def subscriber = Mock(Subscriber)
def subscriber2 = Mock(Subscriber)
Subscriber subscriber3 = Mock {1 * receive("hello")1 * receive("goodbye")
}Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()

【2】注入mock对象

class PublisherSpec extends Specification {Publisher publisher = new Publisher()Subscriber subscriber = Mock()Subscriber subscriber2 = Mock()def setup() {publisher.subscribers << subscriber // << is a Groovy shorthand for List.add()publisher.subscribers << subscriber2}
}

【3】调用频率

1 * subscriber.receive("hello") // exactly one call
0 * subscriber.receive("hello") // zero calls
(1..3) * subscriber.receive("hello") // between one and three calls (inclusive)
(1..) * subscriber.receive("hello") // at least one call
(..3) * subscriber.receive("hello") // at most three calls
_ * subscriber.receive("hello") // any number of calls, including zero
// (rarely needed; see 'Strict Mocking')

【4】目标约束

1 * subscriber.receive("hello") // a call to 'subscriber'
1 * _.receive("hello") // a call to any mock object

【5】方法约束

1 * subscriber.receive("hello") // a method named 'receive'
1 * subscriber./r.*e/("hello") // a method whose name matches the given regular expression (here: method name starts with 'r' and ends in 'e')

【6】参数约束

1 * subscriber.receive("hello") // an argument that is equal to the String "hello"
1 * subscriber.receive(!"hello") // an argument that is unequal to the String "hello"
1 * subscriber.receive() // the empty argument list (would never match in our example)
1 * subscriber.receive() // any single argument (including null)
1 * subscriber.receive(*) // any argument list (including the empty argument list)
1 * subscriber.receive(!null) // any non-null argument
1 * subscriber.receive(_ as String) // any non-null argument that is-a String
1 * subscriber.receive(endsWith("lo")) // any non-null argument that is-a String
1 * subscriber.receive({ it.size() > 3 && it.contains('a') })
// an argument that satisfies the given predicate, meaning that
// code argument constraints need to return true of false
// depending on whether they match or not
// (here: message length is greater than 3 and contains the character a)

【7】返回固定值

subscriber.receive(_) >> "ok"

【8】返回值序列:返回一个序列,迭代且依次返回指定值。如下所示,第一次调用返回ok,第二次调用返回error,以此类推

subscriber.receive(_) >>> ["ok", "error", "error", "ok"]

【9】动态计算返回值

subscriber.receive() >> { args -> args[0].size() > 3 ? "ok" : "fail" }
subscriber.receive() >> { String message -> message.size() > 3 ? "ok" : "fail" }

【10】产生异常

subscriber.receive(_) >> { throw new InternalError("ouch") }

【11】链式响应

subscriber.receive(_) >>> ["ok", "fail", "ok"] >> { throw new InternalError() } >> "ok"

【12】集成其他测试框架:Powermock集成

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([WorkContextFactory.class])
class NotificationDataNewCollectorTest extends Specification {
}

Mockito集成

@ExtendWith(MockitoExtension)
class MockStaticMethodSpec extends Specification {def "mock static method"() {given:def mock = Mockito.mockStatic(StringUtils)and:mock.when { StringUtils.equalsIgnoreCase(Mockito.any(), Mockito.any()) }.thenReturn(false)expect:result == StringUtils.equalsIgnoreCase(s1, s2)cleanup:mock.close()where:s1  | s2  || result"a" | "a" || false"a" | "b" || false}
}

七、静态方法

静态方法案例

public class TestClass {public static String staticMethod() {return null;}}

测试类:对于静态方法,私有方法,final方法,在用powermock做单元测试的时候,需要增加注解@PrepareForTest

这个注解的作用就是:该注释告诉PowerMock(ito)列出的类将需要在字节码级别上进行操作。

import org.junit.Rule
import org.mockito.Mockito
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.rule.PowerMockRule
import spock.lang.Specification@PrepareForTest([TestClass.class])
class MockStaticMethodSpec extends Specification {@RulePowerMockRule mPowerMockRule = new PowerMockRule();def "测试静态方法"() {setup :PowerMockito.mockStatic(TestClass.class)when :Mockito.when(TestClass.staticMethod()).thenReturn("测试用字串")then :TestClass.staticMethod() == "测试用字串"}}

相关文章:

  • c++ 11 =delete
  • 数据结构(面试)
  • Java:类和对象
  • c++网络编程实战——开发基于协议的文件传输模块(一)如何实现一个简单的tcp长连接
  • vulnhub靶机:Tomato
  • 【Spring】详细了解静态代理和动态代理的使用
  • Android读取拨号记录功能
  • 【九】Hadoop3.3.4HA高可用配置
  • Vue3 + js-echarts 实现前端大屏可视化
  • java计算机毕设课设—网上招聘系统(附源码、文章、相关截图、部署视频)
  • 扩展------零拷贝技术(Mmap,SendFile)
  • 统计语言模型——Ngram
  • SpringMVC 工作流程简述
  • 2024年华数杯数学建模竞赛——赛题浅析
  • FFmpeg实现文件夹多视频合并
  • [译] 怎样写一个基础的编译器
  • javascript 哈希表
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • MySQL主从复制读写分离及奇怪的问题
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • PHP CLI应用的调试原理
  • Puppeteer:浏览器控制器
  • socket.io+express实现聊天室的思考(三)
  • 基于Android乐音识别(2)
  • 利用DataURL技术在网页上显示图片
  • 微服务框架lagom
  • 为什么要用IPython/Jupyter?
  • 一起参Ember.js讨论、问答社区。
  • 用jQuery怎么做到前后端分离
  • 06-01 点餐小程序前台界面搭建
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • No resource identifier found for attribute,RxJava之zip操作符
  • python最赚钱的4个方向,你最心动的是哪个?
  • ​zookeeper集群配置与启动
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • ## 基础知识
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • #WEB前端(HTML属性)
  • #传输# #传输数据判断#
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • (14)学习笔记:动手深度学习(Pytorch神经网络基础)
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (论文阅读笔记)Network planning with deep reinforcement learning
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (一)Neo4j下载安装以及初次使用
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • ... 是什么 ?... 有什么用处?
  • .gitignore
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .NET和.COM和.CN域名区别
  • @angular/cli项目构建--Dynamic.Form
  • @EnableAsync和@Async开始异步任务支持
  • @vue/cli脚手架