testing - Groovy Singleton 和测试问题(与 Spock)

标签 testing groovy singleton spock

有一个讨论here关于测试和单例……但那是关于 Java 模式的。

我的问题具体是关于实现此模式的 Groovy @Singleton(注释)方式。

这似乎是 Groovy Goodness 的又一亮点。但是在使用具有此注释的类进行测试(使用 Spock)时,我遇到了一些问题。

如果此实例的任何状态在测试期间发生变化(从原始的、刚刚构建的状态),据我的实验表明,这将继续进行下一个测试...我测试了 MySingletonClass .instancehashCode() 进行了几次测试,结果都一样。也许这并不奇怪。

但是……如果 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/

相关文章:

testing - TFS : How do you control signoff of test cases

java - 测试 Web 服务消费者

unit-testing - 请解释单元测试

java - 基本 Groovy 脚本引擎设置

node.js - 在 AWS Codebuild 上运行时 Jest 不关闭()ing expressjs 服务器

grails - 不能对具有JSON对象的类使用不可变注释

git - 如何在 jenkins 中针对 master 分支运行 git diff-tree?

java - 如何在生产代码中考虑单例?

java 单例 boolean 表达式返回消息而不是 true 或 false

java - 如何创建单例类