Groovy Singleton和测试问题(与Spock一起使用)

mik*_*ent 2 testing groovy singleton spock

有商榷这里有关测试和单身......但是这是关于Java模式。

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

这似乎是Groovy Goodness的另一点。但是,在使用具有此批注的类进行测试(使用Spock)时,我有一个问题。

如果测试(从原始的,只是构建的状态)在任何的这个实例的状态发生变化的,据我的实验表明,那么这将通过进行下一个测试...我测试MySingletonClass.instancehashCode()几个测试和他们都回来了一样。也许这并不奇怪。

但是...如果Spock能够(使用我只能推测的那种超级格罗维魔术)以某种方式重置测试之间的类,那会更好吗?即通过创建新实例?

有一个明显的解决方法:将一个reset方法合并到每个@Singleton类中,在测试期间其状态可能会更改。然后resetsetup()...中调用该方法,实际上我使用了一个公共Specification子类CommonProjectSpec,从中可以得到我所有的Specifications子类...这样就很容易实现。

但这似乎有点不雅。还有其他选择吗?我应该将其作为Spock建议的增强功能提交吗?

PS还可以证明您不能再创建Spy此类(或GroovySpy)。但是您可以使用Mock它:

    ConsoleHandler mockCH = Mock( ConsoleHandler ){
        getDriver() >> ltfm
    }
    GroovyMock( ConsoleHandler, global: true )
    ConsoleHandler.instance = mockCH
Run Code Online (Sandbox Code Playgroud)

...是的,GroovyMock这里的“全局” 实际上具有“驯服”静态instance场的能力,以便它温柔地接受Mock巢中的杜鹃。

kri*_*aex 6

所以基本上您想测试一个单例不是一个单例。这让我感到很奇怪。但是无论如何,我将这个问题视为一个难题,我将自己解决这个难题,因为这是一个很好的挑战。(孩子们,不要在家做!)

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"
  }
}
Run Code Online (Sandbox Code Playgroud)

单例助手类:

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())
  }
}
Run Code Online (Sandbox Code Playgroud)

Spock测试:

该测试显示了如何

  • 在特征方法执行之前重新实例化Groovy单例
  • 使用Stub()Groovy单例
  • 使用Mock()Groovy单例
  • 使用Spy()Groovy单例(需要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]
  }
}
Run Code Online (Sandbox Code Playgroud)

控制台日志:

--- 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
Run Code Online (Sandbox Code Playgroud)