如何验证是否在 Spock 中调用了 super class 方法?

How to verify whether a super class method was called in Spock?

假设我有以下 class 结构:

class Parent {
  public void method() {
    // Some calculations
  }
}
class Child extends Parent {
  @Override
  public void method() {
    super.method();
    // Some additional logic
  }
}

我正在对 Child.method 进行 spock 测试,并想验证是否从 Child.method 调用了 Parent.method。我做了一些研究,但没有找到任何令人满意的解决方案来解决我的问题。

如何在 Spock 测试中验证在 Child.method 的调用中也调用了 superclass 方法 (Parent.method)?

已知解决方案:在 Child 中将 super.method() 移动到单独的包私有方法。

想知道有没有更好的解决办法

我从未使用过 Spock 框架,但我认为您可以使用运算符实例或反射来检查 Parent.method 中实例的类型。

tim_yates评论:

Why do you want to test this? Can't you tell as the super class calculations were performed?

我完全同意。我不会对此进行测试,因为正如 @Override 所暗示的那样,契约是一种覆盖,对超级 class 方法的委托是可选的。你为什么要强迫你的用户调用 super class 方法?但正如蒂姆所说,您可以测试对您很重要的副作用。这是一个小例子,一个副作用是字段赋值,另一个是写入 System.out 的东西(可能很傻,但只是为了用模拟来展示一些不明显的东西):

package de.scrum_master.Whosebug.q60167623;

public class Parent {
  protected String name;

  public void method() {
    // Some calculations
    System.out.println("parent method");
    name = "John Doe";
  }
}
package de.scrum_master.Whosebug.q60167623;

class Child extends Parent {
  @Override
  public void method() {
    super.method();
    // Some additional logic
    System.out.println("child method");
  }

  public static void main(String[] args) {
    new Child().method();
  }
}
package de.scrum_master.Whosebug.q60167623

import spock.lang.Specification

class ChildTest extends Specification {
  static final PrintStream originalSysOut = System.out
  PrintStream mockSysOut = Mock()

  def setup() {
    System.out = mockSysOut
  }

  def cleanup() {
    System.out = originalSysOut
  }

  def test() {
    given:
    def child = new Child()

    when:
    child.method()

    then:
    1 * mockSysOut.println({ it.contains("parent") })
    child.name == "John Doe"
  }
}

更新: 你想做的事情在技术上是不可能的,原因是:它会破坏封装,见 here, here, indirectly also here。该方法是覆盖,这个词说明了一切。测试(副作用)或方法的结果,而不是它的交互(它实际上被调用)。 Spock 的交互测试功能被过度使用,尽管 Spock 手册在某些地方警告过规范。它只会让你的测试变得脆弱。交互测试适用于像 publish/subscribe(观察者模式)这样的设计模式,在这种情况下测试对象之间的交互是有意义的。

如果您需要强制调用 Parent 中的某些功能,您应该通过设计而不是测试来强制执行。

abstract class Parent {
  public final void method() {
    // Some calculations
    additionalLogic();
  }

  protected abstract void additionalLogic();
}

class Child extends Parent {
  @Override
  protected void additionalLogic() {
    super.method();
    // Some additional logic
  }
}

您当然可以不这样做 abstract,只需为 additionalLogic() 添加一个空操作实现即可。

tim_yates 和 kriegaex 是丛林中的大野兽,当谈到好的和坏的 Spock 或一般的 TDD 式测试时......他们不止一次(正确地)在他们这里的做法,基本上是基于测试代码而不是实现。

虽然有时很难。也许在某些情况下您想要测试 super.doSomething() 的调用。我只是使用 TDD 将已经完成 "spike" 的编辑器放在一起,其中我没有测试就匆匆忙忙地完成了 TreeTableView 的编辑器。 "spike"可见here。在对我的回答的建设性评论中,kleopatra 建议我检查(即在应用程序代码中放置一个 if)以确保 super.startEdit() 确实在继续之前开始编辑单元格,所以在在这种情况下,将 super.startEdit() 的 "side-effect" 测试为 isEditing() 现在 returns true 是不够的。你真的需要知道你的 class 的 startEdit() 实际上只是调用 super.startEdit().

但是,我不相信这是可以做到的,而且 tim_yates 或 kriegaex 几乎肯定会说如果可能的话你是怎么做到的。

因此,我建议的 TDD 解决方案如下所示:

def 'super start edit should be called if cell is not empty'(){
    given:
    // NB has to be GroovySpy because isEmpty() is final
    DueDateEditor editor = GroovySpy( DueDateEditor ){
        isEmpty() >> false
    }

    when:
    editor.startEdit()

    then:
    1 * editor.callSuperStartEdit()
}


class DueDateEditor extends TreeTableCell {

    @Override
    void startEdit(){
        if( ! isEmpty() ) {
            // this is the line you have to add to make the test pass
            callSuperStartEdit()
        }
    }

    def callSuperStartEdit(){
        super.startEdit()
    }
}

我认为你必须 "spawn" 人工的单一用途方法,因为准确地说,根本没有副作用!

PS 实际上,我会将此测试参数化,以便它在第二次调用中 returns trueisEmpty(),并要求不调用该方法那种情况。