Spock:最后一堂课的嘲笑/残缺无用的方法 [英] Spock: mocking/stubbing void method of final class

查看:203
本文介绍了Spock:最后一堂课的嘲笑/残缺无用的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这里的模拟类是 org.apache.lucene.document.TextField setStringValue void



My Specification看起来像这样...

 给出:
...
TextField textFieldMock = GroovyMock(TextField)
//文本字段是ConsoleHandler类的字段,CH是类
ch.textField = textFieldMock
//相同的结果有或没有这条线的间谍:
textFieldMock.setStringValue( _)>> null
// NB我在下面解释这一行:
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这是第114行
indexWriter.addDocument(singleLDoc)

来自 println s是

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

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

  textField == null ? false 
textField null

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

java.lang.NullPointerException $ b $ org.apache.lucene.document.Field.setStringValue( Field.java:307)在org.spockframework.mock.runtime.GroovyMockMetaClass.doInvokeMethod(GroovyMockMetaClass.java:86

在org.spockframework.mock.runtime.GroovyMockMetaClass.invokeMethod(GroovyMockMetaClass.java: 42)
at core.ConsoleHandler.parse_closure1(ConsoleHandler.groovy:114)

114行是 setStringValue 行。 Field 这里是 TextField final )。 >。



对我来说,似乎有趣的事情正在发生:仿佛Spock在对自己说:啊,这个类 TextField final ,所以我会查询它的父类,然后从那里使用方法 setStringValue ...我发现/决定它不是一个模拟...



为什么 setStringValue 没有被模拟(或替代或任何正确的术语是一种方法...)?



稍后



我去看了一下包中的Field.java。相关行包括:

pre $ public void setStringValue(String value){
if(!(fieldsData instanceof String)) {
抛出new IllegalArgumentException(不能将值类型从+ fieldsData.getClass()。getSimpleName()+改为String));
}
if(value == null){
throw new IllegalArgumentException(value must not null);
}
fieldsData = value;

$ / code $ / pre
$ b $ p ... 307行(涉及NPE)原来是首先抛出新的IllegalArgumentException ... 行。很奇怪。建议 fieldsData null (如您所料)。

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



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



PPS解决方法我无处可去,最后创建了一个封装类,封装了最终 TextField (并且会发芽任何需要的方法......)。

我曾经有过在Lucene类中挣扎的经历:其中很多都是 final 或者 final 方法。在任何人认为您不需要测试已经可信的软件包(我同意这一点)之前,您仍然需要在开发代码时测试自己对这些类的使用。

解决方案

我无法真正解释为什么它不能像预期的那样工作 - 顺便说一句,存根 getClass()是一个坏主意,也是一个不好的例子,因为它可能有各种副作用 - 但我有一个解决方法:使用全局模拟。



第一个特性方法复制有问题的测试用例,第二个用于展示如何解决它。 $ b

  package de.scrum_master.stackoverflow 

进口org.apache.lucene.document.TextField
进口spock.lang.Specification

类LuceneTest延伸规格{
defLucene文本字段正常GroovyMock(){

给出:正常Groovy模拟
TextField textField = GroovyMock(){
stringValue()>> abc
}

当:调用父方法
textField.setStringValue(test)

then:引发异常
抛出NullPointerException异常

和:父方法存根不起作用
textField.stringValue()== null
}

def Lucene的文本字段全球GroovyMock(){

中给出: 全局Groovy的模拟
的TextField的TextField = GroovyMock(全球:真){
stringValue的()>> abc
}

期望:可以调用父级方法
textField.setStringValue(test)

和:父级方法存根作品
textField.stringValue()==abc
}
}


The mocked class here is org.apache.lucene.document.TextField. setStringValue is void.

My Specification looks like this...

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

the corresponding app code looks like this:

    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 )

The output from the printlns is

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

... which tends to prove that the mock is happening and getClass is being successfully replaced. If I get rid of the line textFieldMock.getClass() >> Object.class I get this output:

textField == null? false
textField null

In both cases the fail happens on the next line:

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)

Line 114 is the setStringValue line. Field here is the (non-final) superclass of TextField.

To me it appears that something funny is happening: as though Spock were saying to itself: "ah, this class TextField is final, so I'll consult its parent class, and use the method setStringValue from there... and I find/decide that it is not a mock..."

Why is setStringValue not being mocked (or "substituted" or whatever the right term is for a method...)?

later

I went and took a look at Field.java in the package in question. The relevant lines are:

  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;
  }

... line 307 (implicated for the NPE) turns out to be the first throw new IllegalArgumentException... line. Quite odd. Suggesting that fieldsData is null (as you'd expect).

But why does Spock find itself processing this bit of code in class Field at all? Illogical: it's mocking, Jim, but not as we know it.

PS I later tried it with a (real) ConsoleHandler and got the same results. I have just noticed that when Spock output suggests you use a GroovyMock it says "If the code under test is written in Groovy, use a Groovy mock." This class isn't... but so far in my testing code I've used GroovyMock for several Java classes from Java packages, including others from Lucene... without this problem...

PPS workaround I got nowhere and in the end just created a wrapper class which encapsulates the offending final TextField (and will sprout whatever methods are needed...).

I have had experience of struggling with Lucene classes in the past: many of these turn out to be final or to have final methods. Before anyone makes the point that you don't need to test packages that can already be trusted (with which I agree!), you still need to test your own use of such classes as you develop your code.

解决方案

I cannot really explain why it does not work for you as expected - BTW, stubbing getClass() is a bad idea and a bad example because it could have all kinds of side effects - but I do have a workaround for you: use a global mock.

The first feature method replicates your problematic test case, the second one shows how to solve it.

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"
  }
}

这篇关于Spock:最后一堂课的嘲笑/残缺无用的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