Spock Unroll, кажется, печатает что-то странное с логическим параметром - PullRequest
0 голосов
/ 04 апреля 2020

Я просто соединил этот метод теста:

@Unroll
def 'super start edit should be called if cell is not empty'( boolean empty ){
    given:
    DueDateEditor editor = GroovySpy( DueDateEditor ){
        isEmpty() >> empty
    }

    when:
    editor.startEdit()

    then:
    if( empty){
        0 * editor.callSuperStartEdit()
    }
    else {
        1 * editor.callSuperStartEdit()
    }

    where:
    empty | _
    true  | _
    false | _
}

... он работает нормально с точки зрения прохождения двух тестов ... но когда вы его проваливаете, это очень странно: вывод, если параметр empty равен false равен

super start edit should be called if cell is not empty[1]

... и равен 0, если параметр empty равен true. Это ошибка?

Ответы [ 2 ]

2 голосов
/ 05 апреля 2020

Я пишу дополнительный ответ, потому что

  • есть небольшая ошибка в решениях Тима относительно заголовка (но все же его ответ технически абсолютно верен!),
  • вам не нужно GroovySpy здесь, просто Spy абсолютно достаточно,
  • Я хочу показать вам альтернативный способ тестирования без заглушки isEmpty(),
  • Я хочу покажу вам, как вы можете использовать только одно взаимодействие с числом вызовов в троичном выражении вместо if-else (даже если отчеты об ошибках уродливы),
  • Я хочу прокомментировать ваш способ тестирования в общие (см. конец этого поста).
package de.scrum_master.stackoverflow.q61032514;

import java.time.LocalDate;

public class DueDateEditor {
  String text;

  public boolean isEmpty() {
    return text == null || text.trim() == "";
  }

  public void startEdit() {
    if (!isEmpty())
      callSuperStartEdit();
  }

  public void callSuperStartEdit() {}
}
package de.scrum_master.stackoverflow.q61032514

import spock.lang.Specification
import spock.lang.Unroll

class DueDateEditorTest extends Specification {
  @Unroll
  def 'super start edit #shouldMsg be called if the cell is #cellStateMsg'() {
    given:
    DueDateEditor editor = Spy() {
      isEmpty() >> empty
    }

    when:
    editor.startEdit()

    then:
    (empty ? 0 : 1) * editor.callSuperStartEdit()

    where:
    empty << [true, false]
    shouldMsg = empty ? 'should not' : 'should'
    cellStateMsg = empty ? 'empty' : 'not empty'
  }

  @Unroll
  def "super start edit #shouldMsg be called if cell text is '#text'"() {
    given:
    DueDateEditor editor = Spy()
    editor.text = text

    when:
    editor.startEdit()

    then:
    (editor.isEmpty() ? 0 : 1) * editor.callSuperStartEdit()
    // Or, if 'isEmpty()' has a side effect:
    // (text ? 1 : 0) * editor.callSuperStartEdit()

    where:
    text << ["foo", "", null, "line 1\nline 2"]
    shouldMsg = text ? 'should' : 'should not'
    cellStateMsg = text ? 'not empty' : 'empty'
  }
}

Общие замечания:

  • Я бы не проверял внутреннюю проводку одного класса с взаимодействиями , Тест будет хрупким, и если вы проведете внутреннюю реорганизацию класса, не изменив API вообще, тест может прерваться, если взаимодействия перестанут соответствовать ожидаемым. Я думаю, что это чрезмерная спецификация, и я бы использовал тестирование взаимодействия только для критически важных взаимодействий между различными классами или, возможно, разными экземплярами одного класса - «критическими», что означает такие вещи, как функциональность шаблонов проектирования, таких как Observer.
  • Наличие if-else для дифференциации двух случаев по двум различным шаблонам взаимодействия, если весь тест знает только эти два случая, просто делает тест менее читаемым и более сложным, смотрите ваш собственный код, а также мой и Тим. В таком случае я бы предпочел написать два метода с простыми заголовками и простой функциональностью, но без if-else или троичных выражений, без вспомогательных переменных для заголовков et c.

