使用 Spock 的數據管道和表格提高測試覆蓋率和可讀性
一、簡介
Spock 是一個用於編寫測試的優秀框架,特別是在增加測試覆蓋率方面。
在本教程中,我們將探索 Spock 的資料管道以及如何透過向資料管道添加額外的資料來提高行和分支程式碼覆蓋率。我們還將研究當數據變得太大時該怎麼辦。
2. 我們測試的主題
讓我們從一種將兩個數字相加但有所不同的方法開始。如果第一個或第二個數字是 42,則傳回 42:
public class DataPipesSubject {
int addWithATwist(final int first, final int second) {
if (first == 42 || second == 42) {
return 42;
}
return first + second;
}
}
我們想使用各種輸入組合來測試此方法。
讓我們看看如何編寫和改進一個簡單的測試來透過資料管道提供輸入。
3. 準備我們的數據驅動測試
讓我們建立一個測試類,對單一場景進行測試,然後在其基礎上進行建置以新增資料管道:
首先,讓我們使用測試主題來建立DataPipesTest
類別:
@Title("Test various ways of using data pipes")
class DataPipesTest extends Specification {
@Subject
def dataPipesSubject = new DataPipesSubject()
<br /> // ...<br />}
我們在類別周圍使用了 Spock 的@Title
註釋,為即將進行的測試提供一些額外的上下文。
我們也使用 Spock 的@Subject
註解來註解測試的主題。請注意,我們應該小心從spock.lang
而不是從javax.security.auth.
導入我們的Subject
。
儘管不是絕對必要的,但這種語法糖可以幫助我們快速識別正在測試的內容。
現在讓我們使用 Spock 的given
/ when
/ then
語法,使用前兩個輸入 1 和 2 建立測試:
def "given two numbers when we add them then our result is the sum of the inputs"() {
given: "some inputs"
def first = 1
def second = 2
and: "an expected result"
def expectedResult = 3
when: "we add them together"
def result = dataPipesSubject.addWithATwist(first, second)
then: "we get our expected answer"
result == expectedResult
}
為了準備資料管道的測試,讓我們將輸入從given/and
區塊移動到where
區塊:
def "given a where clause with our inputs when we add them then our result is the sum of the inputs"() {
when: "we add our inputs together"
def result = dataPipesSubject.addWithATwist(first, second)
then: "we get our expected answer"
result == expectedResult
where: "we have various inputs"
first = 1
second = 2
expectedResult = 3
}
Spock 評估where
區塊並隱式地將任何變數作為參數添加到測試中。所以,Spock 看到我們的方法宣告是這樣的:
def "given some declared method parameters when we add our inputs then those types are used"(int first, int second, int expectedResult)
請注意,當我們將資料強制轉換為特定類型時,我們將類型和變數宣告為方法參數。
由於我們的測試非常簡單,因此讓我們將when
和then
區塊壓縮為單一expect
區塊:
def "given an expect block to simplify our test when we add our inputs then our result is the sum of the two numbers"() {
expect: "our addition to get the right result"
dataPipesSubject.addWithATwist(first, second) == expectedResult
where: "we have various inputs"
first = 1
second = 2
expectedResult = 3
}
現在我們已經簡化了測試,我們準備添加第一個資料管道。
4.什麼是資料管道?
Spock 中的資料管道是一種將不同的資料組合輸入到我們的測試中的方法。當我們要考慮多個場景時,這有助於保持測試程式碼的可讀性。
管道可以是任何Iterable
- 如果它實現Iterable
接口,我們甚至可以創建自己的管道!
4.1.簡單的數據管道
由於數組是Iterable
,所以我們首先將單一輸入轉換為數組,並使用資料管道 '<<' 將它們輸入到我們的測試中:
where: "we have various inputs"
first << [1]
second << [2]
expectedResult << [3]
我們可以透過向每個數組資料管道添加條目來添加額外的測試案例。
因此,讓我們為場景 2 + 2 = 4 和 3 + 5 = 8 的管道添加一些資料:
first << [1, 2, 3]
second << [2, 2, 5]
expectedResult << [3, 4, 8]
為了使我們的測試更具可讀性,讓我們將first
和second
輸入組合到一個多變量數組資料管道中,暫時將expectedResult
分開:
where: "we have various inputs"
[first, second] << [
[1, 2],
[2, 2],
[3, 5]
]
and: "an expected result"
expectedResult << [3, 4, 8]
由於我們可以引用已經定義的提要,因此我們可以將預期結果資料管道替換為以下內容:
expectedResult = first + second
但讓我們將它與我們的輸入管道結合起來,因為我們正在測試的方法有一些微妙之處,會破壞簡單的添加:
[first, second, expectedResult] << [
[1, 2, 3],
[2, 2, 4],
[3, 5, 8]
]
4.2.地圖和方法
當我們想要更大的靈活性並且使用 Spock 2.2 或更高版本時,我們可以使用Map
作為資料管道來提供資料:
where: "we have various inputs in the form of a map"
[first, second, expectedResult] << [
[
first : 1,
second: 2,
expectedResult: 3
],
[
first : 2,
second: 2,
expectedResult: 4
]
]
我們也可以透過單獨的方法傳入資料。
[first, second, expectedResult] << dataFeed()
讓我們看看當我們將地圖資料管道移至dataFeed
方法時會是什麼樣子:
def dataFeed() {
[
[
first : 1,
second: 2,
expectedResult: 3
],
[
first : 2,
second: 2,
expectedResult: 4
]
]
}
儘管這種方法有效,但使用多個輸入仍然感覺很笨拙。讓我們看看 Spock 的數據表如何改進這一點。
5. 數據表
Spock 的資料表格式採用一個或多個資料管道,使它們在視覺上更具吸引力。
讓我們重寫測試方法中的where
區塊以使用資料表而不是資料管道的集合:
where: "we have various inputs"
first | second || expectedResult
1 | 2 || 3
2 | 2 || 4
3 | 5 || 8
現在,每一行都包含特定場景的輸入和預期結果,這使得我們的測試場景更易於閱讀。
作為視覺提示和最佳實踐,我們使用了雙重“||”將我們的輸入與預期結果分開。
當我們使用這三個迭代的程式碼覆蓋率來執行測試時,我們發現並非所有執行行都被覆寫。當任一輸入為 42 時,我們的addWithATwist
方法有一個特殊情況:
if (first == 42 || second == 42) {
return 42;
}
因此,讓我們新增一個場景,其中first
輸入是 42,確保我們的程式碼執行if
語句內的行。我們還添加一個場景,其中second
輸入是 42,以確保我們的測試覆蓋所有執行分支:
42 | 10 || 42
1 | 42 || 42
所以這是我們最後的where
區塊,其中包含迭代,給出了我們的程式碼行和分支覆蓋範圍:
where: "we have various inputs"
first | second || expectedResult
1 | 2 || 3
2 | 2 || 4
3 | 5 || 8
42 | 10 || 42
1 | 42 || 42
當我們執行這些測試時,我們的測試運行器會為每次迭代渲染一行:
DataPipesTest
- use table to supply the inputs
- use table to supply the inputs [first: 1, second: 2, expectedResult: 3, #0]
- use table to supply the inputs [first: 2, second: 2, expectedResult: 4, #1]
...
6. 可讀性改進
我們有一些技術可以用來使我們的測試更具可讀性。
6.1.將變數插入我們的方法名稱中
當我們想要更具表現力的測試執行時,我們可以為方法名稱添加變數。
因此,讓我們透過插入表中的列標題變數(以「#」為前綴)來增強測試的方法名稱,並新增場景列:
def "given a #scenario case when we add our inputs, #first and #second, then we get our expected result: #expectedResult"() {
expect: "our addition to get the right result"
dataPipesSubject.addWithATwist(first, second) == expectedResult
where: "we have various inputs"
scenario | first | second || expectedResult
"simple" | 1 | 2 || 3
"double 2" | 2 | 2 || 4
"special case" | 42 | 10 || 42
}
現在,當我們運行測試時,我們的測試運行器將輸出呈現為更具表現力:
DataPipesTest
- given a #scenario case when we add our inputs, #first and #second, then we get our expected result: #expectedResult
- given a simple case when we add our inputs, 1 and 2, then we get our expected result: 3
- given a double 2 case when we add our inputs, 2 and 2, then we get our expected result: 4
...
當我們使用此方法但輸入錯誤的資料管道名稱時,Spock 將導致測試失敗,並顯示類似以下內容的訊息:
Error in @Unroll, could not find a matching variable for expression: myWrongVariableName
和以前一樣,我們可以使用已經宣告的 feed 來引用表格資料中的 feed,即使在同一行中也是如此。
因此,讓我們新增一行引用列標題變數: first
和second
:
scenario | first | second || expectedResult
"double 2 referenced" | 2 | first || first + second
6.2.當表格列變得太寬時
我們的 IDE 可能包含對 Spock 表格的內在支援 - 我們可以使用 IntelliJ 的「格式代碼」功能( Ctrl+Alt+L)
來為我們對齊表格中的列!知道了這一點,我們就可以快速添加數據,而不必擔心隨後的佈局和格式化。
然而,有時,表中資料項目的長度會導致格式化的表行變得太寬而無法容納在一行中。通常,此時我們的輸入中有字串。
為了演示這一點,讓我們創建一個方法,該方法將字串作為輸入並簡單地添加一個感嘆號:
String addExclamation(final String first) {
return first + '!';
}
現在讓我們建立一個使用長字串作為輸入的測試:
def "given long strings when our tables our too big then we can use shared or static variables to shorten the table"() {
expect: "our addition to get the right result"
dataPipesSubject.addExclamation(longString) == expectedResult
where: "we have various inputs"
longString || expectedResult
'When we have a very long string we can use a static or @Shared variable to make our tables easier to read' || 'When we have a very long string we can use a static or @Shared variable to make our tables easier to read!'
}
現在,讓我們透過用 static 或 @Shared 變數替換字串來使該表更加緊湊。請注意,我們的表不能使用測試中聲明的變數 -我們的表只能使用靜態、 @Shared
或計算值。
因此,讓我們聲明一個靜態共享變數並使用表中的變數:
static def STATIC_VARIABLE = 'When we have a very long string we can use a static variable'
@Shared
def SHARED_VARIABLE = 'When we have a very long string we can annotate our variable with @Shared'
<br />...<br />
scenario | longString || expectedResult
'use of static' | STATIC_VARIABLE || $STATIC_VARIABLE + '!'
'use of @Shared' | SHARED_VARIABLE || "$SHARED_VARIABLE!"
現在我們的桌子更加緊湊了!我們還使用 Groovy 的String
插值在@Shared
行中建立預期結果,以展示這如何有助於提高可讀性。
使大表更具可讀性的另一種方法是使用兩個或多個下劃線 '__' 將表拆分為多個部分:
where: "we have various inputs"
first | second
1 | 2
2 | 3
3 | 5
__
expectedResult | _
3 | _
5 | _
8 | _
當然,我們需要在拆分錶中具有相同的行數。
Spock 表必須至少有兩列,但是在我們拆分錶之後, expectedResult
將獨立存在,因此我們新增了一個空的「_」列來滿足此要求。
6.3.替代表分隔符
有時,我們可能不想使用“|”作為分隔符號。在這種情況下,我們可以使用“;”反而:
first ; second ;; expectedResult
1 ; 2 ;; 3
2 ; 3 ;; 5
3 ; 5 ;; 8
但我們不能混合搭配兩個“|”和 ';'同一個表格中的列分隔符號!
七、結論
在本文中,我們學習如何在where
區塊中使用 Spock 的資料來源.
我們了解了資料表如何在視覺上更好地表示資料來源,以及如何透過簡單地在資料表中添加一行資料來提高測試覆蓋率。我們也探索了一些讓資料更具可讀性的方法,特別是在處理大數據值或表格變得太大時。
與往常一樣,本文的來源可以在 GitHub 上找到。