моки в тестировании что это

Моки не кусаются! Осваиваем мокинг с React Testing Library

моки в тестировании что это. Смотреть фото моки в тестировании что это. Смотреть картинку моки в тестировании что это. Картинка про моки в тестировании что это. Фото моки в тестировании что это

Они предназначены помочь вам создавать более простые и надежные тесты. В этой серии статей я продемонстрирую вам паттерны, на которые я опираюсь при написании моков (mocks или “заглушек”) компонентов React.

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

Что такое мок?

В мире JavaScript термин mock очень широко применяется для обозначения любой имитированной реализации или тестового двойника (test double). Имитированные реализации — это просто значения, которые заменяют другие в вашем производственном коде во время выполнения тестов. Они примеряют на себя интерфейс заменяемого объекта, так что остальная часть вашего кода работает так, как если бы никакой замены не было.

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

Если вам интересно узнать больше о имитированных реализациях, почитайте книгу Мартина Фаулера «Моки — не заглушки».

Jest и мокинг

В книге Mastering React Test-Driven Development я использую импорт именованных модулей ES6 для создания тестовых двойников. Такой подход дает немного больше гибкости, но выглядит слегка более хакерским.

На странице Jest про jest.mock говорится, что моки гарантируют, что ваши тесты будут быстрыми и не хрупкими.

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

Чтобы понять, почему это так, давайте рассмотрим пример.

Почему моки?

Поддерживать тесты в рабочем состоянии становится сложнее. Каждый новый тест имеет более сложный сетап, и набор тестов начинает поедать все больше вашего времени — бремя, которое теперь необходимо поддерживать.

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

Но что, если рендеринг PostContent имеет побочные эффекты?

Набор тестов в BlogPage.test.js должен знать о побочных эффектах и ​​быть готовым обрабатывать их. Например, он должен будет держать наготове fetch ответ.

Зависимость, которой мы пытались избежать путем разделения наших тестовых наборов, все еще существует.

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

Так ли это необходимо?

Кстати, пара слов о переходе из области сквозных тестов в область модульных тестов.

Наличие тестовых двойников — ключевой показатель того, что вы пишете модульные тесты.

Многие опытные тестировщики сразу же начинают новые проекты с модульных тестов (и моков), потому что они знают, что по мере роста их кодовой базы они будут сталкиваться с проблемой нестабильности тестов.

Модульные тесты обычно намного меньше, чем сквозные. Они могут быть настолько малы, что часто занимают не больше трех-четырех строк кода. Это делает их отличными кандидатами для таких практик социального кодирования, как парное и ансамблевое программирование.

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

Источник

Моки — JS: Продвинутое тестирование

В тестировании очень популярен «мокинг». Технически он похож на стабинг, и из-за этого их часто путают (специально или ненамеренно). Но всё же они служат разным целям и используются в разных ситуациях. Разберёмся, что это такое, и когда он нужен.

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

В некоторых ситуациях требуется кое-что другое. Не результат работы функции, а то, что она выполняет нужное нам действие, например, шлёт правильный HTTP-запрос с правильными параметрами. Для этого понадобятся моки. Моки проверяют, как выполняется код.

Это и называется мокинг. Мок проверяет, что какой-то код выполнился определённым образом. Это может быть вызов функции, HTTP-запрос и тому подобное. Задача мока убедиться в том, что это произошло, и в том, как конкретно это произошло, например, что в функцию были переданы конкретные данные.

Что даёт нам такая проверка? В данном случае мало что. Да, мы убеждаемся, что вызов был, но само по себе это ещё ни о чём не говорит. Так когда же полезны моки?

Представьте, что мы бы разрабатывали библиотеку @octokit/rest, ту самую, что выполняет запросы к GitHub API. Вся суть этой библиотеки в том, чтобы выполнить правильные запросы с правильными параметрами. Поэтому там нужно обязательно проверять выполнение запросов с указанием точных URL-адресов. Только в таком случае можно быть уверенными, что она выполняет верные запросы.

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

Функции

Моки довольно часто используют с функциями (методами). К примеру они могут проверять:

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

Так как мы изучаем Jest, то для создания моков воспользуемся встроенным механизмом Jest. В других фреймворках могут быть свои встроенные механизмы. Кроме того, как мы убедились выше, существуют специализированные библиотеки для моков и стабов.

