Спок - Как работать с повторными взаимодействиями - PullRequest
0 голосов
/ 20 октября 2019

В нескольких тестовых примерах я пытаюсь следовать принципу СУХОЙ, когда только взаимодействия различаются при одинаковых условиях тестового примера. Я не могу найти способ реализовать несколько методов в блоке interaction { }.

Как уже упоминалось в http://spockframework.org/spock/docs/1.3/interaction_based_testing.html#_explicit_interaction_blocks, Я использую interaction { } в блоке then:, как показано ниже:

Java-код:

// legacy code (still running on EJB 1.0 framework, and no dependency injection involved)
// can't alter java code base

public voidGetData() {
  DataService ds = new DataService();
  ds = ds.findByOffset(5);
  Long len = ds.getOffset() // happy path scenario; missing a null check
  // other code
}

// other varieties of same code:
public voidGetData2() {
  ItemEJB tmpItem = new ItemEJB();
  ItemEJB item = tmpItem.findByOffset(5);
  if(null != item) {
    Long len = item.getOffset();
    // other code
  }
}

public voidGetData3() {
  ItemEJB item = new ItemEJB().findByOffset(5);
  if(null != item) {
    Long len = item.getOffset();
    // other code
  }
}

Тест Спока:

def "test scene1"() {
  given: "a task"
  // other code ommitted
  DataService mockObj = Mock(DataService)

  when: "take action"
  // code omitted

  then: "action response"
  interaction {
    verifyNoDataScenario()    // How to add verifyErrorScenario() interaction to the list?
  }
}

private verifyDataScenario() {
  1 * mockObj.findByOffset(5) >> mockObj // the findByOffset() returns an object, so mapped to same mock instance
  1 * mockObj.getOffset() >> 200
}

private verifyErrorScenario() {
  1 * mockObj.findByOffset(5) >> null // the findByOffset() returns null
  0 * mockObj.getOffset() >> 200  // this won't be executed, and should ie expected to throw NPE
}

Закрытие interaction не 'не может принимать более одного вызова метода. Я не уверен, если это ограничение дизайна. Я считаю, что в закрытии можно сделать больше, чем просто упомянуть имя метода. Я также попытался интерполировать mockObj как переменную и использовать конвейер данных / таблицу данных, но, поскольку он ссылается на тот же экземпляр mock, он не работает. Я опубликую это как отдельный вопрос.

Я закончил тем, что повторил тестовый случай дважды, чтобы вызвать различные методы взаимодействия. Внизу я вижу больше сценариев, и хотел избежать подхода копирования и вставки. Цените любые указатели для достижения этой цели.

Обновление:

  • Изменен общий код Java, так как более раннее имя DataService сбивало с толку.
  • Поскольку естьDI не задействован, и я не нашел способа смоделировать переменные метода, поэтому я имитирую их с помощью PowerMockito, например PowerMockito.whenNew(DataService.class).withNoArguments().thenReturn(mockObj)

1 Ответ

1 голос
/ 21 октября 2019

Код вашего приложения выглядит очень странно. Стиль программирования в вашем унаследованном приложении настолько плох? Сначала создается объект DataService с помощью конструктора без аргументов, который просто перезаписывается на следующем шаге путем вызова метода для этого экземпляра, который снова возвращает объект DataService. Какой программист создает такой код? Или вы просто создали какой-то псевдокод, который не имеет ничего общего с вашим реальным приложением? Пожалуйста, объясните.

Что касается вашего тестового кода, он также не имеет смысла, потому что вы создаете экземпляр DataService mockObj в качестве локальной переменной в своем методе объекта (методе теста), что означает, что в вашем вспомогательном методе mockObjне могут быть доступныПоэтому вам нужно либо передать объект в качестве параметра вспомогательным методам, либо сделать его полем в вашем тестовом классе.

И последнее, но не менее важное: ваш локальный фиктивный объект никогда не вводится в класстестируется, потому что, как я сказал в первом абзаце, объект DataService в getData() также является локальной переменной. Если код вашего приложения не является полностью фальшивым, невозможно внедрить макет, потому что getData() не имеет никакого параметра метода, а объект DataService не является полем, которое может быть установлено с помощью метода или конструктора сеттера. Таким образом, вы можете создать столько макетов, сколько захотите, приложение никогда не будет о них знать. Таким образом, ваша заглушка findByOffset(long offset) (почему вы не показываете код этого метода?) Не имеет никакого эффекта.

Итог: приведите пример, отражающий структуру вашего реального кода, как приложения, так и тестакод. Предоставленные вами фрагменты, к сожалению, не имеют никакого смысла. Я пытаюсь помочь, но так не могу.


Обновление:

В своих комментариях я упомянул рефакторинг вашего старого кода для тестируемости, добавив конструктор, метод установки или перегруженный метод getData с дополнительным параметром. Вот пример того, что я имею в виду:

Класс вспомогательного помощника:

package de.scrum_master.stackoverflow.q58470315;

public class DataService {
  private long offset;

  public DataService(long offset) {
    this.offset = offset;
  }

  public DataService() {}

  public DataService findByOffset(long offset) {
    return new DataService(offset);
  }

  public long getOffset() {
    return offset;
  }

  @Override
  public String toString() {
    return "DataService{" +
      "offset=" + offset +
      '}';
  }
}

Испытуемый объект:

Позвольте мне добавить приватный DataService член с установщиком, чтобы сделать объект инъекционным. Я также добавляю проверку, был ли введен член ds или нет. Если нет, то код будет работать так же, как и раньше, и сам создаст новый объект.

package de.scrum_master.stackoverflow.q58470315;

public class ToBeTestedWithInteractions {
  private DataService ds;

  public void setDataService(DataService ds) {
    this.ds = ds;
  }

  // legacy code; can't alter
  public void getData() {
    if (ds == null)
      ds = new DataService();
    ds = ds.findByOffset(5);
    Long len = ds.getOffset();
  }
}

Тест Спока:

Теперь давайте проверим оба нормальныхи сценарий ошибки. На самом деле, я думаю, вам следует разбить его на два более мелких метода функций, но, как вы, похоже, хотите проверить все (слишком много IMO) в одном методе, вы также можете сделать это с помощью двух различных пар блоков when-then. Вам не нужно явно объявлять какие-либо блоки взаимодействия для этого.

package de.scrum_master.stackoverflow.q58470315

import spock.lang.Specification

class RepeatedInteractionsTest extends Specification {
  def "test scene1"() {
    given: "subject under test with injected mock"
    ToBeTestedWithInteractions subjectUnderTest = new ToBeTestedWithInteractions()
    DataService dataService = Mock()
    subjectUnderTest.dataService = dataService

    when: "getting data"
    subjectUnderTest.getData()

    then: "no error, normal return values"
    noExceptionThrown()
    1 * dataService.findByOffset(5) >> dataService
    1 * dataService.getOffset() >> 200

    when: "getting data"
    subjectUnderTest.getData()

    then: "NPE, only first method called"
    thrown NullPointerException
    1 * dataService.findByOffset(5) >> null
    0 * dataService.getOffset()
  }
}

Обратите также внимание, что тестирование исключений, которые выдают или не выдает, добавляет значение к тесту w, тестирование взаимодействия просто проверяет внутренний устаревший кодповедение, которое мало что значит.

...