PS: Извините, Мне нужно было создать тестовый класс DueDateEditor, чтобы мой тест скомпилировался и работал как ожидалось. Как обычно, Майк, к сожалению, не предоставил MCVE , а только его часть.


Обновление: Мы говорили о GroovySpy в наших комментариях и, как я уже сказал, он не будет работать, если ваши классы Java классы и есть конечный метод, который вы хотите заглушки, см. руководство Spock . Вот вам подтверждение:

package de.scrum_master.stackoverflow.q61032514;

public class TreeTableCell<A, B> {
  String text;

  public final boolean isEmpty() {
    return text == null || text.trim() == "";
  }
}
package de.scrum_master.stackoverflow.q61032514;

import java.time.LocalDate;

public class DueDateEditor extends TreeTableCell<String, LocalDate> {
  public void startEdit() {
    if (!isEmpty())
      callSuperStartEdit();
  }

  public void callSuperStartEdit() {}
}
package de.scrum_master.stackoverflow.q61032514

import spock.lang.Specification
import spock.lang.Unroll

class DueDateEditorTest extends Specification {
  @Unroll
  def 'super start edit #shouldMsg be called if the cell is #cellStateMsg'() {
    given:
    DueDateEditor editor = GroovySpy() {
      isEmpty() >> empty
    }

    when:
    editor.startEdit()

    then:
    (empty ? 0 : 1) * editor.callSuperStartEdit()

    where:
    empty << [true, false]
    shouldMsg = empty ? 'should not' : 'should'
    cellStateMsg = empty ? 'empty' : 'not empty'
  }
}

Тест сработал бы, если бы ваши классы приложений были только Groovy классами. Но если они являются Java классами, как в моем примере, тест не пройдёт так:

Too few invocations for:

(empty ? 0 : 1) * editor.callSuperStartEdit()   (0 invocations)

Unmatched invocations (ordered by similarity):

1 * editor.startEdit()
methodName == "callSuperStartEdit"
|          |
startEdit  false
           10 differences (44% similarity)
           (s---------)tartEdit
           (callSuperS)tartEdit

Так что в этом случае вы не можете просто использовать Groovy magi c для проверки взаимодействий. Но, как я уже сказал, ты не должен так поступать. Скорее убедитесь, что и startEdit(), и callSuperStartEdit() делают правильные вещи. Проверьте их результаты или, если они void, проверьте их побочные эффекты на состояние испытуемого или его соавторов.


Обновление 2: Относительно вашего исходного вопроса Что касается именованных индексированных методов, на самом деле @ tim_yates дал правильный ответ. Я просто хочу добавить соответствующую ссылку на руководство Спока, объясняющую метод развертывания и как вы можете влиять на именование с помощью переменных из блока where:.

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

Нет, это не ошибка

Вы не сказали spock, как называть свои тесты по-другому, поэтому он добавляет итерацию (0, затем 1) к имени

Измените его на

@Unroll
def 'super start edit should be called if isEmpty for the cell returns #empty'(){
    given:
    DueDateEditor editor = GroovySpy( DueDateEditor ){
        isEmpty() >> empty
    }

    when:
    editor.startEdit()

    then:
    if( empty){
        0 * editor.callSuperStartEdit()
    }
    else {
        1 * editor.callSuperStartEdit()
    }

    where:
    empty << [true, false]
}

Я изменил раздел where, так как таблица с одним столбцом чувствуется нечетным

edit

Вы также можете сделать это:

@Unroll
def 'super start edit should be called if the cell is #msg'(){
    given:
    DueDateEditor editor = GroovySpy( DueDateEditor ){
        isEmpty() >> empty
    }

    when:
    editor.startEdit()

    then:
    if( empty){
        0 * editor.callSuperStartEdit()
    }
    else {
        1 * editor.callSuperStartEdit()
    }

    where:
    empty << [true, false]
    msg = empty ? 'empty' : 'not empty'
}
...