Skip to content

Latest commit

 

History

History
274 lines (173 loc) · 9.31 KB

data_driven_testing_cn.md

File metadata and controls

274 lines (173 loc) · 9.31 KB

###Data Driven Testing 通常,执行相同的测试代码多次是有用的,在不同的输入与预期结果。spock数据驱动测试支持使他成为第一类级别特性

###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 值。

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持有数据表。

###Data Tables Data tables are a convenient way to exercise a feature method with a fixed set of data values:

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会被分别调用在每个迭代执行前后。

共享对象在迭代间 顺序的共享一个对象在迭代间,它使用@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语法不运行$符号在方法名称中