Объекты

Через этот мок можно узнать любую информацию обо всех экземплярах:

Преимущества и недостатки

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

Там, где возможно использование реального кода, используйте реальный. Там, где возможно убедиться в работе кода без моков, делайте это без моков. Излишний мокинг делает тесты бесполезными, а стоимость их поддержки высокой. Идеальные тесты – тесты методом чёрного ящика.

Домашнее задание

Загрузите домашнее задание с помощью команды:

Источник

Моки и явные контракты

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

Ниже представлен вольный перевод статьи, в которой José Valim — создатель языка Elixir — высказал своё мнение на проблему использования моков, с которым я полностью согласен.

Несколько дней назад я поделился своими мыслями по поводу моков в Twitter:

моки в тестировании что это. Смотреть фото моки в тестировании что это. Смотреть картинку моки в тестировании что это. Картинка про моки в тестировании что это. Фото моки в тестировании что это

Мок — полезный инструмент в тестировании, но имеющиеся тестовые библиотеки и фреймворки зачастую приводят к злоупотреблению этим инструментом. Ниже мы рассмотрим лучший способ использования моков.

Что такое мок?

Воспользуемся определением из англоязычной википедии: мок — настраиваемый объект, который имитирует поведение реального объекта. Я сделаю акцент на этом позже, но для меня мок — это всегда существительное, а не глагол [для наглядности, глагол mock везде будет переводиться как «замокать» — прим. перев.].

На примере внешнего API

Давайте рассмотрим стандартный пример из реальной жизни: внешнее API.

Представьте, что вы хотите использовать Twitter API в веб-приложении на фреймворке Phoenix или Rails. В приложение приходит запрос, который перенаправляется в контроллер, который, в свою очередь, делает запрос к внешнему API. Вызов внешнего API происходит прямо в контроллере:

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

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

Решение

В Elixir все приложения имеют конфигурационные файлы и механизм для их чтения. Используем этот механизм, чтобы настроить клиент Twitter’a для различных окружений. Код контроллера теперь будет выглядеть следующим образом:

Соответствующие настройки для различных окружений:

Сейчас мы можем выбрать лучшую стратегию получения данных из Twitter для каждого из окружений. Sandbox может быть полезен, если Twitter предоставляет какой-нибудь sandbox для разработки. Наша замоканная версия HTTPClient позволяла избежать реальных HTTP-запросов. Реализация этой же функциональности в данном случае:

Код получился простым и чистым, а сильной внешней зависимости от HTTPClient больше нет. MyApp.Twitter.InMemory является моком, то есть существительным, и для его создания вам не нужны никакие библиотеки!

Необходимость явных контрактов

Мок предназначен для замены реального объекта, а значит будет эффективен только в том случае, когда поведение реального объекта определено явно. Иначе, вы можете оказаться в ситуации, когда мок начнет становиться все сложнее, увеличивая зависимость между тестируемыми компонентами. Без явного контракта заметить это будет сложно.

Мы уже имеем три реализации Twitter API и лучше сделать их контракты явными. В Elixir описать явный контракт можно с помощью behaviour:

Теперь добавьте @behaviour MyApp.Twitter в каждый модуль, который реализует этот контракт, и Elixir поможет вам создать ожидаемый API.

В Elixir мы полагаемся на такие behaviours постоянно: когда используем Plug, когда работаем с базой данных в Ecto, когда тестируем Phoenix channels и так далее.

Тестирование границ

Сначала, когда явные контракты отсутствовали, границы приложения выглядели так:

Поэтому изменение HTTPClient могло приводить к падению интеграционных тестов. Сейчас приложение зависит от контракта и только одна реализация этого контракта работает с HTTP:

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

Лично я бы протестировал MyApp.Twitter.HTTP на реальном Twitter API, запуская эти тесты по-необходимости во время разработки и каждый раз при сборке проекта. Система тегов в ExUnit — библиотеке для тестирования в Elixir — реализует такое поведение:

Исключим тесты с Twitter API:

При необходимости включим их в общий тестовый прогон:

Также можно запустить их отдельно:

Вместо создания мока HTTPClient можно поднять dummy-сервер, который будет эмулировать Twitter API. bypass — один из проектов, который может в этом помочь. Все возможные варианты вы должны обсудить со своей командой.

