一般,执行相同的测试代码屡次是有用的,在不一样的输入与预期结果。spock数据驱动测试支持使他成为第一类级别特性sql
Suppose we want to specify the behavior of the Math.max method:数据库
class MathSpec extends Specification { def "maximum of two numbers"() { expect: // exercise math method for a few different inputs Math.max(1, 3) == 3 Math.max(7, 4) == 7 Math.max(0, 0) == 0 } }
尽管这种方式是好的在简单的用例像这个,他有一些潜在的缺点:
代码和数据时混合的,可是不容易独立改变
数据不容易自动生成或者获取从外部源
顺序实施相同的代码屡次,它或者已经被复制或者被提取到到一个分离的方法
在失败的用例中,不能马上清理失败引发的输入
实施相同的代码屡次不利于从相同的隔离,做为执行分离方法方式。
spock的数据驱动支持试图解决这些问题。在开始以前,让咱们重构上面的代码使用数据驱动特性方法。首先,咱们介绍三个方法参数替换掉硬编码interge 值。express
class MathSpec extends Specification { def "maximum of two numbers"(int a, int b, int c) { expect: Math.max(a, b) == c ... } }
咱们完成这个逻辑测试,可是仍须要数据值被使用。这样作where block 放置方法的最后。在这个简单的用例,where:block持有数据表。dom
Data tables are a convenient way to exercise a feature method with a fixed set of data values:ide
class Math 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 | 4 0 | 0 | 0 } }
数据表是一个方便的方式实施一个特性方法使用一肯定组的数据。测试
where:
a | _
1 | _
7 | _
0 | _
表第一行,称为数据头,定义了变量。子行,称做数据行,持有相应的数据。对每一行,特性方法都会被执行一次。咱们称做迭代的方法。若是一个迭代方法失败了,意味着其余迭代仍然被执行。全部的
失败都会被报告。编码
迭代式独立的从互相间相同的分割特性方法。每一个迭代获取本身实例从spec类。并setup cleanup会被分别调用在每一个迭代执行先后。code
共享对象在迭代间
顺序的共享一个对象在迭代间,它使用@Shared 或者静态字段对象
提示
只有@Shared 和静态字段 能被访问在where:block里面。
注意,这些对象也能被共享给其余方法。不是一个好的方式共享对象在相同的方法迭代。若是你思考这个问题,思考放入每一个方法在隔离的spec,全部能被处理在相同的文件。这个实现更好的隔离在一些样板代码的成本。索引
class DataDriven extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c where: a | b || c 3 | 5 || 5 7 | 0 || 7 0 | 0 || 0 } }
上面的代码能被调整在几个方面。首先,从where:block 已经定义全部的数据变量,这个方法参数能被提交。其次,输入与期待输出能被分离使用双线去虚拟设置分离。使用它,代码变成这样。
让咱们设想咱们实现max方法有一个错误,其中一个迭代失败了。
两个数字中最大的数字 失败
明显的问题是:迭代失败,数据值时什么。在咱们的例子里,它很是难指出是第二个迭代失败。在其余时候多是更加困难甚至是不可能。在任何用例里,若是spock能大声并清晰哪次失败是很是好的,
超过只是报告失败。这是@Unroll注解的目标
一个方法注释使用@Unroll 将会有迭代过程独立的报告。
为什么@Unroll 不默值?
一个缘由是一些执行环境期待提早告诉测试方法的数量,而且肯定实际数量问题。宁一个缘由是该注解能极大改变测试报告数量,可能不是明智的。
注意unrolling没有影响在方法怎么样执行上。他只是在报告里交替。依赖执行环境,输出像这样
maximum of two numbers[0] PASSED maximum of two numbers[1] FAILED Math.max(a, b) == c | | | | | | 7 0 | 7 42 false
maximum of two numbers[2] PASSED
This tells us that the second iteration (with index 1) failed. With a bit of effort, we can do even better:
@Unroll def "maximum of #a and #b is #c"() { ... }
这个告诉咱们第二个迭代失败,索引为1,随着一点点努力,咱们能作更好。
这个方法名使用占位符,表示经过一前置一个#符号,关联数据变量a b c,在输出,占位符将会被替换使用具体的值。
maximum of 3 and 5 is 5 PASSED maximum of 7 and 0 is 7 FAILED Math.max(a, b) == c | | | | | | 7 0 | 7 42 false maximum of 0 and 0 is 0 PASSED
如今咱们一眼能看出是max 方法失败在输入7与0。看主题上的更多细节在on Unrolled Method Names 这小节
Data tables aren’t the only way to supply values to data variables. In fact, a data table is just syntactic sugar for one or more data pipes:
...
where: a << [3, 7, 0] b << [5, 0, 0] c << [5, 7, 0]
数据表不知是提供数据变量的一种方式。实际上,一个数据表只是一个语法糖为一个或者更多个数据管道。
一个数据管道,经过left-shift (<<) 操做符号,链接一个数据变量到一个数据提供者。数据提供者持有全部的值对变量,每次迭代之一。任何groovy已知对象如何遍历被使用全部数据提供者。这个包含的对象有类型有 Collection, String, Iterable, and 实现了迭代器约定的对象。数据提供者不须要必须有数据。他们能获取数据从外部数据源如文本文件,数据库,电子表格,随即生成数据。数据提供者被查询
只在须要时获取下一个值。
@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
若是一个数据提供者返回多个值在每一个迭代。它将同步链接多个数据变量。这个语法时相似groovy 多任务但使用元括号取代左侧括号
def "maximum of two numbers"() { ... where: [a, b, c] << sql.rows("select a, b, c from maxdata") }
不感兴趣的数据值能被忽略使用一个下划线
...
where: [a, b, _, c] << sql.rows("select * from maxdata")
一个数据变量呗直接分配一个值
...
where: a = 3 b = Math.random() * 100 c = a > b ? a : b
分配被从新评估在每一个迭代。如上面所示,右边部分分配能够关联其余数据变量。
... where: row << sql.rows("select * from maxdata") // pick apart columns a = row.a b = row.b c = row.c
Data tables, data pipes, and variable assignments can be combined as needed:
数据表 数据管道 多个变量分配被组合做为须要
...
where: a | _ 3 | _ 7 | _ 0 | _ b << [5, 0, 0] c = a > b ? a : b
迭代间的数量依赖多少数据时可变的。连续执行相同的方法能产生不一样数量的迭代。若是一个数据提供者运行出值比他的同行快,一个异常将产生。多个变量分配不能影响迭代数量。一个where:block只包含分配确切产生一个迭代。
After all iterations have completed, the zero-argument close method is called on all data providers that have such a method.
全部迭代完成后,没有参数的关闭方法被调用在全部数据提供者有如此的方法
一分unrolled方法名根grooy的字符串相似。除了接下来的不一样:
表达被标注# 代替 $,没有相等于 ${…}语法
表达式只支持属性访问和无参调用
给一个Person类只有name与age以及数据类型为person的变量。接下来的校验方法名是:
def "#person is #person.age years old"() { ... } // property access def "#person.name.toUpperCase()"() { ... } // zero-arg method call
Non-string values (like #person above) are converted to Strings according to Groovy semantics.
The following are invalid method names:
def "#person.name.split(' ')[1]" { ... } // cannot have method arguments def "#person.age / 2" { ... } // cannot use operators
If necessary, additional data variables can be introduced to hold more complex expression:
def "#lastName"() { ... where: person << ... lastName = person.name.split(' ')[1] }
想法背后运行方法参数更好的被IDE支持。而后 最新版本的IntelliJ IDEA 自动认出数据变量,甚至从数据表的值推断出他们的类型
例如,一个特性方法能使用数据变量在setup: block 但不能在其余任何条件
groovy语法不运行$符号在方法名称中