SPOCK 一个基于Groovy 的单元测试框架

发布于:2024-11-28 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、Spock 是什么

简单地说,spock是一个测试框架,他的核心特性有以下几个:
在这里插入图片描述
【1】可以应用于javagroovy应用的单元测试框架。
【2】测试代码使用基于groovy语言扩展而成的规范说明语言specification language
【3】通过junit runner调用测试,兼容绝大部分junit的运行场景(ide,构建工具,持续集成等)。
【4】框架的设计思路参考了JUnitjMockRSpecGroovyScala,Vulcans……

GroovyJava平台上设计的面向对象编程语言。这门动态语言拥有类似PythonRubySmalltalk中的一些特性,可以作为Java平台的脚本语言使用。

Groovy的语法与Java非常相似,以至于多数的Java代码也是正确的Groovy代码。Groovy代码动态的被编译器转换成Java字节码。由于其运行在JVM上的特性,Groovy可以使用其他Java语言编写的库。
【1】轻量级、对于java程序员来说门槛低
【2】完全兼容java语法
【3】提供脚本语言灵活性、元编程、specification language

二、为什么使用 Spock

【1】spock框架使用标签分隔单元测试中不同的代码,更加规范,也符合实际写单元测试的思路
【2】代码写起来更简洁、优雅、易于理解
【3】由于使用groovy语言,所以也可以享受到脚本语言带来的便利
【4】底层基于jUnit,不需要额外的运行框架
【5】已经发布了2.0-M版本,基本没有比较严重的bug

def "test getTransportTypeNameConfig"(){
	given:
	enumMapConfig.put("name.type.transport:"+transportType,expecyName);
	when:
	String actualTypeName = settingContext.getTransportTypeNameConfig(transportType);
	then:
	expecyName == actualTypeName;

	where:
	transportType | expecyName
	"A"           | "AType"
	"B"           | "BType"
	null          | ""
}

三、使用 Spock

【1】maven引入spock框架

<!-- Mandatory dependencies for using Spock -->
<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-core</artifactId>
    <scope>test</scope>
</dependency>

【2】 Groovy语言支持

<dependency> <!-- use a specific Groovy version rather than the one specified by spock-core -->
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy</artifactId>
    <version>2.5.9</version>
</dependency>

由于spock是基于groovy语言的,所以需要创建groovy的测试源码目录:首先在test目录下创建名为groovy的目录,之后将它设为测试源码目录
在这里插入图片描述

def "test getTransportTypeNameConfig"(){
	given:
	enumMapConfig.put("name.type.transport:"+transportType,expecyName);
	when:
	String actualTypeName = settingContext.getTransportTypeNameConfig(transportType);
	then:
	expecyName == actualTypeName;

	where:
	transportType | expecyName
	"A"           | "AType"
	"B"           | "BType"
	null          | ""
}

四、Spock 基本概念

Specification等同于 测试类,继承自spock.lang Specification基类, Specification是一个Groovy类,提供一些有用的方法,verifyAll(Coloure)WithThrow还有Sputnik, Spock’s JUnit runner.Mock之类的

class MyFirstSpecification extends Specification {
	// fields
	// fixture methods
	// feature methods
	// helper methods
}

Methods 夹具

def setupSpec() {} // runs once -before the first feature method

def setup() {} // runs beforeevery feature method

def cleanup() {} // runs afterevery feature method

def cleanupSpec() {} // runs once -after the last feature method

feature method这个是spock测试代码的核心,类似于Junit中的TestMethod,支持使用 字符串字面量命名,这样可以得到更高的可读性。

Conceptually, a feature method consists of four phases:
1. Set up the feature's fixture
2. Provide a stimulus to the system under specification
3. Describe the response expected from the system
4. Clean up the feature's fixture

Blocks每个feature method又被划分为不同的block,不同的block处于测试执行的不同阶段,在测试运行时,各个block按照不同的顺序和规则被执行

Given Blocks 主要做初始化

given: def stack = new Stack() 
def elem = "push me”

When and Then Blocks ,调用被测试逻辑,执行测试条件
在这里插入图片描述
Conditions
测试目标类

def "HashMap accepts null key"(){
	given:
	def map = new HashMap()
	
	when:
	map.put(null, "elem")
	
	then:
	notThrown(NullPointerException)
}
when:
stack.push(elem)

then:
!stack.empty
stack.size()==1
stack.peek()== elem

异常测试

GROOVY
when :
stack.pop()

then :
thrown(EmptyStackException)
stack.empty

Conditions 方法调用

有时候可能需要检查 依赖的方法背调用,调用、几次、参数是啥

def "events are published to all subscribers"() {
	given:
	def subscriber1 = Mock(Subscriber)
	def subscriber2 = Mock(Subscriber)
	def publisher = new Publisher()
	publisher.add(subscriber1)
	publisher.add(subscriber2)

	when:
	publisher.fire("event")
	then:
	1 * subscriber1.receive("event")
	1 * subscriber2.receive("event")
	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)
	}

Conditions —— Expect BlockWhenThen 写到一起就是Expect

when:
def x= Math.max(1, 2)

then :
×==2

expect:
Math.max(1,2) == 2

Cleanup Blocks

given:
def file = new File("/some/path")
file.createNewFile()

//...

cleanup:
file.delete()

Where BlocksData Driven Testing

写好测试逻辑,使用不同的数据测试,方便对边界检查等,还有一些技巧,可以发挥想象力

class MathSpec extends Specification {
	def "maximum of two numbers"(int a, int b, int c) {
	expect:
	Math.max(a, b)== c

	where:
	a | b | c
	1 | 3 | 3
	7 | 4 | 7
	0 | 0 | 0
	}
}

五、Spock Mocking

def subscriber = Mock(Subscriber)

def subscriber2 = Mock(Subscriber)

Subscriber subscriber = Mock()

Subscriber subscriber2 = Mock()

subscriber.receive(_) >> “ok”每次都返回“ok”

subscriber.receive(“message1”) >> “ok”参数是message1时返回OK

subscriber.receive(_) >>> [“ok”, “error”, “error”, “ok”]返回序列值

subscriber.receive(_) >> { args -> args[0].size() > 3 ? “ok” : “fail” }你想返回什么值

subscriber.receive(_) >> { throw new InternalError(“ouch”) } 异常

ubscriber.receive(_) >>> [“ok”, “fail”, “ok”] >> { throw new InternalError() } >> “ok”组合起来

given:
subscriber.receive("message1")>"ok"

when :
publisher.send("message1")

then :
1 * subscriber.receive("message1")

Where BlocksData Driven Testing

写好测试逻辑,使用不同的数据测试,方便对边界检查等,还有一些技巧,可以发挥想象力

class MathSpec extends Specification {
def "maximum of two numbers"(int a, int b, int c) {
	expect:
	Math.max(a, b)== c

	where:
	a | b | c
	1 | 3 | 3
	7 | 4 | 7
	0 | 0 | 0
	}
}

Github 源代码
官方文档
案例
Groovy 文档