Примечания

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

Создание «тестируемого» кода

Получается, предложенное решение делает production-код более «тестируемым», но создает необходимость ходить в конфигурацию приложения на каждый вызов функции? Наличие ненужного оверхэда, чтобы сделать что-то «тестируемым», не похоже на хорошее решение.

Я бы сказал, что речь идет не о создании «тестируемого» кода, а об улучшении дизайна [от англ. design of your code — прим. перев.].

Тест — это пользователь вашего API, как и любой другой код, который вы пишите. Одна из идей TDD заключается в том, что тесты — это код и ничем не отличаются от кода. Если вы говорите: «Я не хочу делать мой код тестируемым», это означает «Я не хочу уменьшать зависимость между компонентами» или «Я не хочу думать о контракте (интерфейсе) этих компонентов».

Нет ничего плохого в нежелании уменьшать зависимость между компонентами. Например, если речь идет о модуле работы с URI [имеется ввиду модуль URI для Elixir — прим. перев.]. Но если мы говорим о чем-то таком же сложном, как внешнее API, определение явного контракта и наличие возможности заменять реализацию этого контракта сделает ваш код удобным и простым в сопровождении.

Кроме того, оверхэд минимален, так как конфигурация Elixir-приложения хранится в ETS, а значит вычитывается прямо из памяти.

Локальные моки

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

Можно избавиться от зависимости, передав её как аргумент. В данном случае будет достаточно передачи анонимной функции:

Тест будет выглядеть следующим образом:

Или, как было описано ранее, можно определить контракт и передать модуль целиком:

Вы также можете представить зависимость в виде data structure и определить контракт с помощью protocol.

Мок — это существительное

Лучше думать о моках как о существительных. Вместо того, чтобы мокать API (мокать — глагол), нужно создать мок (мок — существительное), который реализует необходимый API.

Библиотеки для создания моков

После прочитанного у вас может возникнуть вопрос: «Нужно ли отказываться от библиотек для создания моков?»

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

Однако, есть библиотеки для создания моков, которые не подталкивают вас на использование описанных выше анти-паттернов. Такие библиотеки предоставляют «мок-объекты» или «мок-модули», которые передаются в тестируемую систему в качестве аргумента и собирают информацию о количестве вызовов мока и о том, с какими аргументами он был вызван.

Заключение

Одна из задач тестирования системы — нахождение правильных контрактов и границ между компонентами. Использование моков только в случае наличия явного контракта позволит вам:

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

Источник

Модуль Mock: макеты-пустышки в тестировании

Mock на английском значит «имитация», «подделка». Модуль с таким названием помогает сильно упростить тесты модулей на Питоне.

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

Такое поведение — уже не надувные ракетные установки, а целая надувная земля, вокруг которой могут летать испытуемые ракеты и самолёты.

моки в тестировании что это. Смотреть фото моки в тестировании что это. Смотреть картинку моки в тестировании что это. Картинка про моки в тестировании что это. Фото моки в тестировании что это

На Хабре пакет Mock упоминали только в одном комментарии. В принципе, подменять объекты в других модулях, что называется monkey patch, нам иногда в работе приходится. Но в отличие от ручных патчей, Mock умеет делать очень сложные подстановки, пачками и цепочками вызова, а также убраться за собой, не оставляя побочных эффектов.

Пакет занимает меньше 1 мегабайта и устанавливается очень просто:

$ pip install mock
или
$ easy_install mock

И теперь им можно пользоваться.

Подмена функции

Скажем, наша функция считает что-то, причём очень долго:

Пример надуманный и примитивный, но что-то подобное может встретиться: внутри делаются большие вычисления, которые не хотелось бы повторять в тесте. И в данном примере хочется ввести строку покороче (чтобы число повторений в permutations, len(name), было меньше), но это запрещено. Можно было бы разбить функцию на две, вынести вызов permutations наружу, и в функцию передавать её вывод, но можно сделать по-другому.

Вместо переписывания кода, мы можем просто «пропатчить» функцию permutations на время вызова, задать только определённый вывод и вызвать какой-то код:

Заметьте: print вызван вместо 42! / (42 — 10)! раз всего 3, то есть цикл пробежался по xrange(3), который мы подставили.

Кроме того, после выхода из контекста with функция itertools.permutations вернулась в своё нормальное состояние.

