Data Driven Testing 源文档地址

测试的时候,我们经常需要用不同的输入数据对同一段代码执行很多次,也会有不同的预期返回结果。Spock的数据驱动测试功能使之成为最重要的功能之一。

入门

举个例子,如果我们要测试 Math.max 方法的功能

  1. class MathSpec extends Specification {
  2. def "maximum of two numbers"() {
  3. expect:
  4. // exercise math method for a few different inputs
  5. Math.max(1, 3) == 3
  6. Math.max(7, 4) == 7
  7. Math.max(0, 0) == 0
  8. }
  9. }

这段代码的问题是

  1. 代码和数据是捆在一起的,不能独立对数据进行修改
  2. 数据不能自动生成,或者从外部导入
  3. 为了执行不同的输入,需要重复写这段代码
  4. 在失败的场景下,无法立刻指出失败的是哪个输入Case
  5. 在一个特性方法内的多次调用,没有做到代码的上下文隔离

其实这些问题的反面,就是测试驱动开发所想要达成的事情。

接下来我们就将上述的代码重构成 Spock 测试驱动开发风格的特性方法:

  1. class MathSpec extends Specification {
  2. def "maximum of two numbers"(int a, int b, int c) {
  3. expect:
  4. Math.max(a, b) == c
  5. ...
  6. }
  7. }

上面的代码就是我们的测试逻辑,完全不包含数据。所需要的数据我们放在 where 代码块里的数据表中


数据表

数据表给所在的特性方法提供了一组变量数据,使用起来非常方便

  1. class MathSpec extends Specification {
  2. def "maximum of two numbers"(int a, int b, int c) {
  3. expect:
  4. Math.max(a, b) == c
  5. where:
  6. a | b | c
  7. 1 | 3 | 3
  8. 7 | 4 | 7
  9. 0 | 0 | 0
  10. }
  11. }

where代码块中的第一行是表头,声明了变量。下面每一行,就是特性方法每执行一次,所需要的一组具体的数据。可以理解为每一行都是一个独立的Case。如果某一个Case失败了,也会把所有的行执行完,并报告所有的错误。


迭代间的数据隔离

一行数据的执行,我们称之为一次迭代(Iteration)。每一个迭代都是相互隔离的,都有自己 Specification 上下文,setup()cleanup() 也会在每个迭代中重新执行


迭代间的数据共享

如果要在不同的迭代中共享对象,那么就需要在变量上加上@Share注解

只有静态变量和共享变量才能出现在 where 模块中


变量表的语法改进

  1. class MathSpec extends Specification {
  2. def "maximum of two numbers"() {
  3. expect:
  4. Math.max(a, b) == c
  5. where:
  6. a | b || c
  7. 1 | 3 || 3
  8. 7 | 4 || 7
  9. 0 | 0 || 0
  10. }
  11. }
  1. 方法入参可以去掉:where的表头行就完成了变量声明
  2. || 代表代表返回列

错误报告

我们假设 Math.max 的实现有bug,那么Spock就会返回错误报告:

  1. maximum of two numbers FAILED
  2. Condition not satisfied:
  3. Math.max(a, b) == c
  4. | | | | |
  5. | 7 4 | 7
  6. 42 false

这里的问题是,报告中没有反映出是哪一行的数据触发了这个错误。@Unroll注解可以解决这个问题


Unrolling注解

@Unroll 注解可以让每个迭代都单独出自己错误报告

  1. @Unroll
  2. def "maximum of two numbers"() {
  3. ...
  4. }

如果我们有三个迭代,当只有第二个迭代报错的时候,错误报告就会长这样:

  1. maximum of two numbers[0] PASSED
  2. maximum of two numbers[1] FAILED
  3. Math.max(a, b) == c
  4. | | | | |
  5. | 7 4 | 7
  6. 42 false
  7. maximum of two numbers[2] PASSED

数据管道

where 代码块中的数据表其实就是数据管道的语法糖:

  1. where:
  2. a << [1, 7, 0]
  3. b << [3, 4, 0]
  4. c << [3, 7, 0]

数据管道用的是 <<操作符,左边是变量,右边是数据源。
每一列数据代表的时候一个迭代


返回多个变量的数据管道

如果数据管道操作符右边的数据源,在每个迭代会返回多个数据,可以把这些数据分别赋到多个变量上。

  1. @Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
  2. def "maximum of two numbers"() {
  3. expect:
  4. Math.max(a, b) == c
  5. where:
  6. [a, b, c] << sql.rows("select a, b, c from maxdata")
  7. }

需要跳过的数据用 下划线 代替

  1. where:
  2. [a, b, _, c] << sql.rows("select * from maxdata")

对变量直接赋值

也可以对变量直接赋值,

  1. ...
  2. where:
  3. a = 3
  4. b = Math.random() * 100
  5. c = a > b ? a : b

数据表,数据管道,变量赋值混用

  1. ...
  2. where:
  3. a | _
  4. 3 | _
  5. 7 | _
  6. 0 | _
  7. b << [5, 0, 0]
  8. c = a > b ? a : b

迭代的次数

迭代的次数依赖于spock可以拿到多少数据。如下面的例子,在第三次迭代的时候,c 变量的值取不到了,那Spock就会报错

  1. where:
  2. a << [1, 7, 0]
  3. b << [3, 4, 0]
  4. c << [3, 7]

以上是数据驱动测试这一章的主干内容