一般,執行相同的測試代碼屢次是有用的,在不一樣的輸入與預期結果。spock數據驅動測試支持使他成爲第一類級別特性sql
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 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 | _
表第一行,稱爲數據頭,定義了變量。子行,稱做數據行,持有相應的數據。對每一行,特性方法都會被執行一次。咱們稱做迭代的方法。若是一個迭代方法失敗了,意味着其餘迭代仍然被執行。全部的
失敗都會被報告。編碼
迭代式獨立的從互相間相同的分割特性方法。每一個迭代獲取本身實例從spec類。並setup cleanup會被分別調用在每一個迭代執行先後。code
共享對象在迭代間
順序的共享一個對象在迭代間,它使用@Shared 或者靜態字段對象
提示
只有@Shared 和靜態字段 能被訪問在where:block裏面。
注意,這些對象也能被共享給其餘方法。不是一個好的方式共享對象在相同的方法迭代。若是你思考這個問題,思考放入每一個方法在隔離的spec,全部能被處理在相同的文件。這個實現更好的隔離在一些樣板代碼的成本。索引
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 已經定義全部的數據變量,這個方法參數能被提交。其次,輸入與期待輸出能被分離使用雙線去虛擬設置分離。使用它,代碼變成這樣。
讓咱們設想咱們實現max方法有一個錯誤,其中一個迭代失敗了。
兩個數字中最大的數字 失敗
明顯的問題是:迭代失敗,數據值時什麼。在咱們的例子裏,它很是難指出是第二個迭代失敗。在其餘時候多是更加困難甚至是不可能。在任何用例裏,若是spock能大聲並清晰哪次失敗是很是好的,
超過只是報告失敗。這是@Unroll註解的目標
一個方法註釋使用@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 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 實現了迭代器約定的對象。數據提供者不須要必須有數據。他們能獲取數據從外部數據源如文本文件,數據庫,電子表格,隨即生成數據。數據提供者被查詢
只在須要時獲取下一個值。
@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")
一個數據變量唄直接分配一個值
...
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
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
迭代間的數量依賴多少數據時可變的。連續執行相同的方法能產生不一樣數量的迭代。若是一個數據提供者運行出值比他的同行快,一個異常將產生。多個變量分配不能影響迭代數量。一個where:block只包含分配確切產生一個迭代。
After all iterations have completed, the zero-argument close method is called on all data providers that have such a method.
全部迭代完成後,沒有參數的關閉方法被調用在全部數據提供者有如此的方法
一分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] }
想法背後運行方法參數更好的被IDE支持。而後 最新版本的IntelliJ IDEA 自動認出數據變量,甚至從數據表的值推斷出他們的類型
例如,一個特性方法能使用數據變量在setup: block 但不能在其餘任何條件
groovy語法不運行$符號在方法名稱中