有一个讨论here关于测试和单例……但那是关于 Java 模式的。
我的问题具体是关于实现此模式的 Groovy @Singleton
(注释)方式。
这似乎是 Groovy Goodness 的又一亮点。但是在使用具有此注释的类进行测试(使用 Spock)时,我遇到了一些问题。
如果此实例的任何状态在测试期间发生变化(从原始的、刚刚构建的状态),据我的实验表明,这将继续进行下一个测试...我测试了 MySingletonClass .instance
的 hashCode()
进行了几次测试,结果都一样。也许这并不奇怪。
但是……如果 Spock 可以(使用我只能推测的那种 uber-Groovy 魔法)以某种方式在测试之间重置类,那不是更好吗? IE。通过创建一个新实例?
有一个明显的解决方法:将 reset
方法合并到每个 @Singleton
类中,在测试期间其状态可能会发生变化。然后在 setup()
中调用那个 reset
方法……事实上我使用了一个通用的 Specification
子类,CommonProjectSpec
,我所有真正的 Specification
的子类...因此实现起来很简单。
但是好像有点不雅。还有其他选择吗?我是否应该将其作为 Spock 建议的增强功能提交?
PS 事实证明您不能再制作此类的 Spy
(或 GroovySpy
)。但是你可以制作一个 Mock
:
ConsoleHandler mockCH = Mock( ConsoleHandler ){
getDriver() >> ltfm
}
GroovyMock( ConsoleHandler, global: true )
ConsoleHandler.instance = mockCH
... 是的,这里的“全局”GroovyMock
实际上有能力“驯服”静态instance
字段,以便它温顺地接受一个Mock
布谷鸟在巢中。
最佳答案
所以基本上你想测试单例不是单例。这让我觉得很奇怪。但无论如何,我将这个问题视为一个难题,我将为其本身解决这个难题,因为这是一个很好的挑战。 (不要在家里这样做, children !)
Groovy 单例:
package de.scrum_master.stackoverflow
@Singleton
class Highlander {
def count = 0
def fight() {
println "There can be only one!"
count++
doSomething()
}
def doSomething() {
println "Doing something"
}
}
单例辅助类:
package de.scrum_master.stackoverflow
import java.lang.reflect.Field
import java.lang.reflect.Modifier
class GroovySingletonTool<T> {
private Class<T> clazz
GroovySingletonTool(Class<T> clazz) {
this.clazz = clazz
}
void setSingleton(T instance) {
// Make 'instance' field non-final
Field field = clazz.getDeclaredField("instance")
field.modifiers &= ~Modifier.FINAL
// Only works if singleton instance was unset before
field.set(clazz.instance, instance)
}
void unsetSingleton() {
setSingleton(null)
}
void reinitialiseSingleton() {
// Unset singleton instance, otherwise subsequent constructor call will fail
unsetSingleton()
setSingleton(clazz.newInstance())
}
}
Spock 测试:
这个测试展示了如何
- 在特征方法执行之前重新实例化一个 Groovy 单例
- 对 Groovy 单例使用
Stub()
- 为 Groovy 单例使用
Mock()
- 对 Groovy 单例使用
Spy()
(需要 Objenesis)
package de.scrum_master.stackoverflow
import org.junit.Rule
import org.junit.rules.TestName
import spock.lang.Specification
import spock.lang.Unroll
class HighlanderTest extends Specification {
def singletonTool = new GroovySingletonTool<Highlander>(Highlander)
@Rule
TestName gebReportingSpecTestName
def setup() {
println "\n--- $gebReportingSpecTestName.methodName ---"
}
@Unroll
def "Highlander fight no. #fightNo"() {
given:
singletonTool.reinitialiseSingleton()
def highlander = Highlander.instance
when:
highlander.fight()
then:
highlander.count == 1
where:
fightNo << [1, 2, 3]
}
@Unroll
def "Highlander stub fight no. #fightNo"() {
given:
Highlander highlanderStub = Stub() {
fight() >> { println "I am a stub" }
}
singletonTool.setSingleton(highlanderStub)
def highlander = Highlander.instance
when:
highlander.fight()
then:
highlander == highlanderStub
where:
fightNo << [1, 2, 3]
}
@Unroll
def "Highlander mock fight no. #fightNo"() {
given:
Highlander highlanderMock = Mock() {
fight() >> { println "I am just mocking you" }
}
singletonTool.setSingleton(highlanderMock)
def highlander = Highlander.instance
when:
highlander.fight()
then:
highlander == highlanderMock
0 * highlander.doSomething()
where:
fightNo << [1, 2, 3]
}
@Unroll
def "Highlander spy fight no. #fightNo"() {
given:
// Unset not necessary because Objenesis creates object without constructor call
// singletonTool.unsetSingleton()
Highlander highlanderSpy = Spy(useObjenesis: true)
// Spy's member is not initialised by Objenesis
highlanderSpy.count = 0
singletonTool.setSingleton(highlanderSpy)
def highlander = Highlander.instance
when:
highlander.fight()
then:
highlander == highlanderSpy
highlander.count == 1
1 * highlander.doSomething() >> { println "I spy" }
where:
fightNo << [1, 2, 3]
}
}
控制台日志:
--- Highlander fight no. 1 ---
There can be only one!
Doing something
--- Highlander fight no. 2 ---
There can be only one!
Doing something
--- Highlander fight no. 3 ---
There can be only one!
Doing something
--- Highlander stub fight no. 1 ---
I am a stub
--- Highlander stub fight no. 2 ---
I am a stub
--- Highlander stub fight no. 3 ---
I am a stub
--- Highlander mock fight no. 1 ---
I am just mocking you
--- Highlander mock fight no. 2 ---
I am just mocking you
--- Highlander mock fight no. 3 ---
I am just mocking you
--- Highlander spy fight no. 1 ---
There can be only one!
I spy
--- Highlander spy fight no. 2 ---
There can be only one!
I spy
--- Highlander spy fight no. 3 ---
There can be only one!
I spy
关于testing - Groovy Singleton 和测试问题(与 Spock),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49602657/