内容简介:Spock是一个为groovy和java语言应用程序来测试和规范的框架。这个框架的突出点在于它美妙和高效表达规范的语言。得益于JUnit runner,Spock能够在大多数IDE、编译工具、持续集成服务下工作。Spock的灵感源于JUnit,jMock, RSpec, Groovy, Scala, Vulcans以及其他优秀的框架形态。Spock要求具备基本的groovy和单元测试知识。如果是java程序员,则不需要对groovy感到陌生,因为会java基本就会使用groovy了。首先看一下测试模板方法
介绍
Spock是一个为groovy和 java 语言应用程序来测试和规范的框架。这个框架的突出点在于它美妙和高效表达规范的语言。得益于JUnit runner,Spock能够在大多数IDE、编译 工具 、持续集成服务下工作。Spock的灵感源于JUnit,jMock, RSpec, Groovy, Scala, Vulcans以及其他优秀的框架形态。
基本概念描述
Spock要求具备基本的groovy和单元测试知识。如果是java程序员,则不需要对groovy感到陌生,因为会java基本就会使用groovy了。
Spock测试类的一般结构
首先看一下测试模板方法的定义和JUnit的对比:
Spock基于Groovy,所以像写java测试类一样,需要先创建一个groovy文件。
在一个groovy文件里面的第一行:import spock.lang.*,同时这个类需要继承Specification。
举例:
class MyFirstSpecification extends Specification {
// 变量字段
// 测试模板方法
// 测试方法
// 测试帮助方法
}
Spock的模板方法说明:
def setupSpec() {} // runs once - before the first feature method
def setup() {} // runs before every feature method
def cleanup() {} // runs after every feature method
def cleanupSpec() {} // runs once - after the last feature method
定义变量:
def obj = new ClassUnderSpecification() def coll = new Collaborator()
定义测试方法:
def "pushing an element on the stack"() {
// 测试代码块,最重要的地方
}
代码块一般结构,def定义的方法内容里面:
given: def stack = new Stack() def elem = "push me" when: // 执行需要测试的代码 then: // 验证执行结果
条件判断:
when: stack.push(elem) then: !stack.empty stack.size() == 1 stack.peek() == elem
条件不满足的情况输出结果举例:
Condition not satisfied: stack.size() == 2 | | | | 1 false [push me]
验证抛出异常举例:
when: stack.pop() then: thrown(EmptyStackException) stack.empty
访问异常内容:
when: stack.pop() then: def e = thrown(EmptyStackException) e.cause == null
验证不会抛出异常举例:
//hashmap允许null的key
def "HashMap accepts null key"() {
given:
def map = new HashMap()
when:
map.put(null, "elem")
then:
notThrown(NullPointerException)
}
基于测试的交互,通过mock构造依赖的外部对象
def "events are published to all subscribers"() {
given:
def subscriber1 = Mock(Subscriber)//通过Mock构造一个依赖的对象
def subscriber2 = Mock(Subscriber)
def publisher = new Publisher()
publisher.add(subscriber1)
publisher.add(subscriber2)
when:
publisher.fire("event")
then:
1 * subscriber1.receive("event")//验证方法名为receive,以及参数"event"为的方法执行一次
1 * subscriber2.receive("event")
}
expect代码块:
expect: Math.max(1, 2) == 2
cleanup代码块
given:
def file = new File("/some/path")
file.createNewFile()
// ...
cleanup:
file.delete()
where代码块,可能是Spock最厉害的地方了:
def "computing the maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a << [5, 3]//执行两次测试,值依次为5,3,下面类似
b << [1, 9]
c << [5, 9]
}
测试的时候帮助方法的处理:
def "offered PC matches preferred configuration"() {
when:
def pc = shop.buyPc()
then:
pc.vendor == "Sunny"
pc.clockRate >= 2333
pc.ram >= 4096
pc.os == "Linux"
}
额外定义一个帮助方法:
def "offered PC matches preferred configuration"() {
when:
def pc = shop.buyPc()
then:
matchesPreferredConfiguration(pc)
}
def matchesPreferredConfiguration(pc) {
pc.vendor == "Sunny"
&& pc.clockRate >= 2333
&& pc.ram >= 4096
&& pc.os == "Linux"
}
输出结果,结果没有显示具体的哪个判断失败了:
Condition not satisfied: matchesPreferredConfiguration(pc) | | false ...
使用assert可以达到这个目的:
void matchesPreferredConfiguration(pc) {
assert pc.vendor == "Sunny"
assert pc.clockRate >= 2333
assert pc.ram >= 4096
assert pc.os == "Linux"
}
最终输出结果,可以看到具体的错误条件:
Condition not satisfied:
assert pc.clockRate >= 2333
| | |
| 1666 false
...
在预期值断言的时候使用with:
def "offered PC matches preferred configuration"() {
when:
def pc = shop.buyPc()
then:
with(pc) {
vendor == "Sunny"
clockRate >= 2333
ram >= 406
os == "Linux"
}
}
使用mock对象的时候也可以用with:
def service = Mock(Service) // has start(), stop(), and doWork() methods
def app = new Application(service) // controls the lifecycle of the service
when:
app.run()
then:
with(service) {
1 * start()
1 * doWork()
1 * stop()
}
当多个预期值一起断言的时候使用verifyAll :
def "offered PC matches preferred configuration"() {
when:
def pc = shop.buyPc()
then:
verifyAll(pc) {
vendor == "Sunny"
clockRate >= 2333
ram >= 406
os == "Linux"
}
}
在没有对象参数的时候也可以这样:
expect:
verifyAll {
2 == 2
4 == 4
}
文档规范:
given: "open a database connection" // code goes here
and可以增加多个使代码根据可读性:
given: "open a database connection" // code goes here and: "seed the customer table" // code goes here and: "seed the product table" // code goes here
测试行为规范的一般模式:
给定条件....
当怎么样的时候...
应该怎样
given: "an empty bank account" // ... when: "the account is credited $10" // ... then: "the account's balance is $10" // ...
扩展注解:
@Timeout 设置方法执行的时间
@Ignore 跳过这个测试方法和JUnit类似
@IgnoreRest 跳过其他所有的测试方法,只执行这个测试方法
数据驱动测试
在where里面可以通过数据表格,数据管道,指定变量三种情况对不同的测试case进行赋值,特别是在当需要测试很多种情况的的时候,这个模式具有非常高的可读性和简洁性:
... where: a | _ 3 | _ 7 | _ 0 | _ b << [5, 0, 0] c = a > b ? a : b
基于测试的交互
场景如下,一个发送者向多个订阅者发送消息
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
}
期待执行一次接收方法:
def "should send messages to all subscribers"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello")
1 * subscriber2.receive("hello")
}
断言表达式解释:
1 * subscriber.receive("hello")
| | | |
| | | 关联参数
| | 关联目标方法
| 关联目标对象
执行次数
在mock创建的时候可以定义多个交互行为:
class MySpec extends Specification {
Subscriber subscriber = Mock {
1 * receive("hello")
1 * receive("goodbye")
}
}
一个测试代码的多个交互通过when区分:
when:
publisher.send("message1")
then:
1 * subscriber.receive("message1")
when:
publisher.send("message2")
then:
1 * subscriber.receive("message2")
结果输出:
Too many invocations for: 2 * subscriber.receive(_) (3 invocations)
打桩:
很多时候我们测试的时候不需要执行真正的方法,因为在测试代码里面构建一个真实的对象是比较麻烦的,特别是使用一些依赖注入框架的时候,因此有了打桩的功能:
interface Subscriber {
String receive(String message)
}
模拟返回值:
subscriber.receive(_) >> "ok"
表达式解释:
subscriber.receive(_) >> "ok" | | | | | | | 任何参数的这个方法调用都会返回"ok" | | argument constraint | method constraint target constraint
需要调用多次返回不同结果的时候可以这样定义:
subscriber.receive(_) >>> ["ok", "error", "error", "ok"]
有时候我们测试的一个方法调用了类的其他方法,而其他的方法可能依赖了外部的对象,为了避免引入外部对象,可以使用部分mock功能减少构建外部对象的繁琐:
// this is now the object under specification, not a collaborator
MessagePersister persister = Spy {
// 这个方法的任意参数调用都返回true
isPersistable(_) >> true
}
when:
persister.receive("msg")
then:
// when里面的receive会调用persist,前提是isPersistable是true,因为mock的值是true,这个方法一定会被调用
1 * persister.persist("msg")
和springboot集成
spring本身提供了spring-test测试模块,需要和Spock一起使用的话,只需要在Specification类上面加上SpringApplicationConfiguration这个注解就可以了,如果是mvc应用还需加上WebAppConfiguration这个,其他的使用和原来一样。
maven配置
在pom.xml里面添加
<!-- Spock依赖 -->
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.2-groovy-2.4</version>
<scope>test</scope>
</dependency>
<!-- Spock需要的groovy依赖 -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.15</version>
</dependency>
<!--...... -->
<!--编译插件配置: -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
<configuration>
<useFile>false</useFile>
<includes>
<include>**/*Test.java</include><!--指定JUnit的测试类名称后缀-->
<include>**/*Spec.java</include><!--指定Spock的测试类名称后缀-->
</includes>
</configuration>
</plugin>
总结
Spock整体上是一个规范描述型的测试框架,测试代码非常易读。对于java程序员,上手非常容易,在学习的时候可以先在一个项目上使用,当你发下它的优点的时候,你会尽可能的把其他的测试代码也改成用Spock来写,这个过程可能会花费你更多的时间,但是当你熟悉了以后,它带给你的收益一定是值得的。不要犹豫,学习新框架最快的方式就是马上动手。
参考链接
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 前端单元测试框架梳理
- 单元测试利器Mockito框架
- SniffAir:无线渗透测试框架
- 渗透测试辅助框架WebPocket
- golang 单元测试框架(testing)
- 测试框架原理,构建成功的基石
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
How to Solve It
Zbigniew Michalewicz、David B. Fogel / Springer / 2004-03-01 / USD 59.95
This book is the only source that provides comprehensive, current, and detailed information on problem solving using modern heuristics. It covers classic methods of optimization, including dynamic pro......一起来看看 《How to Solve It》 这本书的介绍吧!