Spock Primer 翻译

原由

最近要搞groovy介绍,准备作成一系列的东西,参考github上的计划。
https://github.com/javahub/groovy_hellojava

spock没有找到翻译文档,动手把最重要的一章primer翻译下,想起了c++ primer。
就看成翻译练习了。c++

目前项目大使用spock作测试,也是比较推荐的方式。简洁清爽git

Spock Primer

这一章假设你有关于groovy与单元测试基本知识。若是你是java开发者,但没据说过groovy,别担忧,groovy对java开发者来讲很是友好。事实上,groovy相关抓哟设计目标是与java共存脚本语言。所以只须要继续并查看groovy docgithub

这一章目标是教会你足够写真实spec的spock,而且激起你的食欲数据库

学习更多关于groovy,点http://groovy-lang.org/.编程

学些更多关于单元测试,点 http://en.wikipedia.org/wiki/Unit_testing.安全

术语

让咱们开始一些定义:spock 让你写描述指望特性展现spec做为系统关注点,系统感兴趣关注点能够是一个简单的类和整个系统中的任何事,而且被称为spec下的系统(sus)。一个特性描述开始于spec sus的临时快照而且做为一个合做者。快照被称做特性fixture网络

接下来的部分沿着在spock的spec可能组合构建blocks。一个常规spec能使用它们的中一部分。架构

Specification

一个spec表现为继承spock.lang.Specification的一个groovy 类。spec名字一般描述系统或系统操做。例如: CustomerSpec, H264VideoPlayback, and ASpaceshipAttackedFromTwoSides是一个规范合理的名称ide

spec类包含一系统有用的方法,此处它含义为spec用Sputnik在junit中运行,spock的junit runner,感谢Sputnik,spock spec能运行在大多数的ides和构建工具

Fields

def obj = new ClassUnderSpecification()
def coll = new Collaborator()

对象实例字段是一个好的地方去存储属于spec fixture 。它是一个好的实践在正确初始化他们在定义时候。(语义上它等价于初始化他们在setup()方法).存储这些实例字段的对象不该该分享在feature方法之间。取而代之,每一个特性方法获得他们本身的初始化对象。这个帮助在相互间独立隔离特性方法,提供一个好的目标。

@Shared res = new VeryExpensiveResource()

一些时候你须要分享一个对象在特性方法之间。例如对象可能建立很昂贵,或者你可能想在不一样特性方法指南交互。为了实现这一点,定义@Shared 字段,它最好事初始化正确字段在一个定义点(语义上等价等价与setupSpec)

static final PI = 3.141592654

静态字段应该是常量,宁一方面shared字段是完美的,由于他的语义表现了更好的定义。

Fixture Methods

Fixture Methods
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

fixture方法负责在环境设置清理在每一个特性方法运行。一般它是一个好的idea使用刷新fixture 方法在为每一个fixture方法,在setup() and cleanup() 作这个。偶尔对特性方法分享是由意义的。他实现了分享使用shared字段与setupSpec() and cleanupSpec() methods。
全部fixture方法都是可选择的。

说明:setupSpec cleanupSpec 方法不能饮用实例字段

Feature Methods

def "pushing an element on the stack"() {
// blocks go here
}

Feature 方法是一个spec核心,他描述了你期待系统的在sus下的特性集。根据约定,特性方法呗命名字符串常量。试着为你的特性方法选择一个好的方法。而且随心使用你喜欢的特性。

概念,一个特性方法包括四个语义段:
设置特性的fixture
提供sus的刺激
描述系统期待的结果
清理fixture

Blocks

spock实现支持每一个feature方法概念语法阶段做为一个特性方法。对结束来讲,特性方法被构建为一个叫作blocks。blocks开始于一个label而且扩展开始下一个label或者方法结束。这个有六个blocks:setup, when, then, expect, cleanup, and where blocks.。任何语句在方法开始与第一次
隐含的setup block之间。

一个特性方法必须至少一个清晰的label,实际上,一个直接的block表现。Blocks 决定一个方法不一样部分而且不能嵌套。

Blocks2Phases
这个右侧图片展现block如何匹配一个特性方法语法概念。那里的block有一个特殊的角色,被简单的展示出来,但首先让咱们有一个近距离查看这个block

Setup Blocks

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

这个setup block 你能作任何你想setpup工做为你想描述的特性。他可能不能被前面其余block处理,而且不能重复。一个setup block 没有任何特殊的语义。setup: label时可选的并被提交。结果在一个直接设置的block。given: label是一个setup的别名,而且一些时候致使更多的刻的特性方法描述。

When and Then Blocks

when与then blocks
when: //刺激
then://响应

when与then blocks 一直一块儿出现。他们描述一个刺激发生并响应期待。
when blocks能包含任意代码;
then blocks被禁止使用在条件,异常条件,交互与变量定义。
一个特性方法可能匹配多个when-then 块

