在上一篇中,我們介紹了什麼是Spock以及它和JUnit,JMock等其它測試架構的對比,從這一篇開始,我們将開始詳細介紹Spock的使用。
在這一章,我們首先講解如何在項目中引入Spock,以及Spock的一些核心概念介紹,并且抛磚引玉給出一個簡單的單測示例。
引入Spock
- 環境
針對Java單元測試,開發工具 IntelliJ IDEA。
- Maven依賴
<!--
可以隻引入這一個包,它已經引入了 spock-core、groovy-all; 如果有沖突可以手動指定版本引入
此包是整合springboot使用,用于在spock中啟動springboot容器,
如果僅想使用spock作為單測使用,可以不引入目前包,隻引入 spock-core、groovy-all 即可
-->
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.3-groovy-2.4</version>
</dependency>
<!-- 可以不引入 -->
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.3-groovy-2.4</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.4.17</version>
</dependency>
- 插件配置
在單測用例所在module的 POM檔案添加plugin的配置,多module的情況下放主pom不生效
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<skipTests>false</skipTests>
<parallel>classes</parallel>
<threadCount>4</threadCount>
<forkCount>1C</forkCount>
<includes>
<include>**/*Test.java</include>
<include>**/*Spec.java</include>
</includes>
</configuration>
</plugin>
<plugin>
<!--groovy plugin-->
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.4</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- spock單測檔案路徑 -->
<testSources>
<testSource>
<directory>${project.basedir}/src/test/java</directory>
<includes>
<include>**/*.groovy</include>
</includes>
</testSource>
<testSource>
<directory>${project.basedir}/src/test/groovy</directory>
<includes>
<include>**/*.groovy</include>
</includes>
</testSource>
</testSources>
</configuration>
</plugin>
- 引入過程中的注意點
問題1:java.lang.NoClassDefFoundError: org/codehaus/groovy/transform/stc/AbstractExtensionMethodCache
解決:将groovy 和 groovy-all 都保持2.4.17版本
問題2:配置了gmavenplus-plugin并指定了路徑但不生效
配置在主pom中,但單測在B子產品中導緻,将插件配置到A或B子產品中
主pom
|----A
|----B
問題3:引入groovy依賴後可能會出現版本沖突的問題
因為如果你的項目引用了springboot-start-base這樣的集合式jar包,它裡面也會引用groovy,有可能跟我們引入的groovy包版本出現沖突,或者公司的一些架構也會引用groovy的包,如果版本不一緻也有可能沖突,需要排下包。
這裡推薦idea的maven插件 MavenHelper 能可視化快速排查依賴問題。
MavenHelper依賴分析
Spock核心概念
- Spock标簽
标簽 | 作用 | 備注 |
given | 為所測試方法做一些前置準備工作的地方 | 一般将方法内部依賴的參數在這個标簽内完成 |
when | 執行測試 | when 和 then 必須成對出現 |
then | 驗證測試結果是否符合預期 | when 和 then 必須成對出現 |
expect | 精簡版when+then | expect裡代碼隻可以包含布爾表達式 |
and | 承接上一個标簽 | |
where | 用于編寫資料驅動的用例方法 | 總是在方法的最後,且不能重複 |
cleanup | 測試方法退出前執行一些清理工作 | 一個 cleanup 後僅僅隻能跟一個where ,即使前面發生了異常cleanup也會運作(類似于Java的finally) |
- Mock、Stub和Spy差別
方法 | 描述 |
Mock() | Mock的對象是一個虛拟類,為每個方法調用傳回了一個預設值,用于替換真實的類。Mock()不僅可以模拟方法傳回結果,還可以模拟方法行為,比如驗證某個方法是否被調用以及調用次數 |
Stub() | Stub的對象也是一個虛拟類,比Mock()更簡單些。隻傳回事先準備好的測試資料,而不提供互動驗證 |
Spy() | 又叫刺探方法,它會包裝一個真實的對象,預設情況下将調用真實的方法。Spy()功能最強大,但這樣做意味着代碼可能有壞味道,設計上可能有問題。 |
Spock單測示例
待測試類:
public class Square {
private final int length;
public Square(int length) {
this.length = length;
}
public int area() {
return length * length;
}
public int perimeter() {
return length * 4;
}
}
測試類:
class SquareTest extends Specification {
@Unroll
def "given square length: #length, square area: #area, square perimeter: #perimeter"() {
given:
def square = new Square(
length: length
)
expect:
square.area() == area
square.perimeter() == perimeter
where:
length || area | perimeter
1 || 1 | 4
2 || 4 | 8
}
}
def 是groovy的關鍵字,可以用來定義變量和方法名,這裡可以用英文也可以用中文。
expect...where... expect為核心的測試校驗語句塊,where則是多個測試用例的舉例,多個列使用"|"單豎線隔開,"||"雙豎線區分輸入和輸出變量,即左邊是輸入值,右邊是輸出值。表格列的長度不一樣,手動對齊比較麻煩,intellij idea支援format格式化快捷鍵。
上面圖中定義的測試方法名使用了groovy的字面值特性,即把請求參數值和傳回結果值的字元串動态替換掉,"#length、#area、#perimeter" #号後面的變量是在方法内部定義的,前面加上#号,實作占位符的功能。
@Unroll 注解,可以把每一次調用作為一個單獨的測試用例運作,這樣運作後的單測結果更直覺:
如果其中一行測試結果錯誤,spock的錯誤提示資訊也很詳細,友善我們排查(比如我們把第2行測試用例傳回的perimeter改成4)
總結
在這篇文章中,我們引入了Spock,并對Spock的一些概念做了簡單介紹,最後給出一個簡單的測試示例,進一步加深對Spock的了解。下一篇章我們将會詳細的介紹如何通過Spock測試複雜的if-else場景。