Какой смысл проверять количество раз, когда функция вызывается с помощью Mockito?

В моем понимании, тестирование кода - проверить правильность результатов, например калькулятор, мне нужно написать тестовый пример, чтобы проверить, является ли результат 1 + 1 равным 2.

Но я прочитал много тестовых примеров о проверке количества раз, когда вызывается метод. Я очень смущен этим. Самый лучший пример - это то, что я только что видел весной в действии:

public class BraveKnight implements Knight {
    private Quest quest;
    public BraveKnight(Quest quest) { 
        this.quest = quest; 
    }
    public void embarkOnQuest() {
        quest.embark(); 
    }
}

public class BraveKnightTest {
    @Test 
    public void knightShouldEmbarkOnQuest() { 
        Quest mockQuest = mock(Quest.class); 
        BraveKnight knight = new BraveKnight(mockQuest); 
        knight.embarkOnQuest(); 
        verify(mockQuest, times(1)).embark(); 
    }
}

Я действительно не знаю, почему они должны проверять, что функция embark() называется один раз. Разве вы не думаете, что embark(), безусловно, будет вызван после вызова embarkOnQuest()? Или возникнут некоторые ошибки, и я увижу сообщения об ошибках в журналах, которые показывают номер строки ошибки, которые могут помочь мне быстро найти неправильный код.

Итак, что такое проверка, как выше?

Ответ 1

Необходимость проста: убедиться, что было сделано правильное количество вызовов. Существуют сценарии, в которых вызовы методов не должны происходить, и другие, в которых они должны происходить более или менее, чем значение по умолчанию.

Рассмотрим следующую измененную версию embarkOnQuest:

public void embarkOnQuest() {
    quest.embark(); 
    quest.embarkAgain(); 
}

И предположим, что вы тестируете ошибки для quest.embark():

@Test 
public void knightShouldEmbarkOnQuest() { 
    Quest mockQuest = mock(Quest.class); 
    Mockito.doThrow(RuntimeException.class).when(mockQuest).embark();
    ...
}

В этом случае вы хотите убедиться, что quest.embarkAgain НЕ вызывается (или вызывается 0 раз):

verify(mockQuest, times(0)).embarkAgain(); //or verifyZeroInteractions

Конечно, это еще один простой пример. Есть много других примеров, которые можно добавить:

  • Коннектор базы данных, который должен кэшировать записи при первой выборке, можно сделать несколько вызовов и убедиться, что соединение с базой данных вызывается только один раз (на каждый тестовый запрос)
  • Объект singleton, который выполняет инициализацию при загрузке (или лениво), может проверить, что вызовы, связанные с инициализацией, выполняются только один раз.

Ответ 2

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

Например, если метод должен выполнить вызов внешнего API, есть несколько проблем с простое тестирование результата:

  • Сетевой ввод-вывод работает медленно. Если у вас много таких проверок, это замедлит ваш тестовый пример
  • В любом случае туда и обратно придется полагаться на код, делающий запрос, API и код, интерпретирующий ответ API, работать правильно. Это много точек отказа для одного теста.
  • Если что-то происходит глупо, и вы случайно делаете несколько запросов, это может вызвать проблемы с производительностью в вашей программе.

Чтобы ответить на ваши вопросы:

Разве вы не думаете, что embark(), безусловно, будет вызван после вызова embarkOnQuest()?

Тесты также имеют значение, позволяя вам рефакторинг, не беспокоясь о нарушении правил. Это очевидно сейчас, да. Будет ли это очевидно через 6 месяцев?

Ответ 3

Рассмотрим следующий код:

public void saveFooIfFlagTrue(Foo foo, boolean flag) {
    if (flag) {
        fooRepository.save(foo);
    }
}

Если вы не проверяете количество раз, fooRepository.save() вызывается fooRepository.save(), то как вы можете узнать, делает ли этот метод то, что вы хотите?

Это относится к другим недействительным методам. Если возврат к методу невозможен, и поэтому ответ на проверку не проверяется, проверка того, какие другие методы вызывают, является хорошим способом проверки правильности поведения метода.

Ответ 4

Я действительно не знаю, почему они должны проверять, что функция embark() называется одно время

Проверка обращения к макету за определенное количество раз - это стандартный способ работы Mockito при вызове Mockito.verify(). На самом деле это:

verify(mockQuest, times(1)).embark(); 

это просто подробный способ написать:

verify(mockQuest).embark(); 

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

Я не говорю, что это никогда не требуется, но используйте его только потому, что это единственный достойный выбор для тестируемого класса.