【问题标题】:How can I test that a field is set to a certain value?如何测试某个字段是否设置为某个值?
【发布时间】:2018-05-07 18:24:51
【问题描述】:

这似乎很基本,所以我认为这是一个骗局......但我还没有找到任何可以回答这个问题的东西。

我的应用代码也是 Groovy。说我有一个字段

def something

在我的测试中(CUT 是 Spock Spy)我运行了一个中间有一行的方法

something = null 

something = new Bubble()

...我只是想找到一种方法来测试确实设置为null(或任何值...)

在我尝试过的then 块中:

1 * spyCUT.setSomething( null ) 

1 * spyCUT.setSomething(_)

1 * spyCUT.set( 'something', _ )

顺便说一句,为了回答我可以在then 块中测试something 的值的反对意见,情况是something 意味着首先设置为一个值,然后设置为另一个值这个方法的过程...

阅读了Groovy In Action 2nd Ed,我对 Groovy 如何处理获取和设置字段有最模糊的概念......显然还不够。

MCVE(FWIW!)

class Spocko {
    def something

    def doStuff() {
        something = 'fruit'
    }
}

class SpockoTest extends Specification {
    def 'test it'(){
        given:
        Spocko spySpocko = Spy( Spocko )

        when:
        spySpocko.doStuff()

        then:
        1 * spySpocko.setSomething(_)
    }
}

稍后(在 kriegaex 非常有帮助的回复之后)

上面的SpockTest setSomething 调用:

class Spocko {
    def something

    def doStuff() {
        this.each{
            it.something = 'fruit' 
        }
    }
}

...通过!我现在正试图理解为什么......

顺便说一句,我还发现以下通过(并且没有关闭):

1 * spySpocko.setProperty( 'something', _ )

【问题讨论】:

  • 恕我直言,没有冒犯的意思:迈克,你什么时候才能学会向MCVEs 提供你的问题?这真的不好玩,我总是必须从你不连贯的 sn-ps 集中拼凑可复制的测试用例。帮助别人帮助你,改善你的提问方式——拜托,拜托。 P.S.:您阅读了很多规范和工具源代码,试图做一些棘手的事情。为什么?保持简单,使用干净的代码并使您的代码可测试。我保证,您会享受结果并节省大量时间。
  • @kriegaex 感谢您的回复。但是......在这种情况下提供 MCVE 是愚蠢的:MCVE 在我描述的内容中是明确的:您可以使用一个 10 行的 Groovy 应用程序文件和一个 10 行的 Spock 测试文件。您甚至可以将它们放在同一个文件中。但是无论如何,您没有义务回答任何问题。或者你可以相信我得到了我描述的结果(在最简单的可以想象的情况下)。
  • 我不知道你所说的“棘手的事情”是什么意思。当我开发这个项目时,我正在旋转由一个非常“严格”的 TDD 逻辑驱动的测试(规范/功能):我希望我的应用程序类做一些他们还没有做的事情,并问自己如何“提出”这个问题.我的大多数测试实际上并不是单元测试,它们更像是功能测试,并且开发是渐进式的,从不“激进”。如果您不喜欢我提出的问题,最好的办法就是忽略它们...
  • 也许更重要的是,关于这一点,问题很简单:Spock 可以对运行时生成的方法应用模拟吗? (AST 方法 - 至少我认为 getter 和 setter 属于这一类......?)。想要对此进行测试并非没有道理。这可能会也可能不会,但我不认为它本质上是“棘手的”。
  • 看到了吗?提供MCVE 并不难,您让我很容易回答您的问题。谢谢你。 :-) 为什么要写三个关于你为什么不想改进你的问题的 cmets?这不是我个人的想法,MCVE 链接(如果你曾经打开过它)是关于如何在这里提问的 StackOverflow 指南。你写多少散文来描述问题,它只是支持你的情况,但容易产生误解。让代码说话!

标签: groovy field spock getter-setter


【解决方案1】:

在我看过你的 MCVE 之后,可以这样回答这个问题:你不能测试一个永远不会发生的方法调用。 doStuff() 只是给一个字段赋值,它不会在内部调用 setter 方法。看看这个:

package de.scrum_master.stackoverflow

import spock.lang.Specification

class SpockoTest extends Specification {
  static class Spocko {
    def something

    def doStuff() {
      something = 'fruit'
    }

    def doMoreStuff() {
      setSomething('vegetable')
    }
  }

