测试的时候,我们经常需要用不同的输入数据对同一段代码执行很多次,也会有不同的预期返回结果。Spock的数据驱动测试功能使之成为最重要的功能之一。
入门
举个例子,如果我们要测试 Math.max
方法的功能
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
}
}
这段代码的问题是
- 代码和数据是捆在一起的,不能独立对数据进行修改
- 数据不能自动生成,或者从外部导入
- 为了执行不同的输入,需要重复写这段代码
- 在失败的场景下,无法立刻指出失败的是哪个输入Case
- 在一个特性方法内的多次调用,没有做到代码的上下文隔离
其实这些问题的反面,就是测试驱动开发所想要达成的事情。
接下来我们就将上述的代码重构成 Spock 测试驱动开发风格的特性方法:
class MathSpec extends Specification {
def "maximum of two numbers"(int a, int b, int c) {
expect:
Math.max(a, b) == c
...
}
}
上面的代码就是我们的测试逻辑,完全不包含数据。所需要的数据我们放在 where
代码块里的数据表中
数据表
数据表给所在的特性方法提供了一组变量数据,使用起来非常方便
class MathSpec 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 | 7
0 | 0 | 0
}
}
where
代码块中的第一行是表头,声明了变量。下面每一行,就是特性方法每执行一次,所需要的一组具体的数据。可以理解为每一行都是一个独立的Case。如果某一个Case失败了,也会把所有的行执行完,并报告所有的错误。
迭代间的数据隔离
一行数据的执行,我们称之为一次迭代(Iteration)。每一个迭代都是相互隔离的,都有自己 Specification 上下文,setup()
和 cleanup()
也会在每个迭代中重新执行
迭代间的数据共享
如果要在不同的迭代中共享对象,那么就需要在变量上加上@Share
注解
只有静态变量和共享变量才能出现在 where
模块中
变量表的语法改进
class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 7
0 | 0 || 0
}
}
- 方法入参可以去掉:
where
的表头行就完成了变量声明 ||
代表代表返回列
错误报告
我们假设 Math.max
的实现有bug,那么Spock就会返回错误报告:
maximum of two numbers FAILED
Condition not satisfied:
Math.max(a, b) == c
| | | | |
| 7 4 | 7
42 false
这里的问题是,报告中没有反映出是哪一行的数据触发了这个错误。@Unroll
注解可以解决这个问题
Unrolling注解
@Unroll
注解可以让每个迭代都单独出自己错误报告
@Unroll
def "maximum of two numbers"() {
...
}
如果我们有三个迭代,当只有第二个迭代报错的时候,错误报告就会长这样:
maximum of two numbers[0] PASSED
maximum of two numbers[1] FAILED
Math.max(a, b) == c
| | | | |
| 7 4 | 7
42 false
maximum of two numbers[2] PASSED
数据管道
where
代码块中的数据表其实就是数据管道的语法糖:
where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]
数据管道用的是 <<
操作符,左边是变量,右边是数据源。
每一列数据代表的时候一个迭代
返回多个变量的数据管道
如果数据管道操作符右边的数据源,在每个迭代会返回多个数据,可以把这些数据分别赋到多个变量上。
@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
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:
a | _
3 | _
7 | _
0 | _
b << [5, 0, 0]
c = a > b ? a : b
迭代的次数
迭代的次数依赖于spock可以拿到多少数据。如下面的例子,在第三次迭代的时候,c 变量的值取不到了,那Spock就会报错
where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7]
以上是数据驱动测试这一章的主干内容