Что такое unit тесты

Unit-тесты, пытаемся писать правильно, чтобы потом не было мучительно больно

100 модульных тестов и приходится тратить продолжительное время на то чтобы заставить их работать вновь. Итак, приступим к «хорошим рекомендациям» при написании автоматических модульных тестов. Нет, я не буду капитаном очевидностью, в очередной раз описывая популярный стиль написания тестов под названием AAA (Arange-Act-Assert). Зато попытаюсь объяснить, чем отличается Mock от Stub-а и что далеко не все тестовые объекты — «моки».

Глобально модульные тесты можно условно поделить на две группы: тесты состояния (state based) и тесты взаимодействия (interaction tests).

Тесты состояния — тесты, проверяющие что вызываемый метод объекта отработал корректно, проверяя состояние тестируемого объекта после вызова метода.

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

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

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

Внешняя зависимость — это объект, с которым взаимодействует код и над которым нет прямого контроля. Для ликвидации внешних зависимостей в модульных тестах используются тестовые объекты, например такие как stubs (заглушки).

Стоит заметить что существует классический труд по модульным тестам за авторством Жерарда Месароша под названием «xUnit test patterns: refactoring test code«, в котором автор вводит аж 5 видов тестовых объектов, которые могут запросто запутать неподготовленного человека:

— dummy object, который обычно передается в тестируемый класс в качестве параметра, но не имеет поведения, с ним ничего не происходит, никакие методы не вызываются. Примером таких dummy-объектов являются new object(), null, «Ignored String» и т.д.

— test stub (заглушка), используется для получения данных из внешней зависимости, подменяя её. При этом игнорирует все данные, могущие поступать из тестируемого объекта в stub. Один из самых популярных видов тестовых объектов. Тестируемый объект использует чтение из конфигурационного файла? Передаем ему ConfigFileStub возвращающий тестовые строки конфигурации для избавления зависимости на файловую систему.

— test spy (тестовый шпион), используется для тестов взаимодействия, основной функцией является запись данных и вызовов, поступающих из тестируемого объекта для последующей проверки корректности вызова зависимого объекта. Позволяет проверить логику именно нашего тестируемого объекта, без проверок зависимых объектов.

— mock object (мок-объект), очень похож на тестовый шпион, однако не записывает последовательность вызовов с переданными параметрами для последующей проверки, а может сам выкидывать исключения при некорректно переданных данных. Т.е. именно мок-объект проверяет корректность поведения тестируемого объекта.

— fake object (фальшивый объект), используется в основном чтобы запускать (незапускаемые) тесты (быстрее) и ускорения их работы. Эдакая замена тяжеловесного внешнего зависимого объекта его легковесной реализацией. Основные примеры — эмулятор для конкретного приложения БД в памяти (fake database) или фальшивый вебсервис.

Roy Osherove в книге «The Art of Unit Testing» предлагает упростить данную классификацию и оставляет всего три типа тестовых объектов — Fakes, Stubs и Mocks. Причем Fake может быть как stub-ом, так и mock-ом, а тестовый шпион становится mock-ом. Хотя лично мне не очень нравится перемешивание тестового шпиона и мок-объекта.

Запутанно? Возможно. У данной статьи была задача показать, что написание правильных модульных тестов — достаточно сложная задача, и что не всё то mock, что создаётся в посредством mock-frameworks.

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

Источник

Зачем нужны юнит-тесты

Что такое unit тесты. Смотреть фото Что такое unit тесты. Смотреть картинку Что такое unit тесты. Картинка про Что такое unit тесты. Фото Что такое unit тесты

Многие разработчики говорят о юнит-тестах, но не всегда понятно, что они имеют в виду. Иногда неясно, чем они отличаются от других видов тестов, а порой совершенно непонятно их назначение.

Доказательство корректности кода

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

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

Отличие от других видов тестов

Все вышесказанное справедливо для любых тестов. Там даже не упомянуты юнит-тесты как таковые. Итак, в чем же их отличие?

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

Это основное отличие юнит-тестов от системных, когда тестированию подвергается вся система или подсистема, и от интеграционных, которые проверяют взаимодействие между модулями.

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

И все-таки, что такое юнит?

Часто встречается мнение, что юнит — это класс. Однако это не всегда верно. Например, в C++, где классы не обязательны.

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

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

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

Отсутствие сцепления необходимо для написания юнит-тестов.

Другие применения юнит-тестов

Кроме доказательства корректности, у юнит-тестов есть еще несколько применений.

Тесты как документация

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

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

Тем не менее, в этом обсуждении после поста про комментарии видно, что не все разделяют мое мнение на этот счет.

Разработка через тестирование

При разработке через тестирование (test-driven development, TDD) вы сначала пишете тесты, которые проверяют поведение вашего кода. При запуске они, конечно, провалятся (или даже не скомпилируются), поэтому ваша задача — написать код, который проходит эти тесты.

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

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

И, поскольку TDD предполагает, что нет участков кода, не покрытых тестами, все поведение написанного кода будет документировано.

