vertigra / mock-object-conspect.md
Введение в mock-объекты. Классификация.
Часто тестируемый метод может вызывать методы других классов, которые в данном случае тестировать не нужно. Unit-тест потому и называется модульным, что тестирует отдельные модули, а не их взаимодействие. Причем, чем меньше тестируемый модуль – тем лучше с точки зрения будущей поддержки тестов. Для тестирования взаимодействия используются интеграционные тесты, где вы уже тестируете скорее полные use cases, а не отдельную функциональность.
Однако наши классы очень часто используют другие классы в своей работе. Например, слой бизнес логики (Business Logic layer) часто работает с другими объектами бизнес логики или обращается к слою доступа к данным (Data Access layer). В трехслойной архитектуре веб-приложений это вообще постоянный процесс: Presentation layer обращается к Business Logic layer, тот, в свою очередь, к Data Access layer, а Data Access layer – к базе данных. Как же тестировать подобный код, если вызов одного метода влечет за собой цепочку вплоть до базы данных?
В таких случаях на помощь приходят так называемые mock-объекты, предназначенные для симуляции поведения реальных объектов во время тестирования. Вообще, понятие mock-объект достаточно широко: оно может, с одной стороны, обозначать любые тест-дублеры (Test Doubles) или конкретный вид этих дублеров – mock-объекты.
Понятие тест-дублеров введено неким Gerard Meszaros в своей книге «XUnit Test Patterns». Джерард и Мартин делят все тест-дублеры на 4 группы:
Предположим, что вам нужно протестировать метод Foo() класса TestFoo, который делает вызов другого метода Bar() класса TestBar. Предположим, что метод Bar() принимает какой-нибудь объект класса Bla в качестве параметра и потом ничего особого с ним не делает. В таком случае имеет смысл создать пустой объект Bla, передать его в класс TestFoo (сделать это можно при помощи широко применяемого паттерна Dependency Injection или каким-либо другим приемлемым способом), а затем уже Foo() при тестировании сам вызовет метод TestBar.Bar() с переданным пустым объектом. Это и есть иллюстрация использования dummy-объекта в unit-тестировании.
Метод Bar() выполняет какие-то действия с ним (допустим, Bar() сохраняет данные в базу или вызывает веб-сервис, а мы этого не хотим). В таких случаях наш объект класса TestBar должен быть уже не таким глупым. Мы должны научить его в ответ на запрос сохранения данных просто выполнить какой-то простой код (допустим, сохранение во внутреннюю коллекцию). В таких случаях можно выделить интерфейс ITestBar, который будет реализовывать класс TestBar и наш дополнительный класс FakeBar. При unit-тестировании мы просто будем создавать объект класса FakeBar и передавать его в класс с методом Foo() через интерфейс. Естественно, при этом класс Bar будет по-прежнему создаваться в реальном приложении, а FakeBar будет использован лишь в тестировании. Это иллюстрация fake-объекта
Stub-объекты (стабы) – это типичные заглушки. Они ничего полезного не делают и умеют лишь возвращать определенные данные в ответ на вызовы своих методов. В нашем примере стаб бы подменял класс TestBar и в ответ на вызов Bar() просто бы возвращал какие-то левые данные. При этом внутренняя реализация реального метода Bar() бы просто не вызывалась. Реализуется этот подход через интерфейс и создание дополнительного класса StubBar, либо просто через создание StubBar, который является унаследованным от TestBar. В принципе, реализация очень похожа на fake-объект с тем лишь исключением, что стаб ничего полезного, кроме постоянного возвращения каких-то константных данных не требует. Типичная заглушка. Стабам позволяется лишь сохранять у себя внутри какие-нибудь данные, удостоверяющие, что вызовы были произведены или содержащие копии переданных параметров, которые затем может проверить тест.
Mock-объект (мок), в свою очередь, является, грубо говоря, более умной реализацией заглушки, которая уже не просто возвращает предустановленные данные, но еще и записывает все вызовы, которые проходят через нее, чтобы вы могли дальше в unit-тесте проверить, что именно эти методы вот этих вот классов были вызваны тестируемым методом и именно в такой последовательности (хотя учет последовательности и строгость проверки, в принципе, настраиваемая вещь). То есть мы можем сделать мок MockFoo, который будет каким-то образом вызывать реальный метод Foo() класса TestFoo и затем смотреть, какие вызовы тот сделал. Или сделать мок MockBar и затем проверить, что при вызове метода Foo() реально произошел вызов метода Bar() с нужными нам параметрами.
Unit-тестирование условно делится на два подхода:
То есть в state-based testing нас интересует в основном, в какое состояние перешел объект после вызова тестируемого метода, или, что более часто встречается, что в реальности вернул наш метод и правилен ли этот результат. Подобные проверки проводятся при помощи вызова методов класса Assert различных unit-тест фреймворков: Assert.AreEqual(), Assert.That(), Assert.IsNull() и т.д.
В interaction testing нас интересует прежде всего не статическое состояние объекта, а те динамические вызовы методов, которые происходят у него внутри. То есть для нашего примера с классами TestFoo и TestBar мы будем проверять, что тестируемый метод Foo() действительно вызвал метод Bar() класса TestBar, а не то, что он при этом вернул и в какое состояние перешел. Как правило, в случае подобного тестирования программисты используют специальные mock-фреймворки (TypeMock.Net, EasyMock.Net, MoQ, Rhino Mocks, NMock2), которые содержат определенные конструкции для записи ожиданий и их последующей проверки через методы Verify(), VerifyAll(), VerifyAllExpectations() или других (в зависимости от конкретного фреймворка).
Примеры используют NUnit и Rhino Mocks, хотя на их месте с небольшим изменением синтаксиса может оказаться почти любая другая пара фреймворков.
Пример тестирования с использованием стаба для state-based тестирования:
Пара пояснений по коду. Сначала мы создаем объект типа Order, затем – стаб для класса Warehouse. После этого мы при помощи mock-фреймворка говорим, что при вызове метода HasInventory с определенными параметрами этот метод должен нам вернуть true. Аналогичным образом переопределяем поведение метода Remove (а то еще вызовет реальный и будет бяка). Далее идет вызов метода Fill() с переданным стабом, после чего проверяется, что свойство IsFilled установлено в true. Как видите, ничего сложного. Однако данный тест обладает некоторыми недостатками. Во-первых, непонятно, что делать, если в тестируемом объекте нет свойства, аналогичного IsFilled. Как проверять правильность выполнения кода? Во-вторых, непонятно, что случится, если программист удалит или закомментирует вызов следующей строчки в коде метода Fill():
IsFilled устанавливается в true, тест проходит, но код-то уже не работает!
Обе эти проблемы легко разрешаются, если мы воспользуемся interaction тестированием с использованием мока. Для этого напишем другой тест:
Начало теста аналогичное, затем идет создание мока Warehouse, после чего идет несколько вызовов метода Expect с теми же параметрами, что и в предыдущем тесте. При помощи этого метода мы говорим моку, что мы ожидаем вызова этих методов с такими параметрами и нам в ответ на их вызовы нужно вернуть такие-то значения. Затем идет вызов метода Replay(), который переводит мок из режима записи ожиданий в режим их проверки, то есть запуска тестового метода. Все моки имеют несколько режимов работы (Record, Replay, Verify), это распространенный подход. Далее непосредственно запуск, проверка IsFilled и вызов нового для нас метода VerifyAllExpectations(). Последний как раз и делает всю работу по проверке вызовов методов, параметров и т.д. Теперь, если метод Remove оказался закомментированным, тест не пройдет. Кроме того, нам уже не так важна проверка состояния объекта Order. Если бы свойства IsFilled не было, ничего бы не изменилось, а так мы лишь проверяем, что оно было установлено в соответствии с алгоритмом. Теперь немного поэкспериментируем с кодом. Что, если мы уберем второй Expect или поменяем их местами? Есть несколько режимов строгости проверки, которые задаются через конструктор класса Mock, который также можно использовать для создания мока. В Rhino Mocks есть три уровня строгости: Loose, Strict и Default (Loose). В Loose-режиме мок проверяет лишь то, что все ожидаемые методы были вызваны из тестируемого метода, в то время как в Strict-режиме проверяется также, что не было любых других вызовов и что порядок вызову соответствует порядку ожиданий. В других фреймворках иногда есть и другие режимы. Таким образом, в нашем случае при изменении порядка тест бы прошел, но в Strict-режиме – уже нет. Еще один момент, который показывает отличие методов Expect от методов Stub (в моке они также доступны): методы, зарегистрированные в моке при помощи метода Stub невидимы для метода VerifyAllExpectations. То есть, если нужна проверка вызовов – используйте Expect. Также стоит отметить, что при помощи дополнительных методов типа Return вы можете не только указывать возвращаемые значения, но еще генерировать exception’ы (Throw), вызывать настоящий метод (CallOriginalMethod), задавать ограничения на параметры (Constraints), вызывать дополнительные методы (Callback, Do), работать со свойствами и событиями. В общем, список потрясающий.
PHPUnit: Mock объекты
Довольно часто при написании модульных тестов нам приходится сталкиваться с тем, что тестируемый класс зависит от данных из внешних источников, состояние которых мы не можем контролировать. К таким источникам можно отнести далеко расположенную общедоступную базу данных или службу, датчик какого-нибудь физического процесса и пр. Либо нам необходимо убедиться, что некие действия выполняются в строго определенном порядке. В этих случаях к нам на помощь приходят Mock объекты (mock в переводе с английского — пародия), позволяя тестировать классы в изоляции от внешних зависимостей. Использованию Mock объектов в PHPUnit посвящается эта статья.
В качестве используемого примера возьмем следующее описание класса:
Объекты этого класса предназначены для отображения на неком устройстве одного из трех состояний погоды в зависимости от температуры окружающей среды. В момент написания кода ни устройство для отображения результата, ни датчик температуры недоступны, и попытка обращения к ним может привести к сбою в программе.
В простейшем случае для проверки логики мы можем отнаследоваться от указанного класса, подменить заглушками методы, которые обращаются к неподключенным устройствам, и провести модульное тестирование на экземпляре потомка. Примерно так же реализованы Mock объекты в PHPUnit, где при этом предоставляется дополнительное удобство в виде встроенного API.
Получение Mock объекта
Для получения экземпляра Mock объекта используется метод getMock():
Как видите, получить нужный нам Mock объект очень просто. По-умолчанию, все методы в нем будут подменены заглушками, которые ничего не делают и всегда возвращают null.
Параметры вызова getMock
Передача строителю getMock() в качестве второго аргумента значения null приведет к тому, что будет возвращен Mock объект вообще без подмен.
getMockBuilder
Для тех, кому приятнее писать в цепном стиле, PHPUnit предлагает соответствущий конструктор:
Цепочка всегда должна начинаться с метода getMockBuilder() и закачиваться методом getMock() — это единственные звенья цепи, которые являются обязательными.
Дополнительные способы получения Mock объектов
Ожидание вызова метода
PHPUnit позволяет нам контроллировать количество и порядок вызовов подмененных методов. Для этого используется конструкция expects() с последующим указанием нужного метода при помощи method(). В качестве примера обратимся к классу, приведенному в начале статьи, и напишем для него вот такой тест:
Результат выполнения этого теста будет успешным, если при вызове метода process() произойдет однократный вызов трех перечисленных методов: getTemperature(), getWord(), showWord(). Обратите внимание, что в тесте проверка вызова getWord() стоит после проверки вызова showWord(), хотя в тестируемом методе наоборот. Все верно, ошибки здесь нет. Для контроля порядка вызова методов в PHPUnit используется другая конструкция — at(). Поправим немного код нашего теста так чтобы PHPUnit проверил заодно очередность вызова методов:
Помимо упомянутых once() и at() для тестирования ожиданий вызовов в PHPUnit есть также следующие конструкции: any(), never(), atLeastOnce() и exactly($count). Их названия говорят сами за себя.
Переопределение возвращаемого результата
Безусловно самой полезной функцией Mock объектов является возможность эмуляции возвращаемого результата подмененными методами. Снова обратимся к методу process() нашего класса. Мы видим там обращение к датчику температуры — getTemperature(). Но мы также помним, что на самом деле датчика у нас нет. Хотя даже если бы он у нас был, не будем же мы охлаждать его ниже 15 градусов или нагревать выше 25 для того, чтобы протестировать все возможные ситуации. Как вы уже догадались, в этом случае на помощь к нам приходят Mock объекты. Мы можем заставить интересующий нас метод вернуть любой результат какой захотим при помощи кострукции will(). Вот пример:
Проверка указанных аргументов
Еще одной полезной для тестирования возможностью Mock объектов является проверка аргументов, указанных при вызове подмененного метода, при помощи конструкции with():
Теперь, когда мы полностью ознакомились с возможностями Mock объектов в PHPUnit, мы можем провести окончательное тестирование нашего класса:
UPD: VolCh справедливо заметил, что написание тестов, наподобие представленного выше, является антипаттерном. Поэтому приведенный пример стоит рассматаривать исключительно в целях ознакомления с возможностями Mock объектов в PHPUnit.
Подмена статических методов
Начиная с PHPUnit версии 3.5 стала возможной подмена статических методов при помощи статической конструкции staticExpects():
Мок-сервер для автоматизации мобильного тестирования
Работая над последним проектом, столкнулся с тестированием мобильного приложения, связанного на уровне бизнес-логики с различными сторонними сервисами. Тестирование этих сервисов не входило в мою задачу, однако проблемы с их API блокировали работу по самому приложению – тесты падали не из-за проблем внутри, а из-за неработоспособности API, даже не доходя до проверки нужной функциональности.
Традиционно для тестирования таких приложений используются стенды. Но они не всегда работают нормально, и это мешает работе. В качестве альтернативного решения я использовал моки. Об этом тернистом пути и хочу рассказать сегодня.

