This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def "Spock 範例測試程式"() { | |
given: | |
def mockClass = Mock(ConcreteClass) | |
def mockInterface1 = Mock(IInjection) | |
IInjection mockInterface2 = Mock() | |
Processor tester = new Processor(mockClass) | |
mockInterface2.getInfo() >> { "二號測試字串" } | |
when: | |
tester.run(mockInterface1) | |
then: | |
1 * mockClass.start() | |
1 * mockInterface1.getInfo() >> { "一號測試字串" } | |
_ * mockClass.process(_) >> { mockInterface2 } | |
1 * mockInterface2.setData(_) | |
1 * mockClass.end() | |
} |
由以上的程式碼可以看到只要使用 Mock() 函式就可以對指定的對象進行 Mock 的程序,同時也提供二種彈性的宣告方式,一是把要 Mock 的對象傳入 Mock() 函式(第 3, 4 行),另一個方式是使用 Mock() 函式來建立執行個體(第 5 行)。第二種 Java-like 的語法主要是希望在編輯程式碼時 IDE 可以提供較好的支援,執行時會以宣告的型別來判定要產生的 Mock 對象之 Instance。
Spock 所提供的 Mock 功能有另一個亮點是不僅能夠針對 Interface,同時也可以使用在一般的 Class 上。如此在進行系統設計時,就可以不用遷就「是否有辦法分開測試」這個問題而要使用大量的 IoC 在設計之中,以致形成了過度設計的情況。在 Mock Class 前,專案必須要引用 cglib-nodep 和 objenesis 這二個函式庫,所以在 build.gradle 的 dependencies 應該像以下所示範的內容:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dependencies { | |
testCompile 'org.codehaus.groovy:groovy-all:2.4.4' | |
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' | |
testCompile 'org.objenesis:objenesis:2.2' | |
testCompile 'cglib:cglib-nodep:3.1' | |
} |
根據官方文件的說明,如果要驗證目標 Class 與其週邊 Object 互動的過程是否符合像是循序圖所表達的設計內容,可以使用範例程式中所示範之內容。使用的語法規格是:[互動的次數] * [互動的標的],1 * mockClass.start() 就是驗證 Processor.run 是否有呼叫 ConcreteClass.start(),而且整個 Processor.run 的執行過程中只呼叫一次。
從範例程式中還可以看到,Spock 所提供的 Mock 功能,除了可以用來驗證 Class 間互動過程,同時也具有 Stub 的能力。可以依據呼叫的內容來提供對應的動作,增加了測試程式的靈活性。像範例程式中的第 8 行,當有程式呼中 mockInterface2.getInfo() 時,就會固定傳回 "二號測試字串" 的內容。而在第 15, 16 行,是代表當 Processor.run 的執行過程中,有呼叫到這二個目標函式時所會取得的內容。
雖然 Spock 提供了許多優異的特性,不過,美中不足的地方是目前的版本沒有辦法 Mock 靜態函式。在網路上看了一些討論之後,PowerMock 似乎是一個可用來搭配 Spock 解決這個問題的框架。進一步了解發現 PowerMock 所提供的套件種類繁多,包含了與幾個主流的測試框架整合的功能。而如何在 Gradle 中設定對應的套件,可以讓 Spock 與 PowerMock 協同運作會是一個門檻。
基本上要在 Spock 中使用 PowerMock 來 Mock 靜態函式,會用到 JUnit、Mockito 和 PowerMock 這幾個框架,這使得在設定 build.gradle 內容時更加的棘手。而在測試程式中要把這四個框架整合起來,以便能夠達到 Mock 靜態函式的目的,也是很費心神的一件事。
目前網路上查到的資訊都是片斷的,所以在這裡把相關的資訊統整起來,做為日後研究的起點。以下是 build.gradle 的內容:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
apply plugin: 'java' | |
apply plugin: 'groovy' | |
dependencies { | |
testCompile 'org.codehaus.groovy:groovy-all:2.4.4' | |
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' | |
testCompile 'org.objenesis:objenesis:2.2' | |
testCompile 'cglib:cglib-nodep:3.1' | |
testCompile 'org.mockito:mockito-core:1.10.19' | |
testCompile 'org.powermock:powermock-api-mockito:1.6.2' | |
testCompile 'org.powermock:powermock-module-junit4:1.6.2' | |
testCompile 'org.powermock:powermock-module-junit4-rule:1.6.2' | |
testCompile 'org.powermock:powermock-classloading-xstream:1.6.2' | |
} |
以上的套件應該要依照所屬框架更新的狀況來調整對應的版本編號。
在測試程式中,如果有一個以下待測的 Class:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class TestClass { | |
public static String staticMethod() { | |
return null; | |
} | |
} |
測試程式應該像以下所示範的內容來進行靜態函式的測試:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.Rule | |
import org.mockito.Mockito | |
import org.powermock.api.mockito.PowerMockito | |
import org.powermock.core.classloader.annotations.PrepareForTest | |
import org.powermock.modules.junit4.rule.PowerMockRule | |
import spock.lang.Specification | |
@PrepareForTest([TestClass.class]) | |
class MockStaticMethodSpec extends Specification { | |
@Rule | |
PowerMockRule mPowerMockRule = new PowerMockRule(); | |
def "測試靜態方法"() { | |
setup : | |
PowerMockito.mockStatic(TestClass.class) | |
when : | |
Mockito.when(TestClass.staticMethod()).thenReturn("測試用字串") | |
then : | |
TestClass.staticMethod() == "測試用字串" | |
} | |
} |
0 意見:
張貼留言