Возможность лучше разобраться в коде

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

Источник

Unit тесты на практике

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

Итак…

Какими должны быть модульные тесты?

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

Какие требования необходимо соблюсти для того, чтобы обеспечить скорость выполнения модульных тестов?

Тесты должны быть небольшими

В идеальном случае — одно утверждение (assert) на тест. Чем меньше кусочек функциональности, покрываемый модульным тестом, тем быстрее тест будет выполняться.

Кстати, на тему оформления. Мне очень нравится подход, который формулируется как «arrange-act-assert».
Суть его заключается в том, чтобы в модульном тесте чётко определить предусловия (инициализация тестовых данных,
предварительные установки), действие (собственно то, что тестируется) и постусловия (что должно быть в
результате выполнения действия). Подобное оформление повышает читаемость теста и облегчает его
использование в качестве документации к тестируемой функциональности.

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

И тогда тест, оформленный подобным образом, может выглядеть примерно так (все имена вымышленные, совпадения случайны):

Тесты должны быть изолированы от окружения (БД, сеть, файловая система)

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

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

Случай 1. Слой доступа к данным (MS SQL Server)

Если в разработке проекта используется MS SQL сервер, то ответом на этот вопрос может быть использование установленного экземпляра MS SQL сервер (Express, Enterprise или Developer Edition) для разворачивания тестовой базы данных. Подобную базу данных можно создать с помощью стандартных механизмов, используемых в MS SQL Management Studio и поместить её в проект с модульными тестами. Общий подход к использованию такой базы данных заключается в разворачивании тестовой БД перед выполнением теста (например, в методе, отмеченном атрибутом SetUp в случае использования NUnit), наполнении БД тестовыми данными и проверками функциональности репозиториев или шлюзов на этих, заведомо известных, тестовых данных. Причём разворачиваться тестовая БД может как на жёстком диске, так и в памяти, используя приложения, создающие и управляющие RAM диском. Допустим, в проекте, над которым я работаю в данное время, используется приложение SoftPerfect RAM Disk. Использование RAM диска в модульных тестах позволяет снизить задержки, возникающие при операциях ввода/вывода, которые возникали бы при разворачивании тестовой БД на жёстком диске. Конечно, данный подход не идеален, так как требует внесения в окружение разработчика стороннего ПО. С другой стороны, если учесть, что среда для разработки разворачивается, как правило, один раз (ну, или достаточно редко), то это требование не кажется таким уж обременяющим. Да и выигрыш от использования такого подхода достаточно заманчив, ведь появляется возможность контролировать корректность работы одного из важнейших слоёв системы.

Кстати, если есть возможность использовать в модульных тестах LINQ2SQL и SMO для MS SQL Server, то можно воспользоваться следующим базовым классом для тестирования слоя доступа к данным:

После использования которого тесты на взаимодействие с БД будут выглядеть примерно так:

Вуаля! Мы получили возможность тестирования слоя доступа к БД.

Случай 2. ASP.NET MVC WebAPI

При тестировании WebAPI один из вопросов заключается в том, каким образом построить модульные тесты так, чтобы можно было протестировать вызов нужных методов нужных контроллеров с нужными аргументами при отправке запроса на определенный url. Если предположить, что ответственность контроллера заключается только в том, чтобы перенаправить вызов соответствующему классу или компоненту системы, то ответ на вопрос о тестировании контроллера сведется к тому, чтобы перед запуском тестов динамически построить некое окружение, в котором контроллеру можно было бы отправлять нужные HTTP запросы и, используя mock’и, проверить правильность настроенного роутинга. При этом совершенно не хочется использовать для разворачивания тестового окружения IIS. В идеале, тестовое окружение должно создаваться перед запуском каждого теста. Это поможет модульным тестам быть достаточно изолированными друг от друга. С IIS в этом плане было бы достаточно непросто.

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

Вот, собственно и все. Наши контроллеры покрыты модульными тестами.

Случай 3. Все остальное

А со всем остальным достаточно просто. Примеры модульных тестов на классы, которые содержат чистую логику, без взаимодействия с каким-либо внешним окружением, практически не отличаются от тех, которые предлагаются в популярной литературе типа «TDD by Example» Кента Бека. Поэтому каких-то особых хитростей здесь нет.

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

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

Источник

Школы юнит-тестирования

Что такое unit тесты. Смотреть фото Что такое unit тесты. Смотреть картинку Что такое unit тесты. Картинка про Что такое unit тесты. Фото Что такое unit тесты

Существуют две основные школы юнит-тестирования: классическая (ее также называют школой Детройта, или Чикаго) и лондонская (ее также называют мокистской школой, от слова mock).

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

Определение юнит-теста

Что же такое «юнит-тест»? Так называется автоматизированный тест, который:

проверяет правильность работы небольшого фрагмента кода (также называемого юнитом)

и поддерживая изоляцию от другого кода.

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

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

Что такое unit тесты. Смотреть фото Что такое unit тесты. Смотреть картинку Что такое unit тесты. Картинка про Что такое unit тесты. Фото Что такое unit тесты

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

