Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Ruby
Code

Антипаттерны: тесты Rails

by
Length:LongLanguages:

Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

Эта статья представляет собой введение в передовые воды антипаттернов тестирования в Rails. Если вы еще новичок в Test-Driven-Development и хотите собрать пару очень ценных лучших практик, эта статья была точно написана для вас.

Темы

  • Let
  • Тайные гости
  • Неясные тесты
  • Медленные тесты
  • Фикстуры
  • Хрупкие тесты
  • Атрибуты данных

Let

Метод let helper в RSpec очень часто используется для создания переменных экземпляра, доступных между несколькими тестами. Будучи нетерпеливым учеником практики TDD, вы, вероятно, написали свою справедливую долю этих данных, но после этого практика может легко привести к появлению большого количества таинственных гостей - см. ниже - это определенно не то, что нам нужно!

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

Это становится хуже, когда вы начинаете соединять эти вещи. let утверждения, оштукатуренные во всех вложенных блоках describe, являются фаворитом во все времена. Я думаю, что это было не несправедливо назвать быстрым способов для повешивания. Более ограниченную область, как правило, легче понять и следовать.

Мы не хотим строить карточный домик с полу-глобальными let фикстурами, которые скрывают понимание и увеличивают шансы на разрыв соответствующих тестов. Шансы на создание качественного кода будут против нас при таком подходе. Извлечение общей настройки объекта также легче сделать с помощью простых старых руби методов или даже классов, если это необходимо.

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

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

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

Если вы пойдете по дороге использования let вы часто закончите с довольно жирными объектами, которые стараются сделать много тестов счастливыми одновременно. Конечно, вы можете создавать множество вариаций этих let вещей, но это, по-моему, делает их совершенно неважными. Почему бы не пойти на один шаг дальше, избегать препятствий и полагаться на Ruby без магии RSpec DSL?

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

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

Тайные гости

Тайные гости - RSpec DSL Puzzles. Некоторое время различные объекты, определенные через RSpec DSL let, не так уж сложно держать под контролем, но вскоре, когда набор тестов растет, вы приглашаете в своих спецификациях множество таинственных гостей. Это дает вашим будущим «я» и другим разработчикам ненужные контекстные головоломки.

Результатом станут неясные тесты, которые потребуют от вас полного режима Шерлока Холмса. Думаю, это звучит намного веселее, чем есть на самом деле. Итог, это пустая трата времени.

Тайные гости задают два проблемных вопроса:

  • Откуда этот объект?
  • Из чего именно состоит?

Этот блок описания для #top_agent не несет ясности и контекста. Какой агент задействован и о какой миссии мы говорим здесь? Это заставляет разработчиков охотиться за объектами, которые внезапно появляются в ваших тестах.

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

Решение довольно простое: вам нужны свежие «светильники» - локальные версии объектов именно с теми данными, которые вам нужны - и не более того! Factory Girl - хороший выбор для решения этой проблемы.

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

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

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

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

Неясные тесты

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

Мы создаем общий агент. Откуда мы знаем, что это 007? Мы также проверяем статус агента, но его также нигде не найти - ни в настройке, ни в явном виде на этапе проверки в нашем expect выражении. Отношения между double_o_seven.status и статусом миссии могут сбивать с толку, поскольку оно выходит из ниоткуда. Мы можем сделать лучше:

Опять же, здесь у нас есть все, что нам нужно рассказать. Все данные, которые нам нужны, находятся прямо перед нами.

Медленные тесты

Итак, вы начали заниматься Test-Driven-Development, и вы начали понимать, что он предлагает. Kudos, это здорово! Я уверен, что ни решение сделать это, ни курс обучения, чтобы добраться туда, не были куском пирога. Но то, что часто случается после этого начального шага, заключается в том, что вы очень стараетесь иметь полное тестовое покрытие, и вы начинаете понимать, что что-то не работает, когда скорость ваших тестов начинает вас раздражать.