Отладочная информация

Допустим, нужно проверить, что происходит с объектом, который мы передали функции. В той ли последовательности, с теми ли параметрами вызываются методы, к тем ли атрибутам обращаются. Для нужно просто запустить в неё объект Mock, который запишет всё, что происходит.

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

моки в тестировании что это. Смотреть фото моки в тестировании что это. Смотреть картинку моки в тестировании что это. Картинка про моки в тестировании что это. Фото моки в тестировании что это

У объекта Mock есть несколько атрибутов с информацией о вызовах:

В нашем примере выше можно убедиться, что функция real() правильно вызывает permutations. Чтобы ещё точнее проверить, например, в автоматических тестах, можно вызвать один из методов assert_*:

Синтаксический сахар

Для юнит-тестов в Джанго пригодится то, что patch работает как декоратор:

Макеты атрибутов и цепочек вызова

Иногда в Джанго нужно делать что-то с файлами, и лучше обойтись без сохранения их на диск или другое хранилище. Обычным путём мы бы наследовали от класса File и перезаписали бы некоторые свойства, что было бы громоздко. Но в Mock можно описать сразу и атрибуты, и цепочки вызова:

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

Как же эти цепочки атрибутов работают?

Как видите, обращение к атрибуту выдаёт ещё один экземпляр класса Mock, а повторное обращение к тому же атрибуту — снова тот же экземпляр. Атрибут может быть чем угодно, в том числе и функцией. Наконец, любой макет можно вызвать (скажем, вместо класса):

Это будет другой экземпляр, но если вызвать ещё раз, экземпляр будет тем же самым. Так мы можем назначить этим объектам некоторые свойства, после чего передать этот объект в тестируемый код, и они там будут считаны.

Если мы назначим атрибуту значение, то никаких сюрпризов: при следующем обращении получим именно это значение:

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

Как ограничить гибкость макета

Как видите, можно обращаться к любому атрибуту макета, не вызывая ошибки AttributeError. У этого удобства есть обратная сторона: что если мы поменяем API, например, переименуем метод, а функция, работающая с нашим классом, будет обращаться к прежнему методу? Код на самом деле не работает, а тест выполняется без ошибок. Для этого можно задать спецификацию объекта в параметре spec (либо классу Mock, либо patch), и макет будет вызывать ошибку при обращении к несуществующим свойствам:

Теперь если мы переименуем model или shoot у танчика, но забудем исправить tank_sugar, тест не выполнится.

Как сделать макет умнее

Хорошо, допустим, Mock умеет заменять нужные объекты на ненужные и подменять вывод. А можно ли подменить функцию на что-то более сложное, чем значение (return_value)? Есть 2 пути:

Кстати, не нужно писать в simple_function контрольный вывод, потому что, как сказано выше, в конце кода в объекте mock_class можно считать method_calls.

Подмена встроенных функций

Сам по себе Mock не может заменить встроенные в язык функции (len, iter), но может сделать макет с нужными «волшебными» функциями. Например, вот мы делаем макет файла:

Для множества подобных случаев, когда требуется эмулировать стандартный объект (список, файл, число), есть класс MagicMock с набором значений, пригодных для тестов.

Где Mock не работает

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

Бывает так, что нужно тестировать связку двух модулей, нового и старого, и в связке много перекрёстных вызовов. Нужно внимательно смотреть: если мы постепенно начинаем переписывать поведение целого модуля, пора остановиться — код теста жёстко сввязан с кодом модуля, и при каждом изменении в рабочем коде придётся менять и тесты. Кроме того не стоит пытаться написать целый модуль-макет вместо старого.

По моему личному опыту, Mock может конфликтовать с отладчиками, например, в PuDB случалась бесконечная рекурсия. IPDB работал нормально, поэтому тесты проекта мы выполняли с IPDB, а просто код — на PuDB.

Выводы

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

Можно выбросить из тестов всё лишнее, всё занимающее время и ресурсы, оставив работать только тот код, который нужно проверить.

Настройки макетов где нужно жёсткие (spec), где нужно гибкие, и патчи не оставляют за собой следов.

Источник

Моки и стабы

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

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

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

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