Классический подход к изоляции не запрещает тестировать несколько классов одновременно, при условии что все они находятся в памяти и не обращаются к совместному состоянию (shared state), через которое тесты могут влиять на результат выполнения друг друга. Типичными примерами такого совместного состояния служат внепроцессные (out-of-process) зависимости — база данных, файловая система и т. д.

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

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

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

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

Ниже приведен тест, написанный в классическом и лондонском стиле.

А вот тот же тест, переписанный в лондонском стиле:

Фаза проверки тоже изменилась, и именно здесь кроется ключевое различие. Лондонский тест проверяет результат работы метода customer.Purchase так же, как и классический тест, но взаимодействие между Customer и Store теперь проверяется по-другому. В классическом тесте для этого используется состояние магазина. Лондонский же анализирует взаимодействия между Customer и Store: тест проверяет, какой метод и с какими параметрами Customer вызвал у Store. Для этого в мок передается вызываемый метод (x.RemoveInventory), а также сколько раз этот метод должен был вызываться в течение работы теста (Times.Once).

Отличия школ юнит-тестирования

К каким же именно отличиям приводит такое расхождение во мнениях о том, что является юнит-тестом?

Мы уже обсудили первое (и, пожалуй, самое главное) отличие — частоту использования моков. Следует отметить, что несмотря на повсеместное использование моков, лондонская школа все же позволяет использовать в тестах некоторые зависимости без их замены на заглушки. Основное различие здесь в том, является ли зависимость изменяемой: лондонская школа допускает не использовать моки для неизменяемых объектов.

В тесте, написанном в лондонском стиле, видно, что на мок был заменен только Store:

Причина как раз в том, что из трех аргументов метода Purchase только Store содержит внутреннее состояние, которое может изменяться со временем. Экземпляры Product (тип Product — перечисление (enum) C#) и число 5 неизменяемы.

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

Что изолируется

Размер юнита

Для чего используются моки

Любые изменяемые зависимости

Класс или набор классов

Внепроцессные (out-of-process) зависимости

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

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

Юнит-тестирование одного класса за раз

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

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

Такая тенденция понятна, но ошибочна.

Тесты не должны проверять *единицы кода* (units of code). Вместо этого они должны проверять *единицы поведения* (units of behavior) — нечто имеющее смысл для предметной области, а в идеале — нечто такое, полезность чего будет понятна бизнесу. Количество классов, необходимых для реализации такой единицы поведения, не имеет значения. Тест может охватывать как несколько классов, так и только один класс или даже всего один метод.

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

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

Пример связного рассказа:

«Когда я зову свою собаку, она идет ко мне».

Теперь сравните это со следующим рассказом:

«Когда я зову свою собаку, она сначала выставляет вперед левую переднюю лапу, потом правую переднюю лапу, поворачивает голову, начинает вилять хвостом. »

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

Юнит-тестирование большого графа взаимосвязанных классов

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

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

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

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

Выявление точного местонахождения ошибки

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

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

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

Различия в подходе к разработке через тестирование (TDD)

Лондонский стиль юнит-тестирования ведет к методологии TDD (Test-Driven Development) по схеме «снаружи внутрь» (outside-in): вы начинаете с тестов более высокого уровня, которые задают ожидания для всей системы. Используя моки, вы указываете, с какими зависимостями система должна взаимодействовать для достижения ожидаемого результата. Затем вы проходите по графу классов, пока не реализуете их все.

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

Классическая школа такой возможности не дает, потому что вам приходится иметь дело с реальными объектами в тестах. Вместо этого обычно используется подход по схеме «изнутри наружу» (inside-out или middle-out). В этом стиле вы начинаете с модели предметной области, а затем накладываете на нее дополнительные слои, пока программный код не станет пригодным для конечного пользователя.

Интеграционные тесты в двух школах

Лондонская и классическая школы также расходятся в определении интеграционного теста. Такое расхождение естественным образом вытекает из различий в их взглядах на вопрос изоляции.

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

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

Итоги

Итак, подведем итоги. Юнит-тестом называется автоматизированный тест, который:

проверяет правильность работы небольшого фрагмента кода (также называемого юнитом),

и поддерживая изоляцию от другого кода.

Различия между лондонской и классической школами юнит-тестирования проистекают из несогласия относительно того, что именно означает «изоляция».

Остальные отличия между школами:

Детализированность тестов — лондонская школа почти всегда тестирует только один класс за раз. Классическая школа может охватывать несколько тестов.

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

Test-Driven Development (TDD) — лондонская школа предпочитает подход outside-in, в то время как классическая — inside-out (или middle-out).

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

Минутка рекламы. Если вам интересны подходы к тестированию, вам наверняка будет интересно и на конференции Heisenbug (онлайн, 5-7 октября). Там будет множество докладов о тестировании в самых разных его проявлениях, описания части этих докладов уже есть на сайте конференции, билеты — там же.

Источник

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

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