Почему ваш тестовый набор становится все медленнее и медленнее, хотя вы думаете, что делаете все правильно? Чувствуете себя наказанным за написание тестов? Медленные тесты высасывают кучу времени! Есть пара проблем с ними. Самая важная проблема заключается в том, что медленные тесты приводят к пропуску тестов в долгосрочной перспективе. Как только вы окажетесь в том месте, где ваш тестовый набор потребует вечность для завершения,  вы будете гораздо более охотно будете думать о том, чтобы пропустить его. У меня есть более важные дела, чем ждать, пока он закончится. «И вы абсолютно правы, у вас есть все, что нужно делать.

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

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

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

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

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

Что мы можем сделать для ускорения тестов? Здесь важны два аспекта:

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

Максимально избегайте записи в базу данных

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

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

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

Постройте свои тесты с тестовой пирамидой в уме

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

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

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

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

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

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

Получение обратной связи от вашего приложения и тестов

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

Итак, какое волшебное число мы должны иметь в виду при выполнении тестов? Ну, разные люди расскажут вам о разных тестах. Я думаю, что выполнение менее 30 секунд - это очень разумное число, которое делает его очень вероятным для проведения полного теста на регулярной основе.  Это будет стоить того, и это заставит вас чувствовать себя намного более комфортно, потому что вы можете регулярно проводить проверки. Скорее всего, вы будете двигаться вперед намного быстрее.

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

Если вам нравится использовать Vim, у вас есть еще одна причина потратить некоторое время на то, чтобы стать более эффективным при использовании вашего редактора. Для редактора Vim доступно множество удобных инструментов. Я помню, что Sublime Text также предлагает запускать тесты из редактора, но кроме этого вам нужно провести немного исследований, чтобы узнать, на что способен ваш редактор. Аргумент, который вы часто слышите от энтузиастов TDD, заключается в том, что вы не хотите покидать свой редактор, потому что в целом вы будете тратить слишком много времени на это. Вы хотите оставаться намного больше в зоне и не терять ход мыслей, когда вы можете создать быстрый ярлык внутри вашего редактора кода, который будет запускать ваши тесты.

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

Последнее на дорожку. Используйте предварительный загрузчик, например Spring. Вы будете удивлены, сколько времени вы можете сэкономить, когда вам не нужно загружать Rails для каждого тестового прогона. Ваше приложение будет работать в фоновом режиме и не должно загружаться все время. Сделайте это!

Фикстуры

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

Фикстуры ActiveRecord - отличные примеры того, что в вашем тестовом наборе есть тонны Mystery Guests. В первые дни Rails и Ruby TDD фикстуры YAML были стандартом де-факто для настройки тестовых данных в вашем приложении. Они сыграли важную роль и помогли продвинуть отрасль вперед. В наши дни у них есть довольно плохая репутация.

YAML Фикстуры

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

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

Чтобы избежать нарушения ваших тестовых данных, когда происходят неизбежные изменения, разработчики с радостью приняли новые стратегии, предлагающие большую гибкость и динамичное поведение. Именно там появилась Factory Girl и поцеловала YAML на прощание.

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

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

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

Сложность - быстрорастущий враг, и фикстуры YAML вряд ли способны эффективно с ним справиться. В некотором роде, я думаю о фикстурах, как о let на стероидах. Вы не только размещаете их еще дальше - находясь в отдельном файле, вы также потенциально можете загружать больше инструментов, чем вам может понадобиться. ПОКОЙСЯ С МИРОМ!

Хрупкие тесты

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

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

В этом сценарии мы четко изменили локально состояние объекта, которое было определено в нашей настройке. Этот agent теперь является оператором ЦРУ и имеет другое имя. mission так же приходит из ниоткуда.

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

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

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

Атрибуты данных

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

Если вы жестко кодируете класс, например class='mission-wrapper' в своем тесте, и умный дизайнер решает изменить это плохое имя, ваш тест будет подвергнут неоправданно затронут. Разумеется, дизайнер не виноват. Как она узнает, что это влияет на часть вашего набора тестов - по крайней мере, это было маловероятно.

Мы ожидаем увидеть некоторый HTML-элемент на странице и пометить его с помощью data-role. У дизайнеров нет причин касаться этого, и вы защищены от хрупких тестов, которые происходят из-за изменений в стилях.

Это довольно эффективная и полезная стратегия, которая в принципе не стоит вам ничего. Единственное, что может потребоваться, - это короткая беседа с дизайнерами!

Финальные мысли

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

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

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

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.