Некачественный код может привести к множеству проблем, включая трудности в сопровождении, увеличение времени на исправление ошибок и снижение производительности приложения. Инструменты для обеспечения качества кода позволяют выявлять ошибки и проверять соответствие стандартам. К таким инструментам относятся линтеры, статические анализаторы и тесты.
В этом уроке мы рассмотрим статический анализатор кода и линтер RuboCop. Мы изучим его установку, настройку и использование для анализа кода, а также познакомимся с основами тестирования в Rails, что поможет обеспечить надежность и стабильность разрабатываемых приложений.
Статический анализатор RuboCop
RuboCop — это статический анализатор кода для Ruby, который помогает поддерживать стиль кода и следовать соглашениям в проекте. Он основан на Ruby Style Guide и предоставляет правила (копов), которые можно настраивать в зависимости от потребностей проекта. Основные возможности RuboCop включают:
-
Статический анализ: Проверка кода на соответствие стилю и стандартам.
-
Автоматическая корректировка: Возможность автоматически исправлять некоторые недочеты.
-
Настраиваемые правила: Возможность включать или отключать определенные правила в зависимости от требований проекта.
-
Интеграция с редакторами: Поддержка плагинов для популярных редакторов, что позволяет получать обратную связь в реальном времени.
Установка RuboCop
Чтобы установить RuboCop для Rails, нужно добавить его в Gemfile:
Так как в это dev-зависимость, то и добавляется Rucobop в окружение для разработки и для тест-окружения (для CI).
Дальше необходимо установить новую зависимость с помощью Bundler:
Проверим, что RuboCop установлен:
Файл конфигурации
RuboCop по умолчанию поставляется со своими правилами и проверяет весь проект. Чтобы применить тонкую настройку, мы можем создать файл .rubocop.yml и добавить туда правила проверок:
Ранее вместе с RuboCop мы добавили гемы rubocop-rails и rubocop-performance - это наборы правил для Rails-приложений. Чтобы их включить нужно их добавить в .rubocop.yml:
Обычно файлы конфигурации не пишут с нуля, а используют готовые, из проекта-шаблона или из предыдущего проекта. Например в проекте Hexlet CV мы можем взять конфиг и добавить к себе.
RuboCop установлен и настроен, теперь необходимо проверить проект на ошибки и соответствие стайл гайду.
Проверка кода
Если мы запустим RuboCop первый раз, мы увидим множество ошибок. Точное их количество будет в конце
Вывод команды bundle exec rubocop показывает, что было проверено 33 файла, и обнаружено 166 недочетов (offenses). Все недочеты помечены буквой "C", что указывает на то, что они являются проблемами стиля (Correctable) и их можно исправить автоматически.
RuboCop предоставляет возможность автоматической корректировки кода. Для этого используются две основные опции:
- Опция -a исправляет только те недочеты, которые не могут повредить бизнес-логике (например, форматирование кода).
- Опция -A может вносить изменения, которые потенциально могут повлиять на логику приложения. Но ее можно использовать без опасений на новом проекте, в котором еще нет никакой логики.
Вывод команды похож на тот, что вы получили при проверке, только здесь будут отмечены применённые исправления. Не все исправления можно выполнить автоматически, поэтому внимательно изучите отчет анализатора.
Тестирование
Rails предоставляет мощные инструменты для написания и выполнения тестов. Тесты обычно располагаются в директории test/, где они разделены по директориям:
-
test/models/ — для тестов моделей.
-
test/controllers/ — для тестов контроллеров.
-
test/integration/ — для интеграционных тестов.
-
test/system/ — для системных тестов.
Для большинства Rails-приложений тестов для контроллеров уже достаточно, чтобы обеспечить базовую проверку приложения. Однако в зависимости от сложности приложения и требований к качеству, может потребоваться также использование других типов тестов, таких как системные тесты.
Пример тестов контроллера резюме в Hexlet CV:
В этих тестах используется фреймворк тестирования Minitest с гемом power-assert.
Фикстуры
Фикстуры в Rails — это способ предварительной загрузки данных в базу данных для тестирования. Они позволяют создавать наборы данных, которые могут быть использованы в тестах, чтобы обеспечить предсказуемую и стабильную среду для выполнения тестов. Фикстуры особенно полезны там, где необходимо иметь определенные данные для проверки работы приложения.
Фикстуры обычно хранятся в формате YAML. Каждый файл фикстуры соответствует одной модели и содержит записи, которые будут загружены в базу данных перед выполнением тестов. В файле фикстуры каждая запись имеет уникальный идентификатор, который используется для ссылки на эту запись в тестах.
Фикстуры автоматически загружаются в базу данных перед выполнением тестов. Это происходит благодаря методу fixtures() в тестах, который указывает, какие фикстуры нужно загрузить.
В тестах можно обращаться к загруженным фикстурам по их идентификаторам:
Фикстуры позволяют легко создавать и управлять тестовыми данными. Особенно там, где требуется сложное состояние БД. Они обеспечивают консистентность данных между тестами. Фикстуры можно использовать с гемами faker или factory_bot_rails для того, чтобы упростить создание фикстур там, где не нужны конкретные значения.
Транзакционные тесты
Транзакционные тесты в Rails — это подход к тестированию, при котором каждый тест выполняется в рамках транзакции базы данных. Это позволяет изолировать тесты друг от друга и гарантировать, что изменения, внесенные в базу данных в ходе теста, не повлияют на другие тесты. После завершения теста транзакция откатывается, и база данных возвращается в исходное состояние.
Каждый тест выполняется в своей транзакции, что предотвращает "загрязнение" базы данных. Это особенно полезно, когда тесты изменяют данные, так как изменения не сохраняются после завершения теста. В Rails при использовании ActiveSupport::TestCase транзакционные тесты включены по умолчанию.
Пример транзакционного теста:
Пишем первый тест
Когда мы вызываем генератор для создания контроллера, то создается также класс для тестирования этого контроллера. Вызовем генератор для создания контроллера:
Получили пустой контроллер:
И тестовый класс без методов
Добавим обработчик в config/routes.rb
И добавим тест на добавленный роут
В контроллер добавим экшен:
И шаблон app/views/pages/index.html.erb, который будет выводиться экшеном:
Теперь мы готовы запустить тесты и проверить, что все работает. Запустить тесты можно командой bin/rails test:
Запуск тестов завершился успешно. Если на каком-то из этапов возникла бы ошибка, то тесты бы об этом сообщили. Например так выглядит ошибка, если нет экшена и шаблона:
binding.irb
В Ruby и Rails есть инструмент для отладки кода - binding.irb, который позволяет вам вставлять интерактивную консоль в любое место вашего кода. Это может быть особенно полезно для диагностики проблем, анализа состояния переменных и выполнения произвольного кода в контексте текущего выполнения программы.
Чтобы использовать irb, нужно вызвать binding.irb внутри кода:
Использовать binding.irb можно даже во время выполнения тестов
Заключение
В ходе урока мы познакомились с RuboCop — статическим анализатором кода для Ruby, который помогает поддерживать стиль и стандарты кода в проектах. Мы рассмотрели процесс установки и настройки RuboCop, включая создание конфигурационного файла .rubocop.yml для настройки правил проверки. Также мы изучили, как использовать RuboCop для анализа кода и автоматической корректировки недочетов.
Мы обсудили основы тестирования в Rails, включая использование фикстур и транзакционных тестов, а также написание простых тестов для контроллеров.
Рассмотрели инструмент binding.irb для отладки кода, который позволяет отлаживать и исправлять ошибки в приложении.
<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 17:12:58 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="CN2WoBWLuEOzLSv1JxD1bWMVPDQf5Q8YKSEcq-QEHBjnDF2X5_UVIwVuD20rHwUaoxwRnhfS8bqUwYb_tgP7dg";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Качество | Ruby On Rails</title>
<meta name="description" content="Качество / Ruby On Rails: Учимся тестировать свое приложение">
<link rel="canonical" href="https://ru.hexlet.io/courses/rails-basics/lessons/quality/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Качество">
<meta property="og:title" content="Ruby On Rails">
<meta property="og:description" content="Качество / Ruby On Rails: Учимся тестировать свое приложение">
<meta property="og:url" content="https://ru.hexlet.io/courses/rails-basics/lessons/quality/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="vBnLmL6i202j8On1II9gi57ebsJ9BH6OmmvsRgGZclRTyACvTNx2LRWzzW0sgJD8XtdDaHUzgCwni3YSU56VOg" />
<script src="/vite/assets/inertia-INZxX8jp.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-nkZBEvfU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-6pOtQ3OW.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTExODcsInB1ciI6ImJsb2JfaWQifX0=--21b225d5b1f2f885f46e9ef4e79641fbea2f76dc/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Coding%20workshop-bro.webp"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T17:12:58.536Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"LKpsCpSbkSWFrOo57aBZt-Ef0Fz5-DvsQe7m9G12IMDDe6c9ZuU8RTPvzqHhr6nAIRb99vHPxU78DnygP3HHrg","topics":[{"id":88070,"title":"Добрый день. У меня возникли трудности в прохождении дз, попробую описать по пунктам:\n\n1. После того как все пункты дз по части создания контроллеров, роутов и вью выполнены при запуске сервера всплывает ошибка \n\n\"Sprockets::Rails::Helper::AssetNotFound in Bulletins#index\" полный скриншот по ссылке внизу:\n\n[](https://drive.google.com/file/d/1iq4XuSTc0p4Q5-USNGOoxKVN-YqtB0KW/view?usp=share_link)\n\nУбрать эту проблему у меня получилось 2мя способами:\n\nI. Либо тупо закоментить строчку \n<%= stylesheet_link_tag 'application.css', 'data-turbo-track': 'reload' %> в файле application.html.erb - я так понимаю понимаю, так мы говорим приложению что отказываемся вообще от использования стилей, но не знаю насколько такой метод корректен и предпочтителен.\n\nII. Добавить пустой \"application.css\" в app/assets/stylesheets и добавить строчку \"//= link application.css\" в \"manifest.js\"\n\nПоскольку и то и другое достаточно неявно исходя из теоретических материалов курса, а при пуле дз с гита оно не работает, не могли бы вы уточнить как тут правильно все чинить)\n\n2. Второй вопрос, с которым я уже справиться не смог заключается в ошибке при запуске тестов после того как фикстуры созданы и сами тесты написаны. \n\nПо сути, это последний этап перед отправкой дз на проверку. \nЗапуск \"bin/rails test:integration\" приводит к следующей ошибке: \n\n` ***quality git:(main) ✗ bin/rails test:integration\n\nyarn install v1.22.19\n\n[1/4] 🔍 Resolving packages...\nsuccess Already up-to-date.\n\n✨ Done in 0.05s.\nyarn run v1.22.19\n\n$ esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets\n\n✘ [ERROR] Could not resolve \"app/javascript/*.*\"\n\n I*t looks like you are trying to use glob syntax (i.e. \"*\") with esbuild. This syntax is typically\n handled by your shell, and isn't handled by esbuild itself. You must expand glob syntax first\n before passing your paths to esbuild.\n\n1 error\nnode:child_process:924\n throw err;\n ^\n\nError: Command failed: /Users/positive_padla/Hexlet/hexlet-assignments/rails-basics-ru/quality/node_modules/@esbuild/darwin-arm64/bin/esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets\n at checkExecSyncError (node:child_process:885:11)\n at Object.execFileSync (node:child_process:921:15)\n at Object.<anonymous> (/Users/positive_padla/Hexlet/hexlet-assignments/rails-basics-ru/quality/node_modules/esbuild/bin/esbuild:220:28)\n at Module._compile (node:internal/modules/cjs/loader:1254:14)\n at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)\n at Module.load (node:internal/modules/cjs/loader:1117:32)\n at Module._load (node:internal/modules/cjs/loader:958:12)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)\n at node:internal/main/run_main_module:23:47 {\n status: 1,\n signal: null,\n output: [ null, null, null ],\n pid: 28453,\n stdout: null,\n stderr: null\n}\n\nNode.js v18.16.0\nerror Command failed with exit code 1.\ninfo Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.\nrails aborted!\njsbundling-rails: Command build failed, ensure yarn is installed and `yarn build` runs without errors\n\nTasks: TOP => test:integration => test:prepare => javascript:build\n(See full trace by running task with --trace)****\n` \nЯ честно пытался погуглить как можно справиться с данной проблемой, но найденные решения не помогли.\n\nХотелось бы уже начать двигаться дальше, а то очень много времени отнял модуль с тестированием..","plain_title":"Добрый день. У меня возникли трудности в прохождении дз, попробую описать по пунктам: После того как все пункты дз по части создания контроллеров, роутов и вью выполнены при запуске сервера всплывает ошибка \"Sprockets::Rails::Helper::AssetNotFound in Bulletins#index\" полный скриншот по ссылке внизу: (https://drive.google.com/file/d/1iq4XuSTc0p4Q5-USNGOoxKVN-YqtB0KW/view?usp=share_link) Убрать эту проблему у меня получилось 2мя способами: I. Либо тупо закоментить строчку <%= stylesheetlinktag 'application.css', 'data-turbo-track': 'reload' %> в файле application.html.erb - я так понимаю понимаю, так мы говорим приложению что отказываемся вообще от использования стилей, но не знаю насколько такой метод корректен и предпочтителен. II. Добавить пустой \"application.css\" в app/assets/stylesheets и добавить строчку \"//= link application.css\" в \"manifest.js\" Поскольку и то и другое достаточно неявно исходя из теоретических материалов курса, а при пуле дз с гита оно не работает, не могли бы вы уточнить как тут правильно все чинить) Второй вопрос, с которым я уже справиться не смог заключается в ошибке при запуске тестов после того как фикстуры созданы и сами тесты написаны. По сути, это последний этап перед отправкой дз на проверку. Запуск \"bin/rails test:integration\" приводит к следующей ошибке: ` ***quality git:(main) ✗ bin/rails test:integration yarn install v1.22.19 [1/4] 🔍 Resolving packages... success Already up-to-date. ✨ Done in 0.05s. yarn run v1.22.19 $ esbuild app/javascript/. --bundle --sourcemap --outdir=app/assets/builds --public-path=assets ✘ [ERROR] Could not resolve \"app/javascript/.\" It looks like you are trying to use glob syntax (i.e. \"\") with esbuild. This syntax is typically handled by your shell, and isn't handled by esbuild itself. You must expand glob syntax first before passing your paths to esbuild. 1 error node:child_process:924 throw err; ^ Error: Command failed: /Users/positivepadla/Hexlet/hexlet-assignments/rails-basics-ru/quality/nodemodules/@esbuild/darwin-arm64/bin/esbuild app/javascript/. --bundle --sourcemap --outdir=app/assets/builds --public-path=assets at checkExecSyncError (node:childprocess:885:11) at Object.execFileSync (node:childprocess:921:15) at Object. (/Users/positivepadla/Hexlet/hexlet-assignments/rails-basics-ru/quality/nodemodules/esbuild/bin/esbuild:220:28) at Module.compile (node:internal/modules/cjs/loader:1254:14) at Module.extensions..js (node:internal/modules/cjs/loader:1308:10) at Module.load (node:internal/modules/cjs/loader:1117:32) at Module.load (node:internal/modules/cjs/loader:958:12) at Function.executeUserEntryPoint as runMain (node:internal/modules/run_main:81:12) at node:internal/main/runmain_module:23:47 { status: 1, signal: null, output: [ null, null, null ], pid: 28453, stdout: null, stderr: null } Node.js v18.16.0 error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. rails aborted! jsbundling-rails: Command build failed, ensure yarn is installed and yarn build runs without errors Tasks: TOP => test:integration => test:prepare => javascript:build (See full trace by running task with --trace)**** ` Я честно пытался погуглить как можно справиться с данной проблемой, но найденные решения не помогли. Хотелось бы уже начать двигаться дальше, а то очень много времени отнял модуль с тестированием.. ","creator":{"public_name":"Petr Che","id":147023,"is_tutor":false},"comments":[{"creator":{"public_name":"Petr Che","id":147023,"is_tutor":false},"id":176422,"body":"**Nikolai Gagarinov**, yarn run build постоянно падает с ошибкой, остальные команды выполнились успешно.\n\nПроверка, на гите не проходит:\n\nhttps://github.com/PeterChebanov/hexlet-assignments/tree/main/rails-basics-ru/quality","topic_id":88070},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":176356,"body":"Петр, добрый день.\n\nДля запуска тестов локально необходимо использовать команду `make setup`.\n\nПопробуйте перед запуском приложения выполнить команды\n\n```\nyarn\nyarn run build\nyarn run build:css\n```\n\nПервая команда установит зависимости для фронтенда. Вторая - соберет JS, третья - соберет CSS.\nПо идее команды сборки выполняются в тестах перед `make setup`.\n\nВообще будет хорошо, если вы отправите свои ДЗ на гитхаб. Тогда можно будет посмотреть код. Отправить ДЗ можно даже с неработающими тестами.","topic_id":88070},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":176475,"body":"Петр, добрый день.\n\nВижу, что сейчас проходит проверка. Вам удалось разобраться самостоятельно?\n\nЕсли у вас падает пайплайн - скидывайте ссылку именно на запуск, в нем будет виден вывод тестов. \n\nИ не забывайте, что если что-то пошло не так, можно сбросить версию домашнего задания (могла появиться новое). Все необходимые команды указаны у вас на странице ДЗ на Хекслете.","topic_id":88070},{"creator":{"public_name":"Petr Che","id":147023,"is_tutor":false},"id":176564,"body":"**Nikolai Gagarinov**, здравствуйте, проверку прошел, хотя yarn run build все равно падает с ошибкой. Пока не буду на этом останавливаться и пойду дальше по курсу, наверняка я еще ни раз встречусь с подобной проблемой, там ее и буду решать :))","topic_id":88070}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Качество","entity_url":null,"active":true}},{"id":60982,"title":"Привет, а что все таки было с Brakeman, почему он не показал ошибку при удалении csrf токен?","plain_title":"Привет, а что все таки было с Brakeman, почему он не показал ошибку при удалении csrf токен? ","creator":{"public_name":"Алексей","id":637,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":128686,"body":"Алексей, добрый день.\n\nСудя по документации Brakeman не реагирует на эти теги. Он реагирует на настройку [protect_from_forgery](https://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/), а она по умолчанию [включена](https://edgeguides.rubyonrails.org/configuring.html#config-action-controller-default-protect-from-forgery) начиная с версии 5.2\n","topic_id":60982},{"creator":{"public_name":"Александр Иноземцев","id":189031,"is_tutor":false},"id":179681,"body":"в самом низу в задании дублируется пункт добавить гемы\nGemfile\nДобавьте гемы rubocop и rubocop-rails, я думаю это избыточно и можно удалить","topic_id":60982},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":179682,"body":"Александр, добрый день.\n\nСпасибо поправили. (Этот топик в архиве, поэтому в следующий раз создавайте отдельный топик, чтобы мы не пропустили ваше сообщение).","topic_id":60982}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Качество","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":4257,"slug":"rails_basics_quality_exercise","name":null,"state":"active","kind":"exercise","language":"ruby","locale":"ru","has_web_view":true,"has_test_view":false,"reviewable":true,"readme":"Автоматизированное тестирование – неотъемлемая часть процесса программирования в профессиональной среде. Тесты — единственный надёжный способ убедиться в работоспособности кода. Они не избавляют от багов, но позволяют держать их количество на приемлемом уровне. Тесты дают уверенность, что изменения в одной части системы, не сломали другие части.\n\nТакже нужно не забывать о стиле кода. Хороший стиль кодирования – базовое требование к коду в коммерческой разработке. Это важно, в том числе для упрощения командной разработки. Как правило, в одном проекте работает от нескольких до десятков программистов и крайне важно, чтобы им было легко читать код друг друга не спотыкаясь о неправильное форматирование.\n\n### config/routes.rb\n\nДобавьте маршруты обработчиков `index()` и `show()` для `bulletins`.\n\n### app/controllers/bulletins_controller.rb\n\nНапишите обработчики для экшенов `index()`, `show()`.\n\n### /app/views/bulletins/index.html.erb\n\nВыведите список всех сущностей.\n\n### app/views/bulletins/show.html.erb\n\nВыведите показ объявления.\n\n### test/fixtures/bulletins.yml\n\nЗаполните фикстуры для модели *Bulletin*.\n\n### test/controllers/bulletins_controller_test.rb\n\nНапишите тесты для экшенов `index()`, `show()`.\n","prepared_readme":"Автоматизированное тестирование – неотъемлемая часть процесса программирования в профессиональной среде. Тесты — единственный надёжный способ убедиться в работоспособности кода. Они не избавляют от багов, но позволяют держать их количество на приемлемом уровне. Тесты дают уверенность, что изменения в одной части системы, не сломали другие части.\n\nТакже нужно не забывать о стиле кода. Хороший стиль кодирования – базовое требование к коду в коммерческой разработке. Это важно, в том числе для упрощения командной разработки. Как правило, в одном проекте работает от нескольких до десятков программистов и крайне важно, чтобы им было легко читать код друг друга не спотыкаясь о неправильное форматирование.\n\n### config/routes.rb\n\nДобавьте маршруты обработчиков `index()` и `show()` для `bulletins`.\n\n### app/controllers/bulletins_controller.rb\n\nНапишите обработчики для экшенов `index()`, `show()`.\n\n### /app/views/bulletins/index.html.erb\n\nВыведите список всех сущностей.\n\n### app/views/bulletins/show.html.erb\n\nВыведите показ объявления.\n\n### test/fixtures/bulletins.yml\n\nЗаполните фикстуры для модели *Bulletin*.\n\n### test/controllers/bulletins_controller_test.rb\n\nНапишите тесты для экшенов `index()`, `show()`.\n","has_solution":true,"entity_name":"Качество"},"units":[{"id":4607,"name":"theory","url":"/courses/rails-basics/lessons/quality/theory_unit"},{"id":13264,"name":"quiz","url":"/courses/rails-basics/lessons/quality/quiz_unit"},{"id":14496,"name":"exercise","url":"/courses/rails-basics/lessons/quality/exercise_unit"}],"links":[{"id":424191,"name":"Начинаем писать тесты (правильно)","url":"https://ru.hexlet.io/blog/posts/how-to-test-code\n"},{"id":424192,"name":"Тестирование и база данных. Фикстуры, фабрики, моки.\n","url":"https://www.youtube.com/watch?app=desktop&v=lBv_kF4PnEs\n"},{"id":424193,"name":"Fixture в Rails\n","url":"https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html\n"},{"id":424194,"name":"Пример конфига .rubocop.yml\n","url":"https://github.com/Hexlet/hexlet-cv/blob/main/.rubocop.yml\n"},{"id":424195,"name":"Чек-лист хороших инженерных практик в компаниях","url":"https://guides.hexlet.io/ru/check-list-of-engineering-practices/\n"},{"id":424196,"name":"Гем rubocop-rails","url":"https://github.com/rubocop/rubocop-rails"},{"id":424197,"name":"Инструкция Rails по тестированию приложений","url":"https://guides.rubyonrails.org/testing.html"}],"ordered_units":[{"id":4607,"name":"theory","url":"/courses/rails-basics/lessons/quality/theory_unit"},{"id":13264,"name":"quiz","url":"/courses/rails-basics/lessons/quality/quiz_unit"},{"id":14496,"name":"exercise","url":"/courses/rails-basics/lessons/quality/exercise_unit"}],"id":2059,"slug":"quality","state":"approved","name":"Качество","course_order":350,"goal":"Учимся тестировать свое приложение","self_study":"1. Создайте новый Rails-проект, если у вас его еще нет.\n2. Добавьте в приложение главную страницу со списком страниц и 2-3 страницы с любым содержимым.\n3. Добавьте в проект RuboCop и тесты на созданные страницы.\n4. Результат запушьте на Github\n\nВ результате у вас получится репозиторий с приложением со статическими страницами и тестами.\n","theory_video_provider":"vimeo","theory_video_uid":"579151204","theory":"Некачественный код может привести к множеству проблем, включая трудности в сопровождении, увеличение времени на исправление ошибок и снижение производительности приложения. Инструменты для обеспечения качества кода позволяют выявлять ошибки и проверять соответствие стандартам. К таким инструментам относятся линтеры, статические анализаторы и тесты.\n\nВ этом уроке мы рассмотрим статический анализатор кода и линтер RuboCop. Мы изучим его установку, настройку и использование для анализа кода, а также познакомимся с основами тестирования в Rails, что поможет обеспечить надежность и стабильность разрабатываемых приложений.\n\n## Статический анализатор RuboCop\n\nRuboCop — это статический анализатор кода для Ruby, который помогает поддерживать стиль кода и следовать соглашениям в проекте. Он основан на Ruby Style Guide и предоставляет правила (копов), которые можно настраивать в зависимости от потребностей проекта. Основные возможности RuboCop включают:\n\n- **Статический анализ**: Проверка кода на соответствие стилю и стандартам.\n- **Автоматическая корректировка**: Возможность автоматически исправлять некоторые недочеты.\n- **Настраиваемые правила**: Возможность включать или отключать определенные правила в зависимости от требований проекта.\n- **Интеграция с редакторами**: Поддержка плагинов для популярных редакторов, что позволяет получать обратную связь в реальном времени.\n\n### Установка RuboCop\n\nЧтобы установить RuboCop для Rails, нужно добавить его в *Gemfile*:\n\n```ruby\ngroup :development, :test do\n gem 'rubocop'\n gem 'rubocop-rails'\n gem 'rubocop-performance'\nend\n```\n\nТак как в это dev-зависимость, то и добавляется Rucobop в окружение для разработки и для тест-окружения (для CI).\n\nДальше необходимо установить новую зависимость с помощью Bundler:\n\n```bash\nbundle install\n```\n\nПроверим, что RuboCop установлен:\n\n```ruby\nbundle exec rubocop -v\n1.69.2\n```\n\n### Файл конфигурации\n\nRuboCop по умолчанию поставляется со своими правилами и проверяет весь проект. Чтобы применить тонкую настройку, мы можем создать файл *.rubocop.yml* и добавить туда правила проверок:\n\n```yaml\n---\n\nAllCops:\n DisplayCopNames: true\n Exclude:\n - \"bin/**\"\n - \"db/schema.rb\"\n - \"db/migrate/**\"\n - \"vendor/**/*\"\n - \"node_modules/**/*\"\n NewCops: enable\n TargetRailsVersion: 6\n TargetRubyVersion: 3.2\n\nStyle/AsciiComments:\n Enabled: false\n\nStyle/ClassAndModuleChildren:\n Enabled: false\n\nStyle/Documentation:\n Enabled: false\n\nStyle/IfUnlessModifier:\n Enabled: false\n\n# ...\n```\n\nРанее вместе с RuboCop мы добавили гемы `rubocop-rails` и `rubocop-performance` - это наборы правил для Rails-приложений. Чтобы их включить нужно их добавить в *.rubocop.yml*:\n\n```rails\nrequire:\n - rubocop-performance\n - rubocop-rails\n```\n\nОбычно файлы конфигурации не пишут с нуля, а используют готовые, из проекта-шаблона или из предыдущего проекта. Например в проекте [Hexlet CV](https://github.com/Hexlet/hexlet-cv/blob/main/.rubocop.yml) мы можем взять конфиг и добавить к себе.\n\nRuboCop установлен и настроен, теперь необходимо проверить проект на ошибки и соответствие стайл гайду.\n\n### Проверка кода\n\nЕсли мы запустим RuboCop первый раз, мы увидим множество ошибок. Точное их количество будет в конце\n\n```bash\nbundle exec rubocop\nInspecting 33 files\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\nOffenses:\n\nGemfile:1:1: C: [Correctable] Style/FrozenStringLiteralComment: Missing frozen string literal comment.\nsource \"https://rubygems.org\"\n...\n33 files inspected, 166 offenses detected, 155 offenses autocorrectable\n\nYou can opt out of this message by adding the following to your config (see https://docs.rubocop.org/rubocop/extensions.html#extension-suggestions for more options):\n AllCops:\n SuggestExtensions: false\n```\n\nВывод команды `bundle exec rubocop` показывает, что было проверено 33 файла, и обнаружено 166 недочетов (offenses). Все недочеты помечены буквой \"C\", что указывает на то, что они являются проблемами стиля (Correctable) и их можно исправить автоматически.\n\nRuboCop предоставляет возможность автоматической корректировки кода. Для этого используются две основные опции:\n\n```bash\nbundle exec rubocop -a\nbundle exec rubocop -A\n```\n\n* Опция `-a` исправляет только те недочеты, которые не могут повредить бизнес-логике (например, форматирование кода).\n* Опция `-A` может вносить изменения, которые потенциально могут повлиять на логику приложения. Но ее можно использовать без опасений на новом проекте, в котором еще нет никакой логики.\n\n```bash\nbundle exec rubocop -A 1 ↵\nInspecting 33 files\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\nOffenses:\n# ...\ntest/test_helper.rb:3:9: C: [Corrected] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.\nrequire \"rails/test_help\"\n ^^^^^^^^^^^^^^^^^\n\n33 files inspected, 225 offenses detected, 215 offenses corrected\n\nYou can opt out of this message by adding the following to your config (see https://docs.rubocop.org/rubocop/extensions.html#extension-suggestions for more options):\n AllCops:\n SuggestExtensions: false\n```\n\nВывод команды похож на тот, что вы получили при проверке, только здесь будут отмечены применённые исправления. Не все исправления можно выполнить автоматически, поэтому внимательно изучите отчет анализатора.\n\n## Тестирование\n\nRails предоставляет мощные инструменты для написания и выполнения тестов. Тесты обычно располагаются в директории *test/*, где они разделены по директориям:\n\n* *test/models/* — для тестов моделей.\n* *test/controllers/* — для тестов контроллеров.\n* *test/integration/* — для интеграционных тестов.\n* *test/system/* — для системных тестов.\n\nДля большинства Rails-приложений тестов для контроллеров уже достаточно, чтобы обеспечить базовую проверку приложения. Однако в зависимости от сложности приложения и требований к качеству, может потребоваться также использование других типов тестов, таких как системные тесты.\n\nПример тестов контроллера резюме в [Hexlet CV](https://github.com/Hexlet/hexlet-cv):\n\n```ruby\n# https://github.com/Hexlet/hexlet-cv/blob/592468dbe24a33d1d12de779d3fcdb2fb030311f/test/controllers/web/resumes_controller_test.rb\nclass Web::ResumesControllerTest < ActionDispatch::IntegrationTest\n setup do\n @resume = resumes(:one)\n end\n\n test '#show' do\n get resume_path(@resume, locale: I18n.locale)\n\n @resume.reload\n assert_response :success\n assert { @resume.impressions_count == 1 }\n end\n\n test '#show from boot' do\n @resume.destroy\n get resume_path(@resume, locale: I18n.locale), headers: { 'User-Agent': 'Bot' }\n assert_redirected_to root_path\n end\n\n test '#index rss' do\n get resumes_path(format: :rss)\n assert_response :success\n end\n\n test '#user anonimus' do\n user = users(:without_last_name_and_first_name)\n sign_in(user)\n\n get resume_path(@resume, locale: I18n.locale)\n assert_redirected_to edit_account_profile_path\n end\n\n test '#user is author' do\n user = @resume.user\n sign_in(user)\n\n get resume_path(@resume, locale: I18n.locale)\n\n @resume.reload\n assert_response :success\n assert { @resume.impressions_count.zero? }\n end\n\n test '#user is not author' do\n user = users(:full)\n sign_in(user)\n\n get resume_path(@resume, locale: I18n.locale)\n\n @resume.reload\n assert_response :success\n assert { @resume.impressions_count == 1 }\n end\nend\n```\n\nВ этих тестах используется фреймворк тестирования Minitest с гемом power-assert.\n\n### Фикстуры\n\nФикстуры в Rails — это способ предварительной загрузки данных в базу данных для тестирования. Они позволяют создавать наборы данных, которые могут быть использованы в тестах, чтобы обеспечить предсказуемую и стабильную среду для выполнения тестов. Фикстуры особенно полезны там, где необходимо иметь определенные данные для проверки работы приложения.\n\nФикстуры обычно хранятся в формате YAML. Каждый файл фикстуры соответствует одной модели и содержит записи, которые будут загружены в базу данных перед выполнением тестов. В файле фикстуры каждая запись имеет уникальный идентификатор, который используется для ссылки на эту запись в тестах.\n\n```yaml\nDEFAULTS: &DEFAULTS\n first_name: $LABEL\n last_name: Last\n encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %>\n locale: <%= I18n.locale %>\n confirmed_at: <%= Time.current %>\n resume_mail_enabled: true\n role: :user\n state: permitted\n\nfull:\n <<: *DEFAULTS\n email: $LABEL@email.com\n about: PR-специалист Hexlet\n role: admin\n```\n\nФикстуры автоматически загружаются в базу данных перед выполнением тестов. Это происходит благодаря методу `fixtures()` в *тестах*, который указывает, какие фикстуры нужно загрузить.\n\n```ruby\n# test/test_helper.rb\n\nmodule ActiveSupport\n class TestCase\n parallelize(workers: :number_of_processors)\n # Загружаем все фикстуры\n fixtures :all\n end\nend\n```\n\nВ тестах можно обращаться к загруженным фикстурам по их идентификаторам:\n\n```ruby\n test '#user is not author' do\n # обращаемся к модели User с id = full\n user = users(:full)\n sign_in(user)\n\n get resume_path(@resume, locale: I18n.locale)\n\n @resume.reload\n assert_response :success\n assert { @resume.impressions_count == 1 }\n end\n```\n\nФикстуры позволяют легко создавать и управлять тестовыми данными. Особенно там, где требуется сложное состояние БД. Они обеспечивают консистентность данных между тестами. Фикстуры можно использовать с гемами `faker` или `factory_bot_rails` для того, чтобы упростить создание фикстур там, где не нужны конкретные значения.\n\n### Транзакционные тесты\n\nТранзакционные тесты в Rails — это подход к тестированию, при котором каждый тест выполняется в рамках транзакции базы данных. Это позволяет изолировать тесты друг от друга и гарантировать, что изменения, внесенные в базу данных в ходе теста, не повлияют на другие тесты. После завершения теста транзакция откатывается, и база данных возвращается в исходное состояние.\n\nКаждый тест выполняется в своей транзакции, что предотвращает \"загрязнение\" базы данных. Это особенно полезно, когда тесты изменяют данные, так как изменения не сохраняются после завершения теста. В Rails при использовании ActiveSupport::TestCase транзакционные тесты включены по умолчанию.\n\nПример транзакционного теста:\n\n```ruby\n test '#create and to draft' do\n attrs = {\n name: 'Ruby developer'\n }\n\n params = {\n hide: true,\n resume: attrs\n }\n\n post(account_resumes_path, params:)\n assert_response :redirect\n\n resume = Resume.find_by(attrs)\n assert { resume.not_evaluated? }\n end\n```\n\n### Пишем первый тест\n\nКогда мы вызываем генератор для создания контроллера, то создается также класс для тестирования этого контроллера. Вызовем генератор для создания контроллера:\n\n```bash\nbin/rails g controller pages\n create app/controllers/pages_controller.rb\n invoke erb\n create app/views/pages\n invoke test_unit\n create test/controllers/pages_controller_test.rb\n invoke helper\n create app/helpers/pages_helper.rb\n invoke test_unit\n```\n\nПолучили пустой контроллер:\n\n```ruby\nclass PagesController < ApplicationController\nend\n```\n\nИ тестовый класс без методов\n\n```ruby\nclass PagesControllerTest < ActionDispatch::IntegrationTest\n # test \"the truth\" do\n # assert true\n # end\nend\n```\n\nДобавим обработчик в *config/routes.rb*\n\n```ruby\nRails.application.routes.draw do\n # Используем сразу же ресурсный роутинг\n resources :pages, only: [:index]\nend\n```\n\nИ добавим тест на добавленный роут\n\n```ruby\n\nclass PagesControllerTest < ActionDispatch::IntegrationTest\n test '#index' do\n get pages_url\n\n assert_response :success\n end\nend\n```\n\nВ контроллер добавим экшен:\n\n```ruby\nclass PagesController < ApplicationController\n def index; end\nend\n```\n\nИ шаблон *app/views/pages/index.html.erb*, который будет выводиться экшеном:\n\n```html\n<h1>Pages catalog</h1>\n<p>Find me in app/views/pages/index.html.erb</p>\n```\n\nТеперь мы готовы запустить тесты и проверить, что все работает. Запустить тесты можно командой `bin/rails test`:\n\n```text\nbin/rails test 1 ↵\nRunning 1 tests in a single process (parallelization threshold is 50)\nRun options: --seed 24561\n\n# Running:\n\n.\n\nFinished in 0.322993s, 3.0960 runs/s, 3.0960 assertions/s.\n1 runs, 1 assertions, 0 failures, 0 errors, 0 skips\n```\n\nЗапуск тестов завершился успешно. Если на каком-то из этапов возникла бы ошибка, то тесты бы об этом сообщили. Например так выглядит ошибка, если нет экшена и шаблона:\n\n```text\nbin/rails test 130 ↵\nRunning 1 tests in a single process (parallelization threshold is 50)\nRun options: --seed 25906\n\n# Running:\n\nF\n\nFailure:\nPagesControllerTest#test_#index [test/controllers/pages_controller_test.rb:11]:\nExpected response to be a <2XX: success>, but was a <404: Not Found>\n\n\nbin/rails test test/controllers/pages_controller_test.rb:8\n\n\nFinished in 0.652640s, 1.5322 runs/s, 1.5322 assertions/s.\n1 runs, 1 assertions, 1 failures, 0 errors, 0 skips\n```\n\n## binding.irb\n\nВ Ruby и Rails есть инструмент для отладки кода - `binding.irb`, который позволяет вам вставлять интерактивную консоль в любое место вашего кода. Это может быть особенно полезно для диагностики проблем, анализа состояния переменных и выполнения произвольного кода в контексте текущего выполнения программы.\n\nЧтобы использовать irb, нужно вызвать `binding.irb` внутри кода:\n\n```ruby\nclass UsersController < ApplicationController\n def index\n @users = User.all\n binding.irb\n end\nend\n```\n\nИспользовать `binding.irb` можно даже во время выполнения тестов\n\n```text\nbin/rails test\nRunning 7 tests in a single process (parallelization threshold is 50)\nRun options: --seed 57184\n\n# Running:\n\n.....\napp/controllers/users_controller.rb @ line 7 :\n\n 3:\n 4: # GET /users or /users.json\n 5: def index\n 6: @users = User.all\n => 7: binding.irb\n 8: end\n 9:\n 10: # GET /users/1 or /users/1.json\n 11: def show\n 12: end\n\nirb(#<UsersController:0x00007526f...):001>\n```\n\n## Заключение\n\nВ ходе урока мы познакомились с RuboCop — статическим анализатором кода для Ruby, который помогает поддерживать стиль и стандарты кода в проектах. Мы рассмотрели процесс установки и настройки RuboCop, включая создание конфигурационного файла *.rubocop.yml* для настройки правил проверки. Также мы изучили, как использовать RuboCop для анализа кода и автоматической корректировки недочетов.\n\nМы обсудили основы тестирования в Rails, включая использование фикстур и транзакционных тестов, а также написание простых тестов для контроллеров.\n\nРассмотрели инструмент `binding.irb` для отладки кода, который позволяет отлаживать и исправлять ошибки в приложении.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":7193,"name":"theory","url":"/courses/rails-basics/lessons/intro/theory_unit"}],"links":[{"id":424121,"name":"Rails Guides","url":"https://guides.rubyonrails.org/index.html\n"}],"ordered_units":[{"id":7193,"name":"theory","url":"/courses/rails-basics/lessons/intro/theory_unit"}],"id":3211,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с целями и задачами курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Этот курс посвящен Ruby on Rails – самому популярному фреймворку на Ruby. В этом курсе мы будем создавать простые веб-приложения и рассмотрим следующие темы:\n\n* Создание CRUD, валидация данных\n* ORM, создание сущностей и связи\n* Конфигурация приложения\n* Вложенные ресурсы\n* Тестирование и линтинг\n* i18n и локализация\n* Написание middlewares\n\nДля полноценного погружения рекомендуем выполнять все домашние задания и выкатывать результат на сервисы, подобные [Render](https://render.com).\n"},"id":234,"slug":"rails-basics","challenges_count":0,"name":"Ruby On Rails","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"В этом курсе вы изучите основы работы с фреймоворком Ruby on Rails. Вы узнаете о роутинге, контроллерах, шаблонизаторах, моделях и связях между ними. В итоге научитесь создавать полноценные сайты с CRUD действиями, писать тесты на них, работать с базой данных через ActiveRecord и миграции. Знания из этого курса помогут создать свой сайт и сделать его доступным в интернете.","kind":"basic","updated_at":"2026-01-20T11:46:14.961Z","language":"ruby","duration_cache":61080,"skills":["Создавать сайты на Rails, покрывать их автоматическими тестами и выкладывать на PaaS-сервисы","Основным концепциям фреймворка, таким как контроллеры, роутинг, модели, шаблоны и тесты.","Автоматизировать большинство рутинных задач с помощью генераторов, автоматических форм и интеграций фронтенда.","Использовать repl и эффективно отлаживать приложения","Основным принципам построения безопасных приложений."],"keywords":["ruby","rails","orm","rest api","job workers","архитертура","rake"],"lessons_count":16,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NzM3MiwicHVyIjoiYmxvYl9pZCJ9fQ==--3b3f159f182adb8eb21f75671f2839f8c4374db6/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":475,"slug":"rails","title":"Ruby on Rails","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"not_finished","order":null,"duration_in_months":4},"id":632,"slug":"rails","title":"Разработка на Ruby on Rails","subtitle":"Изучите Ruby, Rails и проектирование REST API","subtitle_for_lists":"Изучите Ruby, Rails и проектирование REST API","locale":"ru","current":true,"duration_in_months_text":"4 месяца","stack_slug":"rails","price_text":"от 3 900 ₽","duration_text":"4 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTExODcsInB1ciI6ImJsb2JfaWQifX0=--21b225d5b1f2f885f46e9ef4e79641fbea2f76dc/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Coding%20workshop-bro.webp"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/rails-basics/lessons/quality/theory_unit","version":"0b0c6d4ebbd40fd58630a0dd89cc25544ccdf24e","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Ruby On Rails</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Качество</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Качество","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Ruby On Rails"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Некачественный код может привести к множеству проблем, включая трудности в сопровождении, увеличение времени на исправление ошибок и снижение производительности приложения. Инструменты для обеспечения качества кода позволяют выявлять ошибки и проверять соответствие стандартам. К таким инструментам относятся линтеры, статические анализаторы и тесты.</p>
<p>В этом уроке мы рассмотрим статический анализатор кода и линтер RuboCop. Мы изучим его установку, настройку и использование для анализа кода, а также познакомимся с основами тестирования в Rails, что поможет обеспечить надежность и стабильность разрабатываемых приложений.</p>
<h2 id="heading-2-1">Статический анализатор RuboCop</h2>
<p>RuboCop — это статический анализатор кода для Ruby, который помогает поддерживать стиль кода и следовать соглашениям в проекте. Он основан на Ruby Style Guide и предоставляет правила (копов), которые можно настраивать в зависимости от потребностей проекта. Основные возможности RuboCop включают:</p>
<ul>
<li><strong>Статический анализ</strong>: Проверка кода на соответствие стилю и стандартам.</li>
<li><strong>Автоматическая корректировка</strong>: Возможность автоматически исправлять некоторые недочеты.</li>
<li><strong>Настраиваемые правила</strong>: Возможность включать или отключать определенные правила в зависимости от требований проекта.</li>
<li><strong>Интеграция с редакторами</strong>: Поддержка плагинов для популярных редакторов, что позволяет получать обратную связь в реальном времени.</li>
</ul>
<h3 id="heading-3-2">Установка RuboCop</h3>
<p>Чтобы установить RuboCop для Rails, нужно добавить его в <em>Gemfile</em>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">group :development, :test do
gem 'rubocop'
gem 'rubocop-rails'
gem 'rubocop-performance'
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Так как в это dev-зависимость, то и добавляется Rucobop в окружение для разработки и для тест-окружения (для CI).</p>
<p>Дальше необходимо установить новую зависимость с помощью Bundler:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">bundle install</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Проверим, что RuboCop установлен:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">bundle exec rubocop -v
1.69.2</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h3 id="heading-3-3">Файл конфигурации</h3>
<p>RuboCop по умолчанию поставляется со своими правилами и проверяет весь проект. Чтобы применить тонкую настройку, мы можем создать файл <em>.rubocop.yml</em> и добавить туда правила проверок:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">---
AllCops:
DisplayCopNames: true
Exclude:
- "bin/**"
- "db/schema.rb"
- "db/migrate/**"
- "vendor/**/*"
- "node_modules/**/*"
NewCops: enable
TargetRailsVersion: 6
TargetRubyVersion: 3.2
Style/AsciiComments:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
Style/Documentation:
Enabled: false
Style/IfUnlessModifier:
Enabled: false
# ...</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Ранее вместе с RuboCop мы добавили гемы <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">rubocop-rails</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">rubocop-performance</code> - это наборы правил для Rails-приложений. Чтобы их включить нужно их добавить в <em>.rubocop.yml</em>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">require:
- rubocop-performance
- rubocop-rails</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Обычно файлы конфигурации не пишут с нуля, а используют готовые, из проекта-шаблона или из предыдущего проекта. Например в проекте <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://github.com/Hexlet/hexlet-cv/blob/main/.rubocop.yml" rel="noopener noreferrer" target="_blank">Hexlet CV</a> мы можем взять конфиг и добавить к себе.</p>
<p>RuboCop установлен и настроен, теперь необходимо проверить проект на ошибки и соответствие стайл гайду.</p>
<h3 id="heading-3-4">Проверка кода</h3>
<p>Если мы запустим RuboCop первый раз, мы увидим множество ошибок. Точное их количество будет в конце</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">bundle exec rubocop
Inspecting 33 files
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
Offenses:
Gemfile:1:1: C: [Correctable] Style/FrozenStringLiteralComment: Missing frozen string literal comment.
source "https://rubygems.org"
...
33 files inspected, 166 offenses detected, 155 offenses autocorrectable
You can opt out of this message by adding the following to your config (see https://docs.rubocop.org/rubocop/extensions.html#extension-suggestions for more options):
AllCops:
SuggestExtensions: false</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Вывод команды <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">bundle exec rubocop</code> показывает, что было проверено 33 файла, и обнаружено 166 недочетов (offenses). Все недочеты помечены буквой "C", что указывает на то, что они являются проблемами стиля (Correctable) и их можно исправить автоматически.</p>
<p>RuboCop предоставляет возможность автоматической корректировки кода. Для этого используются две основные опции:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">bundle exec rubocop -a
bundle exec rubocop -A</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<ul>
<li>Опция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-a</code> исправляет только те недочеты, которые не могут повредить бизнес-логике (например, форматирование кода).</li>
<li>Опция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">-A</code> может вносить изменения, которые потенциально могут повлиять на логику приложения. Но ее можно использовать без опасений на новом проекте, в котором еще нет никакой логики.</li>
</ul>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">bundle exec rubocop -A 1 ↵
Inspecting 33 files
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
Offenses:
# ...
test/test_helper.rb:3:9: C: [Corrected] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
require "rails/test_help"
^^^^^^^^^^^^^^^^^
33 files inspected, 225 offenses detected, 215 offenses corrected
You can opt out of this message by adding the following to your config (see https://docs.rubocop.org/rubocop/extensions.html#extension-suggestions for more options):
AllCops:
SuggestExtensions: false</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Вывод команды похож на тот, что вы получили при проверке, только здесь будут отмечены применённые исправления. Не все исправления можно выполнить автоматически, поэтому внимательно изучите отчет анализатора.</p>
<h2 id="heading-2-5">Тестирование</h2>
<p>Rails предоставляет мощные инструменты для написания и выполнения тестов. Тесты обычно располагаются в директории <em>test/</em>, где они разделены по директориям:</p>
<ul>
<li><em>test/models/</em> — для тестов моделей.</li>
<li><em>test/controllers/</em> — для тестов контроллеров.</li>
<li><em>test/integration/</em> — для интеграционных тестов.</li>
<li><em>test/system/</em> — для системных тестов.</li>
</ul>
<p>Для большинства Rails-приложений тестов для контроллеров уже достаточно, чтобы обеспечить базовую проверку приложения. Однако в зависимости от сложности приложения и требований к качеству, может потребоваться также использование других типов тестов, таких как системные тесты.</p>
<p>Пример тестов контроллера резюме в <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://github.com/Hexlet/hexlet-cv" rel="noopener noreferrer" target="_blank">Hexlet CV</a>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"># https://github.com/Hexlet/hexlet-cv/blob/592468dbe24a33d1d12de779d3fcdb2fb030311f/test/controllers/web/resumes_controller_test.rb
class Web::ResumesControllerTest < ActionDispatch::IntegrationTest
setup do
@resume = resumes(:one)
end
test '#show' do
get resume_path(@resume, locale: I18n.locale)
@resume.reload
assert_response :success
assert { @resume.impressions_count == 1 }
end
test '#show from boot' do
@resume.destroy
get resume_path(@resume, locale: I18n.locale), headers: { 'User-Agent': 'Bot' }
assert_redirected_to root_path
end
test '#index rss' do
get resumes_path(format: :rss)
assert_response :success
end
test '#user anonimus' do
user = users(:without_last_name_and_first_name)
sign_in(user)
get resume_path(@resume, locale: I18n.locale)
assert_redirected_to edit_account_profile_path
end
test '#user is author' do
user = @resume.user
sign_in(user)
get resume_path(@resume, locale: I18n.locale)
@resume.reload
assert_response :success
assert { @resume.impressions_count.zero? }
end
test '#user is not author' do
user = users(:full)
sign_in(user)
get resume_path(@resume, locale: I18n.locale)
@resume.reload
assert_response :success
assert { @resume.impressions_count == 1 }
end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В этих тестах используется фреймворк тестирования Minitest с гемом power-assert.</p>
<h3 id="heading-3-6">Фикстуры</h3>
<p>Фикстуры в Rails — это способ предварительной загрузки данных в базу данных для тестирования. Они позволяют создавать наборы данных, которые могут быть использованы в тестах, чтобы обеспечить предсказуемую и стабильную среду для выполнения тестов. Фикстуры особенно полезны там, где необходимо иметь определенные данные для проверки работы приложения.</p>
<p>Фикстуры обычно хранятся в формате YAML. Каждый файл фикстуры соответствует одной модели и содержит записи, которые будут загружены в базу данных перед выполнением тестов. В файле фикстуры каждая запись имеет уникальный идентификатор, который используется для ссылки на эту запись в тестах.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">DEFAULTS: &DEFAULTS
first_name: $LABEL
last_name: Last
encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %>
locale: <%= I18n.locale %>
confirmed_at: <%= Time.current %>
resume_mail_enabled: true
role: :user
state: permitted
full:
<<: *DEFAULTS
email: $LABEL@email.com
about: PR-специалист Hexlet
role: admin</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Фикстуры автоматически загружаются в базу данных перед выполнением тестов. Это происходит благодаря методу <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">fixtures()</code> в <em>тестах</em>, который указывает, какие фикстуры нужно загрузить.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"># test/test_helper.rb
module ActiveSupport
class TestCase
parallelize(workers: :number_of_processors)
# Загружаем все фикстуры
fixtures :all
end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В тестах можно обращаться к загруженным фикстурам по их идентификаторам:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">test '#user is not author' do
# обращаемся к модели User с id = full
user = users(:full)
sign_in(user)
get resume_path(@resume, locale: I18n.locale)
@resume.reload
assert_response :success
assert { @resume.impressions_count == 1 }
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Фикстуры позволяют легко создавать и управлять тестовыми данными. Особенно там, где требуется сложное состояние БД. Они обеспечивают консистентность данных между тестами. Фикстуры можно использовать с гемами <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">faker</code> или <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">factory_bot_rails</code> для того, чтобы упростить создание фикстур там, где не нужны конкретные значения.</p>
<h3 id="heading-3-7">Транзакционные тесты</h3>
<p>Транзакционные тесты в Rails — это подход к тестированию, при котором каждый тест выполняется в рамках транзакции базы данных. Это позволяет изолировать тесты друг от друга и гарантировать, что изменения, внесенные в базу данных в ходе теста, не повлияют на другие тесты. После завершения теста транзакция откатывается, и база данных возвращается в исходное состояние.</p>
<p>Каждый тест выполняется в своей транзакции, что предотвращает "загрязнение" базы данных. Это особенно полезно, когда тесты изменяют данные, так как изменения не сохраняются после завершения теста. В Rails при использовании ActiveSupport::TestCase транзакционные тесты включены по умолчанию.</p>
<p>Пример транзакционного теста:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">test '#create and to draft' do
attrs = {
name: 'Ruby developer'
}
params = {
hide: true,
resume: attrs
}
post(account_resumes_path, params:)
assert_response :redirect
resume = Resume.find_by(attrs)
assert { resume.not_evaluated? }
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h3 id="heading-3-8">Пишем первый тест</h3>
<p>Когда мы вызываем генератор для создания контроллера, то создается также класс для тестирования этого контроллера. Вызовем генератор для создания контроллера:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">bin/rails g controller pages
create app/controllers/pages_controller.rb
invoke erb
create app/views/pages
invoke test_unit
create test/controllers/pages_controller_test.rb
invoke helper
create app/helpers/pages_helper.rb
invoke test_unit</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Получили пустой контроллер:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class PagesController < ApplicationController
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>И тестовый класс без методов</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class PagesControllerTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Добавим обработчик в <em>config/routes.rb</em></p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">Rails.application.routes.draw do
# Используем сразу же ресурсный роутинг
resources :pages, only: [:index]
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>И добавим тест на добавленный роут</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class PagesControllerTest < ActionDispatch::IntegrationTest
test '#index' do
get pages_url
assert_response :success
end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В контроллер добавим экшен:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class PagesController < ApplicationController
def index; end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>И шаблон <em>app/views/pages/index.html.erb</em>, который будет выводиться экшеном:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><h1>Pages catalog</h1>
<p>Find me in app/views/pages/index.html.erb</p></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Теперь мы готовы запустить тесты и проверить, что все работает. Запустить тесты можно командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">bin/rails test</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">bin/rails test 1 ↵
Running 1 tests in a single process (parallelization threshold is 50)
Run options: --seed 24561
# Running:
.
Finished in 0.322993s, 3.0960 runs/s, 3.0960 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Запуск тестов завершился успешно. Если на каком-то из этапов возникла бы ошибка, то тесты бы об этом сообщили. Например так выглядит ошибка, если нет экшена и шаблона:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">bin/rails test 130 ↵
Running 1 tests in a single process (parallelization threshold is 50)
Run options: --seed 25906
# Running:
F
Failure:
PagesControllerTest#test_#index [test/controllers/pages_controller_test.rb:11]:
Expected response to be a <2XX: success>, but was a <404: Not Found>
bin/rails test test/controllers/pages_controller_test.rb:8
Finished in 0.652640s, 1.5322 runs/s, 1.5322 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h2 id="heading-2-9">binding.irb</h2>
<p>В Ruby и Rails есть инструмент для отладки кода - <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">binding.irb</code>, который позволяет вам вставлять интерактивную консоль в любое место вашего кода. Это может быть особенно полезно для диагностики проблем, анализа состояния переменных и выполнения произвольного кода в контексте текущего выполнения программы.</p>
<p>Чтобы использовать irb, нужно вызвать <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">binding.irb</code> внутри кода:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class UsersController < ApplicationController
def index
@users = User.all
binding.irb
end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Использовать <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">binding.irb</code> можно даже во время выполнения тестов</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">bin/rails test
Running 7 tests in a single process (parallelization threshold is 50)
Run options: --seed 57184
# Running:
.....
app/controllers/users_controller.rb @ line 7 :
3:
4: # GET /users or /users.json
5: def index
6: @users = User.all
=> 7: binding.irb
8: end
9:
10: # GET /users/1 or /users/1.json
11: def show
12: end
irb(#<UsersController:0x00007526f...):001></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h2 id="heading-2-10">Заключение</h2>
<p>В ходе урока мы познакомились с RuboCop — статическим анализатором кода для Ruby, который помогает поддерживать стиль и стандарты кода в проектах. Мы рассмотрели процесс установки и настройки RuboCop, включая создание конфигурационного файла <em>.rubocop.yml</em> для настройки правил проверки. Также мы изучили, как использовать RuboCop для анализа кода и автоматической корректировки недочетов.</p>
<p>Мы обсудили основы тестирования в Rails, включая использование фикстур и транзакционных тестов, а также написание простых тестов для контроллеров.</p>
<p>Рассмотрели инструмент <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">binding.irb</code> для отладки кода, который позволяет отлаживать и исправлять ошибки в приложении.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/rails?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">4 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Разработка на Ruby on Rails</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Ruby, Rails и проектирование REST API</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTExODcsInB1ciI6ImJsb2JfaWQifX0=--21b225d5b1f2f885f46e9ef4e79641fbea2f76dc/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Coding%20workshop-bro.webp" alt="Разработка на Ruby on Rails" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/rails-basics/lessons/quality/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 16</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/rails-basics/lessons/quality/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-CdBlNCiQ.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-nkZBEvfU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>