Чтобы не трогать код реального проекта (под NDA), для наглядности дальнейшего изложения я создал простой REST-клиент под Android, позволяющий отправлять на некий адрес HTTP-запросы (GET/POST) с необходимыми мне параметрами. Его-то мы и будем тестировать.
Код приложения-клиента, диспатчеров и тестов можно скачать с GitLab.
Какие существуют варианты?
Подходов к мокированию в моем случае существовало два:
Выбор мок-сервера
Разных инструментов существует много. Я пытался работать с несколькими и почти в каждом столкнулся с определенными проблемами:
Разбираем принцип работы
Текущая версия okhttpmockwebserver позволяет реализовать несколько сценариев работы:
Очередь ответов
Простейшая реализация мок-сервера – очередь ответов. До теста я определяю адрес и порт, где будет развернут мок-сервер, а также тот факт, что он будет работать по принципу очереди из сообщений – FIFO (first in first out).
Далее запускаю мок-сервер.
Тесты написаны с помощью фреймворка Espresso, предназначенного для исполнения действий в мобильных приложениях. В этом примере я выбираю типы запросов и отправляю их по очереди.
После запуска теста мок-сервер дает ему ответы в соответствии с прописанной очередью, и тест проходит без ошибок.
Реализация диспатчера
Диспатчер – это набор правил, по которым работает мок-сервер. Для удобства изложения я создал три разных диспатчера: SimpleDispatcher, OtherParamsDispatcher и ListingDispatcher.
SimpleDispatcher
Логика в этом примере простая: если приходит GET, я возвращаю сообщение, что это GET request. Если POST, возвращаю сообщение о POST request. В иных ситуациях возвращаю пустой запрос.
Исходники тестов с SimpleDispatcher можно найти в репозитории.
OtherParamsDispatcher
В данном случае я демонстрирую несколько вариантов условий.
За примерами тестов рекомендую обратиться к репозиторию.
ListingDispatcher
В результате я могу строить более сложные сочетания условий для стабов. Мне эта конструкция показалась более гибкой, хотя принцип в ее основе очень простой.
Но более всего в этом подходе мне понравилось, что я могу подставлять какие-то стабы прямо на ходу, если появилась необходимость что-то поменять в ответе мок-сервера на одном тесте. При тестировании больших проектов такая задача возникает довольно часто, например при проверке каких-то специфических сценариев.
Замену можно осуществить следующим образом:
Ради эксперимента я заменил стаб на POST:
В тесте выше я проверяю, что стаб был заменен.
Затем я добавил новый стаб, которого не было в дефолтных.
Request Verifier
Выше я показал проверку на простом примере. Точно такой же подход можно использовать для сложных JSON, в том числе для проверки всей структуры запроса (можно сравнивать на уровне JSON или распарсить JSON на объекты и проверить равенство на уровне объектов).
Итоги
В целом инструмент (okhttpmockwebserver) мне понравился, и я использую его на большом проекте. Безусловно, есть некоторые мелочи, которые я хотел бы изменить.
Например, мне не нравится, что приходится стучаться по локальному адресу (localhost:8080 в нашем примере) в конфигах своего приложения; возможно, я еще найду способ все настроить так, чтобы мок-сервер отвечал при попытке отправить запрос на любой адрес.
Также мне не хватает возможности переадресации запросов – когда мок-сервер отправляет запрос дальше, если у него нет для него подходящего стаба. В данном мок-сервере такого подхода нет. Впрочем, до их внедрения и не дошло, поскольку на данный момент в “боевом” проекте не стоит такой задачи.
Моки не кусаются! Осваиваем мокинг с 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 ответ.
Зависимость, которой мы пытались избежать путем разделения наших тестовых наборов, все еще существует.
Организация нашего тестирования, безусловно, стала лучше, но в конечном итоге ничего, что бы сделало наши тесты менее хрупки, не произошло.
Так ли это необходимо?
Кстати, пара слов о переходе из области сквозных тестов в область модульных тестов.
Наличие тестовых двойников — ключевой показатель того, что вы пишете модульные тесты.
Многие опытные тестировщики сразу же начинают новые проекты с модульных тестов (и моков), потому что они знают, что по мере роста их кодовой базы они будут сталкиваться с проблемой нестабильности тестов.
Модульные тесты обычно намного меньше, чем сквозные. Они могут быть настолько малы, что часто занимают не больше трех-четырех строк кода. Это делает их отличными кандидатами для таких практик социального кодирования, как парное и ансамблевое программирование.
Даже когда мы проводим модульное тестирование, тестовые двойники не всегда необходимы — это просто еще один инструмент в вашем наборе, который вы должны знать, когда и как применять.
Модуль 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), где нужно гибкие, и патчи не оставляют за собой следов.





