Как проверить, был ли вызван метод суперкласса в Споке? - PullRequest
0 голосов
/ 11 февраля 2020

Представьте, что у меня есть следующая структура класса:

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

Я спок-тестирую Child.method и хочу убедиться, что Parent.method вызывается из Child.method. Я провел некоторое исследование, и я не нашел никакого удовлетворительного решения, чтобы решить мою проблему.

Как я могу проверить в тесте Спока, что при вызове Child.method также был вызван метод суперкласса (Parent.method)?

Известное решение: В Child переместите super.method() к отдельному, пакетно-приватному методу.

Я хочу знать, есть ли лучшее решение.

Ответы [ 4 ]

0 голосов
/ 04 апреля 2020

tim_yates и kriegaex - большие звери в джунглях, когда дело доходит до хорошего и плохого Спока, или тестирование в стиле TDD в целом ... они не раз (справедливо) разделяли мои вопросы так, как они это делают здесь, в основном на основе тестирования кода, а не реализации.

Иногда это трудно, хотя. Возможно, могут быть случаи, когда вы захотите проверить вызов super.doSomething(). Я просто собираю, используя TDD, уже сделав «всплеск», в котором я бросился вперед без тестирования, редактор для TreeTableView. «Шип» можно увидеть здесь . В конструктивном комментарии к моему ответу kleopatra посоветовала мне проверить (т.е. вставить if в код приложения), чтобы убедиться, что super.startEdit() действительно начал редактирование ячейки, прежде чем идти дальше, поэтому в данном случае это недостаточно проверить «побочный эффект» super.startEdit(), поскольку isEditing() теперь возвращает true. Вам действительно нужно знать, что 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()
    }
}

Я думаю, что вы должны "породить" искусственное целевой метод, поскольку, в общем, побочного эффекта нет совсем!

PS Я на самом деле параметризую этот тест так, чтобы он возвращал от true до isEmpty() во втором вызове, и потребовал, чтобы метод NOT быть вызванным в этом случае.

0 голосов
/ 12 февраля 2020

tim_yates прокомментировал:

Почему вы хотите это проверить? Не могли бы вы рассказать, как были выполнены расчеты суперкласса?

Я полностью согласен. Я бы не стал это проверять, поскольку, как предполагает @Override, контракт является переопределением, делегирование методу суперкласса необязательно. Почему вы заставляете своих пользователей вызывать метод суперкласса? Но, как сказал Тим, вы можете проверить наличие побочных эффектов, которые важны для вас. Вот небольшой пример с одним побочным эффектом, являющимся полевым присваиванием, а другим - что-то записанное в System.out (может быть, глупо, но только для того, чтобы показать что-то неочевидное с насмешкой):

package de.scrum_master.stackoverflow.q60167623;

public class Parent {
  protected String name;

  public void method() {
    // Some calculations
    System.out.println("parent method");
    name = "John Doe";
  }
}
package de.scrum_master.stackoverflow.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.stackoverflow.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"
  }
}

Обновление: То, что вы хотите сделать, просто технически невозможно, и по причине: это нарушит инкапсуляцию, см. здесь , здесь косвенно также здесь . Метод переопределен , слово говорит само за себя. Проверка на (побочный) эффект или результат метода, а не на его взаимодействие (которое он на самом деле вызывается). Возможности тестирования взаимодействия Spock используются слишком часто, хотя руководство Spock в некоторых местах предупреждает о чрезмерной спецификации. Это просто делает ваши тесты хрупкими. Тестирование взаимодействия подходит для шаблонов проектирования, таких как публикация / подписка (шаблон Observer), где имеет смысл тестировать взаимодействия между объектами как таковыми.

0 голосов
/ 15 февраля 2020

Если вам нужно обеспечить, чтобы некоторые функции в 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().

0 голосов
/ 11 февраля 2020

Я никогда не использовал платформу Спока, но я думаю, что вы можете проверить тип экземпляра в Parent.method с экземпляром оператора или отражения.

...