测试的时候,我们经常需要用不同的输入数据对同一段代码执行很多次,也会有不同的预期返回结果。Spock的数据驱动测试功能使之成为最重要的功能之一。
入门
举个例子,如果我们要测试 Math.max 方法的功能
class MathSpec extends Specification {def "maximum of two numbers"() {expect:// exercise math method for a few different inputsMath.max(1, 3) == 3Math.max(7, 4) == 7Math.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) == cwhere:a | b | c1 | 3 | 37 | 4 | 70 | 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) == cwhere:a | b || c1 | 3 || 37 | 4 || 70 | 0 || 0}}
- 方法入参可以去掉:
where的表头行就完成了变量声明 ||代表代表返回列
错误报告
我们假设 Math.max 的实现有bug,那么Spock就会返回错误报告:
maximum of two numbers FAILEDCondition not satisfied:Math.max(a, b) == c| | | | || 7 4 | 742 false
这里的问题是,报告中没有反映出是哪一行的数据触发了这个错误。@Unroll注解可以解决这个问题
Unrolling注解
@Unroll 注解可以让每个迭代都单独出自己错误报告
@Unrolldef "maximum of two numbers"() {...}
如果我们有三个迭代,当只有第二个迭代报错的时候,错误报告就会长这样:
maximum of two numbers[0] PASSEDmaximum of two numbers[1] FAILEDMath.max(a, b) == c| | | | || 7 4 | 742 falsemaximum 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) == cwhere:[a, b, c] << sql.rows("select a, b, c from maxdata")}
需要跳过的数据用 下划线 代替
where:[a, b, _, c] << sql.rows("select * from maxdata")
对变量直接赋值
也可以对变量直接赋值,
...where:a = 3b = Math.random() * 100c = 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]
以上是数据驱动测试这一章的主干内容
