Spock:最终类

时间:2018-02-10 17:04:29

标签: groovy mocking spock final

这里的模拟类是org.apache.lucene.document.TextFieldsetStringValuevoid

我的规格看起来像这样......

given:
    ...
    TextField textFieldMock = GroovyMock( TextField )
    // textField is a field of the ConsoleHandler class, ch is a Spy of that class
    ch.textField = textFieldMock
    // same results with or without this line: 
    textFieldMock.setStringValue( _ ) >> null
    // NB I explain about this line below:
    textFieldMock.getClass() >> Object.class

相应的应用代码如下所示:

    assert textField != null
    singleLDoc.add(textField)
    writerDocument.paragraphIterator.each{

        println( "textField == null? ${ textField == null }" )
        println( "textField ${ textField.getClass() }" )

        textField.setStringValue( it.textContent ) // NB this is line 114
        indexWriter.addDocument( singleLDoc )

println的输出是

textField == null? false
textField class java.lang.Object

...这往往证明模拟正在发生并且getClass正在被成功替换。如果我摆脱了textFieldMock.getClass() >> Object.class行,我得到了这个输出:

textField == null? false
textField null

在这两种情况下,失败发生在下一行:

java.lang.NullPointerException
at org.apache.lucene.document.Field.setStringValue(Field.java:307)
at org.spockframework.mock.runtime.GroovyMockMetaClass.doInvokeMethod(GroovyMockMetaClass.java:86)
at org.spockframework.mock.runtime.GroovyMockMetaClass.invokeMethod(GroovyMockMetaClass.java:42)
at core.ConsoleHandler.parse_closure1(ConsoleHandler.groovy:114)

第114行是setStringValue行。 Field这里是final的(非TextField)超类。

对我而言似乎发生了一些有趣的事情:好像Spock对自己说:"啊,这个班TextFieldfinal,所以我会咨询其父母类,并从那里使用方法setStringValue ...我发现/决定它不是模拟..."

为什么setStringValue没有被嘲笑(或者#34;替换"或者任何正确的术语用于方法......)?

我去看看有问题的包中的Field.java。相关的行是:

  public void setStringValue(String value) {
    if (!(fieldsData instanceof String)) {
      throw new IllegalArgumentException("cannot change value type from " + fieldsData.getClass().getSimpleName() + " to String");
    }
    if (value == null) {
      throw new IllegalArgumentException("value must not be null");
    }
    fieldsData = value;
  }

...第307行(涉及NPE)原来是throw new IllegalArgumentException...行。很奇怪。建议fieldsDatanull(正如您所期望的那样)。

但是为什么Spock会发现自己在类Field中处理这段代码呢?不合逻辑:这是嘲笑,吉姆,但不是我们所知道的。

PS 我后来尝试了(真实的)ConsoleHandler并得到了相同的结果。我刚刚注意到,当Spock输出建议您使用GroovyMock时,它会说&#34;如果测试中的代码是用Groovy编写的,请使用Groovy mock。&#34;这个类不是......但到目前为止,在我的测试代码中,我已经使用GroovyMock从Java包中获得了几个Java类,包括来自Lucene的其他Java类...没有这个问题... < / p>

PPS解决方法我无处可去,最后创建了一个封装类,它封装了有问题的final TextField(并且会发布所需的任何方法......)

我过去曾经历过与Lucene课程斗争的经历:其中很多都是finalfinal方法。在任何人指出你不需要测试已经可信任的软件包之前(我同意!),你仍然需要在开发代码时测试自己对这些类的使用。

2 个答案:

答案 0 :(得分:1)

我无法解释为什么它不能像预期的那样对你起作用 - 顺便说一下,剔除getClass()是一个坏主意和一个坏例子,因为它可能有各种副作用 - 但我确实有一个解决方法你:使用全局模拟。

第一个特征方法复制了有问题的测试用例,第二个特征方法显示了如何解决它。

package de.scrum_master.stackoverflow

import org.apache.lucene.document.TextField
import spock.lang.Specification

class LuceneTest extends Specification {
  def "Lucene text field normal GroovyMock"() {

    given: "normal Groovy mock"
    TextField textField = GroovyMock() {
      stringValue() >> "abc"
    }

    when: "calling parent method"
    textField.setStringValue("test")

    then: "exception is thrown"
    thrown NullPointerException

    and: "parent method stubbing does not work"
    textField.stringValue() == null
  }

  def "Lucene text field global GroovyMock"() {

    given: "global Groovy mock"
    TextField textField = GroovyMock(global: true) {
      stringValue() >> "abc"
    }

    expect: "can call parent method"
    textField.setStringValue("test")

    and: "parent method stubbing works"
    textField.stringValue() == "abc"
  }
}

答案 1 :(得分:0)

kriegaex提供了解决方案。

但是,正如我对他的回答所说的那样,我不明白具体的类实例TextField实际上是如何使用ch模拟的。 FWIW我把我的(现在通过)测试的整个版本放入了。

def "parse should put several documents into the index"() {
    given: 
    // NB the method we need to mock is setStringValue, which is void
    // but (again to my astonishment) if the method is not mocked test still passes
    TextField textFieldMock = GroovyMock(global: true) { 
        // setStringValue() >> "abc"
    }
    // turns out not to be needed... why not???
    // ch.textField = textFieldMock

    IndexWriter indexWriterMock = Mock( IndexWriter )
    // commenting out this line means the test fails... as I'd expect
    // i.e. because I'm "injecting" the mock to be used instead of ch's field
    ch.indexWriter = indexWriterMock
    // this line included to be able to mock static method loadDocument:
    GroovyMock( TextDocument, global: true)
    def textDocMock = Mock( TextDocument )
    TextDocument.loadDocument(_) >> textDocMock
    Paragraph paraMock1 = Mock( Paragraph )
    paraMock1.textContent >> 'para 1'
    Paragraph paraMock2 = Mock( Paragraph )
    paraMock2.textContent >> 'para 2'
    Paragraph paraMock3 = Mock( Paragraph )
    paraMock3.textContent >> 'para 3'
    textDocMock.getParagraphIterator() >> [paraMock1, paraMock2, paraMock3].listIterator()
    Document lDocMock = GroovyMock( Document )
    // commenting out this line means the test fails... as I'd expect
    // i.e. because I'm "injecting" the mock to be used instead of ch's field
    ch.singleLDoc = lDocMock

    when: 
    def fileMock = Mock( File )
    fileMock.path >> testFolder.root.path + '/dummy.odt'
    fileMock.name >> '/dummy.odt'
    ch.parse( fileMock )

    then: 
    3 * indexWriterMock.addDocument( lDocMock )
    // this is the crucial line which is now passing!
    3 * textFieldMock.setStringValue(_)

PS令人担忧的是,如果我将then子句中的上述“3”中的任何一个更改为另一个值(例如4),则测试将失败而不输出任何测试结果。我刚从Gradle收到此消息:

  

core.ConsoleHandlerUTs&gt; Lucene文本字段全局GroovyMock FAILED
      ConsoleHandlerUTs.groovy上的org.spockframework.mock.TooFewInvocationsError:255
...发生故障:构建失败   例外。
  *出了什么问题:
执行任务失败':currentTestBunch'   &GT; java.lang.NullPointerException(无错误消息)

...其中第255行是ch.parse( fileMock )行。这显然不会发生在我的包装类版本中...所以目前我已经回到那个了!

PPS我知道我可能会在这里同时测试太多东西......但作为TDD的初学者,我经常发现最大的难题之一是如何挖掘内部使用相当多的合作类做一些事情的方法。上面涉及的课程证明有些不可分割(无论如何)。