Conditions

Conditions被描述为一个期待的状态,必须像junit的assertions。然而,conditions被写作一个描述布尔表达式,消除为必需的assertion API(偏偏更多的是一个条件呗处理为一个非布尔表达式,将会被做为处理根据groovy truth)让咱们来看一些场景在如下

stack.size() == 2
|     |      |
|     1      false

尽量保持在每一个小特性方法里的条件数量。一到五个是一个好的指导。若是你有更多,你得问本身,若是在一个场景下有多个不相关的特性被说起。答案若是是yes,在几个小的特性方法重构。若是你的场景时只有不一样的值,考虑使用数据表驱动(spock带有这个特性)

若是一个条件没经过,spock提供什么样的反馈。让咱们试并改变第二个场景

如你所见,spock在一个场景执行期间,捕获全部值处理在,并在一个容易理解的形式表现他们。干得漂亮,不是么?

Implicit and explicit conditions

场景必须有一个 then blocks and expect blocks其中之一。出了调用空方法雨归类表达式做为交互,全部顶级表达式在这些块里面是隐含做为场景对待。在别的地方使用题哦安,你须要设计他们使用groovy的assert关键字。

def setup() {
stack = new Stack()
assert stack.empty
}

若是显试条件被触发,他将处理一些相同的诊断信息做为隐含条件。

Exception Conditions

异常场景呗使用描述做为一个when 语句块应该抛出一个异常。他们被定义使用 thrown()方法,被传递期待的异常类型。例如,描述被弹出一个空stack 应该抛出EmptyStackException,你能像下面这样写

when:
stack.pop()

then:
thrown(EmptyStackException)
stack.empty

如你所见,异常场景多是跟别的场景相关。这个特别有用为对详细说明期待内容做为异常,访问这个异常,首先绑定一个变量。

when:
stack.pop()

then:
def e = thrown(EmptyStackException)
e.cause == null

宁外,你也可使用上面语法做为一个轻微有变化

when:
stack.pop()

then:
EmptyStackException e = thrown()
e.cause == null

这个语法有两个小优点,首先,异常变量时强类型,让它很是容易被ide补全。其次,场景读起来有一点更像句子。注意,若是没有异常类型被传递在thrown方法,它从左边的变量类型被推断

一些时候咱们须要表达一个异常不该该被抛出。例如,让咱们尝试表达hashmap不该该接受null key。

def "HashMap accepts null key"() {
  setup:
  def map = new HashMap()
  map.put(null, "elem")
}

一些时候咱们须要表达一个异常不该该被抛出。例如,让咱们尝试表达hashmap不该该接受null key。

这个表示不能揭示代码的含义。 是否有人离开构建以前就完成实现了这个方法。毕竟,场景在哪里? 幸运是咱们能作更好。

def "HashMap accepts null key"() {
  setup:
def map = new HashMap()

  when:
  map.put(null, "elem")

  then:
  notThrown(NullPointerException)
}

经过使用notThrown。咱们能使清晰在一个特殊的NullPointerException 不该该被抛出。(按照Map.put()方法的约定,它是正确的事情不支持null keys在map上使用。)然而,方法将会失败若是有任何逼的异常抛出。

Interactions

尽管条件描述了一个对象的状态,交互描述了对象互相间关系。交互被转用于一章。因此咱们只快速给一个例子在这。假设咱们想描述从一个发布者到订阅者的事件流。代码以下

def "events are published to all subscribers"() {
    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")

Expect Blocks

一个expect block 比then block有更多的限制。他可能包含场景与变量定义。它是有用的情形当他更加天然描述刺激与期待结果响应在一个简单的表达式里。例如,比较下面两个描述 Math.max()方法。

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

尽管两个片断时语义等价。第二个是更清晰选择。做为一个指引,使用when-then 去描述带有反作用。而且期待描述纯碎的函数方法。

TIP

利用groovy jdk方法像any() every()去建立更多表现力与简洁的条件。

Cleanup Blocks

setup:
def file = new File("/some/path")
file.createNewFile()
// ...
cleanup:
file.delete()

cleanup block

只能跟着where block后面。而且不能重复。像一个cleanup 方法,它做为释放资源使用做为一个特性方法使用,运行即便特性方法抛出异常。所以,cleanup block 必须防护编程。在最糟糕的状况下,它必须优雅的处理在特性方法里抛出异常的第一个语句。而且全部本地变量有默认值。

TIPS

groovy的安全引用操做精简写防护是代码
对象级别的spec 一般不用cleanup方法。做为惟一资源他们消费内存,自动被回收经过垃圾收集齐。更多粗力度spec,可是,可能使用clean 块 清理文件系统关闭数据库链接,关闭网络服务

def "computing the maximum of two numbers"() {
expect:
Math.max(a, b) == c

where:
a << [5, 3]
b << [1, 9]
c << [5, 9]

}
This where block effectively creates two "versions" of the feature method: One where a is 5, b is 1, and c is 5, and another one where a is 3, b is 9, and c is 9.

这个where 块建立两个版本特性方法很是有效:One where a is 5, b is 1, and c is 5, and another one where a is 3, b is 9, and c is 9.

where block 将会在数据驱动测试章节解释

Helper Methods

一些时候特性方法增加巨大在 and/or包含重复代码。在这种情形下引入一个多个帮助方法会有意义。两个好的候选者为帮助方法是setup/cleanup 逻辑与复杂场景。分解出前者是直接的,让咱们看下场景

def "offered PC matches preferred configuration"() {
when:
def pc = shop.buyPc()

then:
pc.vendor == "Sunny"
pc.clockRate >= 2333
pc.ram >= 4096
pc.os == "Linux"

}
若是碰巧你也是电脑极客,你更愿意pc配置是详细的,或者你可能想比较供应从同的商店。所以,让咱们分解条件

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"

}
新的帮助方法matchesPreferredConfiguration 由简单的布尔表达式组成做为结果返回。
这是好的除了那些一个不充分的供应被描述。

