如何使用 Spock 测试 Static Method
Spock 简易使用说明
使用 Spock 来开发测试程式除了在之前的文章中提到的:可辅助 BDD 的开发流程、与 JUnit 相容及内建 Data Driven 功能等特性之外,还有另外一项优点是内建了 Mock 的功能。Spock 所提供的 Mock 功能在使用上相当的简洁,以下是一段范例的源代码:
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 应该像以下所示范的内容:
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 的运行过程中,有调用到这二个目标函式时所会取得的内容。
如何 Mock 静态方法
虽然 Spock 提供了许多优异的特性,不过,美中不足的地方是目前的版本没有办法 Mock 静态方法。在网路上看了一些讨论之后,PowerMock 似乎是一个可用来搭配 Spock 解决这个问题的框架。进一步了解发现 PowerMock 所提供的套件种类繁多,包含了与几个主流的测试框架整合的功能。而如何在 Gradle 中设定对应的套件,可以让 Spock 与 PowerMock 协同运作会是一个门槛。
基本上要在 Spock 中使用 PowerMock 来 Mock 静态方法,会用到 JUnit、Mockito 和 PowerMock 这几个框架,这使得在设定 build.gradle 内容时更加的棘手。而在测试程式中要把这四个框架整合起来,以便能够达到 Mock 静态方法的目的,也是很费心神的一件事。
目前网络上查到的资讯都是片断的,所以在这里把相关的资讯统整起来,做为日后研究的起点。以下是 build.gradle 的内容:
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:
public class TestClass {
public static String staticMethod() {
return null;
}
}
测试程式应该像以下所示范的内容来进行静态方法的测试:
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() == "测试用字串"
}
}