TDD Проверка подлинности звонка - это антишаблон?

11

Я занимаюсь TDD уже год, мне это очень нравится, я люблю свои тестовые наборы и все такое. Тем не менее, я заметил, что в последнее время я провожу много проверок. Например, у меня будет служба, в которую будет добавлен репозиторий - в моем модульном тесте я пройду макет репозитория и проверим, что он был вызван в тестируемом методе. Затем я проверил бы, верны ли результаты (в другом тесте). Это определенно "чувствует" неправильно, так как мои модульные тесты теперь очень связаны с деталями реализации. Я слышал, что вы должны проверить «поведение», однако во многих ситуациях это ... ммм - невозможно? Если у тебя естьvoidМетод, например, вы обычно тестируете побочные эффекты. Я имею в виду, что легко пойти дальше и показать несколько простых код-ката, где это можно продемонстрировать, но ИМХО это не очень хорошо отражается на реальных программах, которые мы пишем. Что я делаю не так? Является ли этот тип тестирования анти-паттерном? Буду признателен за ваше мнение по этому поводу, я все еще немного новичок, когда дело доходит до TDD.

Димитар Димитров
источник
2
Краткий ответ: да. Есть очень интересные вопросы по этой теме уже где-то здесь. Ваши юнит-тесты не должны быть хрупкими и сильно зависеть от вашей реализации. Вот почему тесты более высокого уровня предназначены для (интеграции и т. Д.). Здесь: programmers.stackexchange.com/questions/198453/…
Кемода
@Kemoda Я был бы признателен, если бы вы связали меня с обсуждением или каким-либо другим материалом по этому вопросу, я бы очень хотел улучшить свои методы.
Димитар Димитров
1
у вас это есть, например, programmers.stackexchange.com/questions/198453/… другие ссылки я найду позже
Kemoda

Ответы:

8

Ну, вы должны пытаться проверить входы и выходы. Вы должны проверять внешне видимое поведение. «Обещания» или «контракт», которые дает ваш класс.

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

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

М. Дадли
источник
2

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

Поэтому, если, например, ваша служба добавляет что-то в репозиторий, вам следует проверить, что новая запись впоследствии содержится в репозитории, а не что действие add запущено.

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


«Но что, если использование реальных реализаций в тесте стоит дорого (например, потому что они требуют ресурсов, которые сложно настроить)? Мне нужно использовать mocks в этом случае, верно?»

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

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

oberlies
источник
1
Я понимаю, что вы говорите. Эта тема немного сбивает с толку относительно того, «сколько тестирование - это слишком много», скажем, у меня есть «агрегатный сервис», который в основном является фасадом и просто «склеивает» кучу других сервисов / репозиториев / компонентов, какие тесты ты пишешь для этого? Все, что я могу думать, это проверка вызова. Я надеюсь, что я понимаю.
Димитар Димитров
2

Мои мысли о: «совокупные услуги».

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

Есть 3 неисключительных других способа:

  1. Расширьте свои тесты для каждой отдельной службы, чтобы она проверяла поведение более высокого уровня. Например, если вы попали в базу данных в памяти в своих модульных тестах службы, поднимите ее на уровень, чтобы вы тестировали службу на предмет фактического значения в БД. Уровень обслуживания находится выше в дереве абстракций, как и ваш тест.

  2. Используйте генерацию кода для создания сервиса напрямую из агрегированных сервисов.

  3. Используйте что-то вроде рефлексии или динамического языка, чтобы сделать то же самое. Например, в Java может быть возможным использовать интерфейс Groovy, который передает вызов напрямую.

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

Эндрю Оксенбург
источник