Condition not satisfied:

matchesPreferredConfiguration(pc)
|                             |
false                         ...
Not very helpful. Fortunately, we can do better:

void matchesPreferredConfiguration(pc) {
  assert pc.vendor == "Sunny"
  assert pc.clockRate >= 2333
  assert pc.ram >= 4096
  assert pc.os == "Linux"
}

不是颇有什么帮助。幸运的事,咱们能够作更好
当在一个帮助方法分解场景时,必须思考两个点:首先,隐含条件必须转换为显性条件使用assert关键字。其次,帮助方法必须返回空类型。不然,spock会解释返回值为失败条件,这并非咱们想要的。

做为猜测,改进的帮助方法告诉咱们确切什么是错的。

Condition not satisfied:

  assert pc.clockRate >= 2333
         |  |         |
         |  1666      false
         ...

条件不是满意:

最后一个建议:尽管重用代码是一个好事情,但不要是他太远。更聪明使用fixture 和帮助方法能增长耦合在特性方法间。若是你重用太多或者有错误代码,你将以脆弱和艰难改进的spec结束。

Specifications as Documentation

写得好的spec做为一个源码信息价值点。尤为是对高级别spec目标是更普遍的受众不知是开发者(架构师 领域专家 客户等)它是有道理的提供更多信息使用天然语言比只是使用规格与特性的名称。所以,spock提供了一种方式附属文本话的描述使用块。

setup: "open a database connection"
  // code goes here
  Individual parts of a block can be described with and::

  setup: "open a database connection"
  // code goes here

  and: "seed the customer table"
  // code goes here

  and: "seed the product table"

一个and 标签 跟着描述能插入到特性方法的任何位置,没有改变方法语义。

在行为驱动测试,面向客户的特性被描述在一个given-when-then 格式。spock直接支持这个风格的spec使用given标签

given: "an empty bank account"
  // ...

  when: "the account is credited $10"
  // ...

  then: "the account's balance is $10"
  // ...
  As noted before, given: is just an alias for setup:.

做为以前的提示,given知识setup的一个别名

块描述不该该出如今源码里。但应该在运行时可用对spock运行时。块描述用法的计划被加强诊断信息而且文本化的报告被全部利益相关者一样理解。

扩展
如所见,spock提供许多写spec功能。然而老是有这种时候,当一些别的东西被须要。所以,spock提供了一个基于拦截的扩展机制。扩展经过注解被直接激活。当前,spock附带一下指令

@Timeout  
Sets a timeout for execution of a feature or fixture method.

设置超时时间为特性方法或者fixture方法

@Ignore 
Ignores a feature method.

忽略特性方法

@IgnoreRest 
Ignores all feature methods not carrying this annotation. Useful for quickly running just a single method.
不移除这个注释,忽略全部特性方法不去。对快速运行单个方法很是有用。

期待一个特性方法去完成打断。@FailsWith有两个用例:首先,文档化咱们知道的bug不须要马上解释。其次,替换异常条件在一个肯定的角落用例在后面不能使用。在全部的用例,异常条件不是优选。

学习怎样实现你本身的指令与扩展,去看扩展章节

Comparison to JUnit

尽管spock使用不一样的技术,大部分概念和特性呗激活从junit。这有一个粗的比较
comparison

|Spock|JUnit| |--|--| |Specification|Test class| |setup()|@Before| |cleanup()|@After| |setupSpec()|@BeforeClass||cleanupSpec()|@AfterClass| |Feature|Test| |Feature method|Test method||Data-driven feature|Theory||Condition|Assertion||Exception condition|@Test(expected=…​)| |Interaction|Mock expectation (e.g. in Mockito)|

相关文章
相关标签/搜索