  def 'test it'(){
    given: 'Spocko spy'
    Spocko spySpocko = Spy(Spocko)

    when: 'calling method assigning value to property'
    spySpocko.doStuff()

    then: 'no setter is called'
    0 * spySpocko.setSomething(_)
    spySpocko.something == 'fruit'

    when: 'calling method using setter'
    spySpocko.doMoreStuff()

    then: 'setter gets called'
    1 * spySpocko.setSomething('vegetable')

    when: 'using Groovy setter-like syntax from another class'
    spySpocko.something = 'fish'

    then: 'actually a setter gets called'
    1 * spySpocko.setSomething('fish')
  }
}

这就是发生的事情。调用时

javap -v target/test-classes/de/scrum_master/stackoverflow/SpockoTest\$Spocko.class

你看(输出缩短):

public java.lang.Object doStuff();
  descriptor: ()Ljava/lang/Object;
  flags: ACC_PUBLIC
  Code:
    stack=2, locals=3, args_size=1
       0: invokestatic  #24                 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
       3: astore_1
       4: ldc           #36                 // String fruit
       6: astore_2
       7: aload_2
       8: aload_0
       9: swap
      10: putfield      #38                 // Field something:Ljava/lang/Object;
      13: aload_2
      14: areturn
      15: aconst_null
      16: areturn

public java.lang.Object doMoreStuff();
  descriptor: ()Ljava/lang/Object;
  flags: ACC_PUBLIC
  Code:
    stack=3, locals=2, args_size=1
       0: invokestatic  #24                 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
       3: astore_1
       4: aload_1
       5: ldc           #40                 // int 0
       7: aaload
       8: aload_0
       9: ldc           #42                 // String vegetable
      11: invokeinterface #48,  3           // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.callCurrent:(Lgroovy/lang/GroovyObject;Ljava/lang/Object;)Ljava/lang/Object;
      16: areturn
      17: aconst_null
      18: areturn

你能看出区别吗?


问题编辑 2 后更新:您想知道为什么这会触发 setter 调用:

def doStuff() {
  this.each {
    it.something = 'fruit' 
  }
}

这是因为this 作为参数提供给闭包,因此it.something = 'fruit' 像我的示例spySpocko.something = 'fish' 一样动态解析,因为它不是something = 'fruit' 中的内部赋值(相当于@987654330 @) 了。

其实我认为即使不看字节码也不是那么难理解,只要按照通常的 Groovy 教程进行操作即可。我在重复自己,但我确实认为您对事情进行了过度设计和过度复杂化,对事情进行了过于深入的测试。我不会将这样的测试放入生产代码库中。尝试测试你的类的行为(想想规范和特性!),而不是内部的复杂性。但如果它有助于您了解 Groovy 的工作原理,请继续玩。

截至目前,请不要进行进一步的问题编辑和后续问题。如果您有新问题,最好使用新的 MCVE 创建一个新问题。

【讨论】:

  • 感谢您的耐心(和专业知识)。是的,我可以看到差异。我刚刚做了另一个测试,我现在要在 MCVE 之后添加它......它使用了一个闭包。我现在将尝试理解这一点(!)。我从来没有遇到过javap:很有趣。但我(非常不熟练)的理解是,Groovy 在运行时添加了 getter 和 setter AST 方法:显然我还有很多要学习的......
  • 谢谢...你说“测试太深入”我并不感到惊讶。但我在这里要做的只是测试一个开放组件(Lucene IndexWriter)首先关闭,然后设置为null,然后再将另一个IndexWriter 分配给相关字段(indexWriter)。当然,我可以旋转一个人为地称为assignIndexWriter(或者可能是setIndexWriter!)的方法。将字段设置为null 以允许打开新的IndexWriter 失败的可能性很重要:否则indexWriter 将不是null,而是分配给关闭的IndexWriter
  • 你只能测试你知道的方法——更糟糕的是,你需要知道它们的内部实现。你真的应该有某种 setter 方法(不仅仅是一个自动生成的方法)来执行该合同。更好的是包装类型的最终字段(例如,称为IndexWriterManager,尽管这是一个糟糕的名字),它负责它管理的实例,并且只通过关闭它当前持有的实例来接受一个新的实例。然后可以正确测试该包装器。您不能仅通过测试来执行合同。
  • 好的,谢谢...了解您对如何看待这个问题的想法很有用。
猜你喜欢
  • 1970-01-01
  • 2018-04-19
  • 2018-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-21
  • 2012-04-24
  • 1970-01-01
相关资源
最近更新 更多