Spock翻译Data Driven Testing

Data Driven Testing

一般,执行相同的测试代码屡次是有用的,在不一样的输入与预期结果。spock数据驱动测试支持使他成为第一类级别特性sql

Introduction

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

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 | _
表第一行,称为数据头,定义了变量。子行,称做数据行,持有相应的数据。对每一行,特性方法都会被执行一次。咱们称做迭代的方法。若是一个迭代方法失败了,意味着其余迭代仍然被执行。全部的
失败都会被报告。编码

Isolated Execution of Iterations

迭代式独立的从互相间相同的分割特性方法。每一个迭代获取本身实例从spec类。并setup cleanup会被分别调用在每一个迭代执行先后。code

共享对象在迭代间
顺序的共享一个对象在迭代间,它使用@Shared 或者静态字段对象

提示
只有@Shared 和静态字段 能被访问在where:block里面。
注意,这些对象也能被共享给其余方法。不是一个好的方式共享对象在相同的方法迭代。若是你思考这个问题,思考放入每一个方法在隔离的spec,全部能被处理在相同的文件。这个实现更好的隔离在一些样板代码的成本。索引

Syntactic Variations

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 已经定义全部的数据变量,这个方法参数能被提交。其次,输入与期待输出能被分离使用双线去虚拟设置分离。使用它,代码变成这样。

Reporting of Failures

让咱们设想咱们实现max方法有一个错误,其中一个迭代失败了。

两个数字中最大的数字 失败

明显的问题是:迭代失败,数据值时什么。在咱们的例子里,它很是难指出是第二个迭代失败。在其余时候多是更加困难甚至是不可能。在任何用例里,若是spock能大声并清晰哪次失败是很是好的,
超过只是报告失败。这是@Unroll注解的目标

Method Unrolling

一个方法注释使用@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 Pipes

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 实现了迭代器约定的对象。数据提供者不须要必须有数据。他们能获取数据从外部数据源如文本文件,数据库,电子表格,随即生成数据。数据提供者被查询
只在须要时获取下一个值。

Multi-Variable Data Pipes

@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")

Data Variable Assignment

一个数据变量呗直接分配一个值
...

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

Combining Data Tables, Data Pipes, and Variable Assignments

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

Number of Iterations

迭代间的数量依赖多少数据时可变的。连续执行相同的方法能产生不一样数量的迭代。若是一个数据提供者运行出值比他的同行快,一个异常将产生。多个变量分配不能影响迭代数量。一个where:block只包含分配确切产生一个迭代。

Closing of Data Providers

After all iterations have completed, the zero-argument close method is called on all data providers that have such a method.
全部迭代完成后,没有参数的关闭方法被调用在全部数据提供者有如此的方法

More on Unrolled Method Names

一分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]
}
  1. 想法背后运行方法参数更好的被IDE支持。而后 最新版本的IntelliJ IDEA 自动认出数据变量,甚至从数据表的值推断出他们的类型

  2. 例如,一个特性方法能使用数据变量在setup: block 但不能在其余任何条件

  3. groovy语法不运行$符号在方法名称中

相关文章
相关标签/搜索