ПРИМЕЧАНИЕ
Не нужно бросать в меня камнями с криками «Да кто сегодня вообще такую хрень написать можно? Ведь уже столько всего написано о вреде такого подхода, да и вообще, у нас есть юнити-шмунити и другие полезности, так что это нереальный баян двадцатилетней давности!». Кстати, да, это баян, но, во-первых, речь не юнитях и других контейнерах, а о базовых принципах, а во-вторых, подобное «интеграционное» тестирование все еще невероятно популярно, во всяком случае, среди многих моих «зарубежных» коллег.

Создания «швов» для тестирования приложения

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

Пока что написание модульного теста все еще остается затруднительным, но становится понятным, как можно достаточно просто «подделать» реальную реализацию класса LastUsernameProvider и сымитировать нужное для нас поведение. Достаточно выделить методы этого класса в отдельный интерфейс или просто сделать их виртуальными и переопределить в наследнике. После чего останется лишь «прикрутить» нужный нам объект в нашу вью-модель.

Даже не прибегая ни к каким сторонним библиотекам для «инджекта» зависимостей мы можем сделать это самостоятельно несколько простыми способами. Нужную зависимость можно передать через дополнительный конструктор, через свойство или создать фабричный метод, который будет возвращать интерфейс ILastUsernmameProvider.

Давайте рассмотрим вариант с конструктором, который является довольно простым и популярным (при небольшом количестве внешних зависимостей он работает просто прекрасно).

Поскольку дополнительный конструктор является внутренним (internal), то он доступен только внутри этой сборке, а также «дружеской» сборке юнит-тестов. Конечно, если тестируемые классы являются внутренними не будет не какой, но поскольку все «клиенты» внутреннего класса находятся в одной сборке, то и контролировать их проще. Подобный подход, основанный на добавлении внутреннего метода для установки «фальшивого» поведения является разумным компромиссом упрощения тестирования кода, не налагая ограничения на использования более сложных механизмов управления зависимостями, типа IoC контейнеров.

ПРИМЕЧАНИЕ
Одним из недостатков при работе с интерфейсами является падение читабельности, поскольку не понятно, сколько реализаций интерфейса существует и где находится реализация того или иного метода интерфейса. Такие инструменты, как Решарпер существенно смягчают эту проблему, поскольку поддерживают не только навигацию к объявлению метода (Go To Declaration), но также и навигацию к реализации метода (Go To Implementation):

моки в тестировании что это. Смотреть фото моки в тестировании что это. Смотреть картинку моки в тестировании что это. Картинка про моки в тестировании что это. Фото моки в тестировании что это

Проверка состояния vs проверка поведения

Теперь давайте попробуем написать юнит-тест вначале для конструктора класса LoginViewModel, который получает имя последнего залогиненного пользователя, а потом юнит-тест для метода Login, после выполнения которого, имя последнего пользователя должно быть сохранено.

Для нормальной реализации этих тестов нам нужна «фейковая» реализация интерфейса, при этом в первом случае, нам нужно вернуть произвольное имя последнего пользователя в методе ReadLastUserName, а во втором случае – удостовериться, что вызван метод SaveLastUserName.

Именно в этом и отличаются два типа «фейковых» классов: стабы предназначены для получения нужного состояния тестируемого объекта, а моки применяются для проверки ожидаемого поведения тестируемого объекта.

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

У моков же другая роль. Моки «подсовываются» тестируемому объекту, но не для того, чтобы создать требуемое окружение (хотя они могут выполнять и эту роль), а прежде всего для того, чтобы потом можно было проверить, что тестируемый объект выполнил требуемые действия. (Именно поэтому такой вид тестирования называется behaviortesting, в отличие от стабов, которые применяются для statebasedtesting).

А зачем мне знать об этих отличиях?

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

Однако рано или поздно вам может надоесть это чудесное занятие по ручной реализации интерфейсов и вы обратите свое внимание на один из Isolation фреймворков, таких как Rhino Mocks, Moq или Microsoft Moles. Там эти термины встретятся обязательно и понимание отличий между этими типами фейков вам очень пригодится.

Я осознанно не касался ни одного из этих фреймворков, поскольку каждый из них заслуживает отдельной статьи и ИМО лишь усложнит понимание этих понятий. Но если вам все же интересно посмотреть на некоторые из этих фреймворков более подробно, то об одном из них я писал более подробно: “Microsoft Moles”.

Источник

Добавить комментарий

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