Pull to refresh

Comments 5

Не вижу никакого смысла плодить сущности и увеличивать терминологическую сложность на ровном месте без какого-либо выхлопа. Тем более, когда границы у терминов размыты и это создаёт ещё один повод для паралитического тупика: это что? это мок? это спай? это спай-мок? стаб-дамми-спай? Для чего эти заморочки нужны? Ни для чего.

Достаточно банального разделения на стабы и моки. Стаб просто статичная заглушка входных данных, мок имитация поведения, которая может сопровождаться проверкой фактов вызова, а может и нет. Совершенно не принципиально как реализован мок: с помощью библиотечной генерации, собственной реализацией, или их комбинации. С ассертом вызова с данными, или накоплением данных и последующей проверкой, или ещё как-то, при подаче данных, проверке их наличия, сравнения содержимого, количество вызовов, вариаций масса. При желании можно ещё десяток терминов наплодить, но зачем?

Вместо борьбы со сложностью, мы её зачем-то создаём.

Эта статья задумана как справочник, и я буду ссылаться на неё в следующих статьях.
Следом будет статья про внешние API в интеграционных тестах API-сервисов, и там я покажу применение 4 из 5 типов: Fake, Mock, Spy, Stub.

В реальной практике можно сократить инструментарий до 4, 3, 2 или даже единственного типа дублёра. Но возникает вопрос: какие именно типы выбросить?

С подачи Мартина Фаулера и Владимира Хорикова популярна дихотомия Mock vs Stub. И я считаю это ошибкой. В моей личной практике (с фокусом на интеграционные приёмочные тесты) чаще всего я выбирал Fake. На второе место я бы поставил Spy/Mock (я их не различал до недавних пор).

Итого: если дихотомия, то лучше оставить Fake и Mock, а не Stub и Mock. Игнорирование Fake или неумение их писать снижает качество тестов в целом.

В реальной практике можно сократить инструментарий до 4, 3, 2 или даже единственного типа дублёра.

Правильно - до Mock (я имею в виду тот, который из Moq). А дальше всё зависит его от последующей настройки: не настраиваем ничего, чисто шоб було, - муляж(dummy), настраиваем статическую возвращаемую информацию через Returns - заглушка (по-вашему dummy), настраиваем фиксацию факта вызова (Verify) и, опционально, параметры вызова (Callback) - получаем наблюдатель (по-вашему, spy), используем созраненные параметры вызова вместо статических в том же Returns (он ест не только константы, но и лямбды )- получаем имитатор (по-вашему, fake). Поменялись тестовые требования - подкручиваем настройку в любую нужную сторону, не меняя большую часть тестового кода. Опять-таки, для серии последовательных тестов можно сделать заглушку, возвращающую данные, настраиваемые извне: пишем в коде теста в переменную, а в Returns выводим значение этой переменной через ту же лямбду. Короче, всё - Mock, а с теоретически верными названиями можно не заморачиваться. И никакой дихотомии: что хотим, то и воротим.

PS Но это, конечно, если тесты писать вручную. Если код теста генерится из описания на каком-то промежуточном языке, которым, предполагается, могут пользоваться и люди, кодированию не обученные (на самом деле нет), то гибкость, таки да - теряется.

Сокращение пяти типов до одного Mock — тоже популярный способ.

Он хорошо сочетается с модульными тестами и каноничным TDD, изложенным кратко в статье Canon TDD Кента Бека или подробно в его же книге «Экстремальное программирование: разработка через тестирование».

Аналогичный подход изложен в книге «Growing Object-Oriented Software, Guided by Tests». Само название этой книги очень правильно описывает суть подхода:

  • проектировать и реализовывать программу как множество объектов средствами ООП

  • каждый добавляемый объект покрывать тестами по циклу каноничного TDD

А есть иной подход: фокус на интеграционные приёмочные тесты. Этот подход хорошо описывают два источника:

Лично я сторонник именно второго подхода, в моей практике он прекрасно показал себя в разработке бэкенд-сервисов для бэкофисной автоматизации (то, что Джоэль Спольски в статье «Пять миров» называл миром корпоративного ПО).

Если же, например, разработчик создаёт библиотеки или фреймворки, то он может сфокусироваться на модульных тестах и использовать их как приёмочные.

От этого зависит набор используемых тестовых дублёров:

  • при фокусе на интеграционные приёмочные тесты и BDD / ATDD лучше всего работают Fake-объекты, на втором месте Mock

  • при фокусе на модульные тесты и каноничный TDD лучше всего, пожалуй, работают Mock-объекты

Впрочем, Кент Бек (создатель TDD) избегает Mock-объектов, вот цитата из статьи A week with Kent Beck:

Kent Beck explained that he does not use mocks, but instead works from the lowest assumption, validates that assumption, and works to the next. He splits up the design strategy path, and avoids to mocks by this completely.

Не очень понял как терминология влияет на качество тестов. Какую задачу это решает в принципе тоже непонятно.

Изоляция модуля в тестах нужна для того, чтобы сосредоточиться на тестировании его функциональности, без влияния. Это же нужно ещё и для рефакторинга, так тесты модуля не будут ломаться при изменениях в других модулях. Для изоляции нам нужны объекты, имитирующие зависимости. Какая именно имитация нужна? Создаётся мок-объект и настраивается. Какая разница как это называется, Spy, Fake,...etc.? Это для чего нужно?

Ещё раз, всё описанное в статье обычно используется. Но оно именно так не называется, так как часто границы пересекаются. Тот же Moq по сути делает Spy-объекты, потому как считает количество вызовов как минимум. Легко можно настроить и запоминание входных данных вместе с этим.

Сам вопрос какой-то странный. "Если вам нужно 2-4-5..25 инструментария", мне нужны все. Но возможности. А не отдельные какие-то сущности со сложной таксономией.

Sign up to leave a comment.

Articles