Лекция №9. Практики непрерывной поддержки интеграции (CI/CD). Автоматический запуск тестов в конвейерах сборки (Build Pipelines).
§9.1. Что такое CI/CD и зачем это нужно?
Представьте себе команду разработчиков в 2000-х годах. Они пишут код на своих компьютерах. Каждый работает над своей задачей. Через несколько недель или месяцев они решают, что пора «собрать релиз». Как это выглядело:
1. Разработчики отправляют свой код в общую папку.
2. Начинается ручная сборка проекта (занимает часы).
3. Кто-то запускает тесты вручную (если они вообще есть).
4. Оказывается, что код Ивана не совместим с кодом Петра.
5. Все собираются и два дня разбираются, кто что сломал.
6. Исправляют ошибки, снова собирают, снова тестируют.
7. Через неделю выкатывают релиз.
Проблемы этого подхода:
| Проблема | Что это значит |
|---|---|
| Интеграционный ад | Разработчики пишут код в изоляции, а потом мучительно склеивают его в одно целое |
| Долгий цикл обратной связи | Ошибку, допущенную в первый день, находили через месяц |
| Страх перед релизом | Каждый релиз был стрессом — «а вдруг что-то сломается?» |
| Ручная работа | Сборка, тестирование, деплой — всё делали руками |
Если рассматривать аналогию, то это как если бы каждый повар в ресторане готовил свое блюдо в отдельной комнате, а перед открытием ресторана все блюда смешивали в одну кастрюлю. И надеялись, что получится вкусно.
На смену такому подходу пришел подход CI / CD. Давайте для начала раскроем первую часть данного понятия.
CI расшифровывается как «Continuous Integration»
— непрерывная интеграция. Оно отвечает на вопрос главный: «А не сломал ли я что – нибудь?». Вместо того чтобы накапливать код неделями и мучительно объединять его, разработчики интегрируют (объединяют) свой код в общую ветку каждый день, а часто — несколько раз в день.
Как это работает:
1. Разработчик пишет код для своей задачи.
2. Он делает git push (отправляет код в репозиторий).
3. Автоматически запускается CI-сервер.
4. CI-сервер:
- Собирает проект (проверяет, что код компилируется).
- Запускает все тесты (проверяет, что ничего не сломалось).
- Проверяет стиль кода (не нарушены ли правила оформления).
5. Результат приходит разработчику:
- ✅ Зеленый — всё хорошо, можно отправлять pull request.
- ❌ Красный — код сломал сборку или тесты, нужно исправлять.
CI отвечает на два главных вопроса:
- «Собирается ли проект?» — не допустили ли мы синтаксических ошибок?
- «Работают ли тесты?» — не сломали ли мы существующую функциональность?
Теперь разберем вторую часть понятия CI / CD.
CD расшифровывается как Continuous Delivery (непрерывная доставка) или Continuous Deployment (непрерывное развертывание).
Continuous Delivery (Непрерывная доставка)
- после того как код успешно прошел CI (собрался и прошел тесты), он автоматически готовится к развертыванию. Но решение о том, когда именно выкатывать на продакшен, принимает человек.
Continuous Deployment (Непрерывное развертывание)
- код не только готовится к развертыванию, но и автоматически развертывается на продакшен-сервере сразу после успешного прохождения тестов. Никто не нажимает кнопку «Деплой».
Какая разница между CD Delivery и CD Deployment?
| Режим | Continuous Delivery | Continuous Deployment |
|---|---|---|
| Решение о релизе | Принимает человек | Принимает система |
| Когда выкатывается | По команде (руками) | Сразу после успешных тестов |
| Подходит для | Банковские системы, медицинское ПО | Сайты, интернет-магазины, мобильные приложения |
| Риск | Ниже (человек контролирует) | Выше (всё автоматически) |
Для понимания зачем же вообще нужен CI / CD, есть простая аналогия. Представьте себе автомобильный завод. Есть конвейер, по которому движется машина.
| Этап на заводе | Этап в CI/CD |
|---|---|
| Детали подаются на конвейер | Разработчик отправляет код (git push) |
| Автоматическая сборка кузова | Сборка проекта (dotnet build) |
| Проверка качества сварки | Запуск модульных тестов |
| Проверка тормозов и двигателя | Запуск интеграционных тестов |
| Проверка, как машина выглядит | Статический анализ кода (Linter) |
| Готовая машина выезжает с конвейера | Готовое приложение выкатывается на сервер |
Если на этапе проверки качества обнаружен дефект — машина снимается с конвейера. Её не отправляют клиенту.
То же самое в CI/CD: если тесты упали — код не попадает в продакшен.
Что дает CI/CD разработчику:
| Что было раньше | Что стало с CI/CD |
|---|---|
| «У меня работает» (а у всех остальных — нет)» | «Все тесты зеленые» (значит, у всех работает) |
| Релиз раз в месяц (и каждый раз страшно) | Релиз несколько раз в день (и это безопасно) |
| Исправлять ошибки неделями | Ошибка находится в первые минуты после отправки кода |
| «А кто сломал сборку?» (иголка в стоге сена) | «Иван сломал сборку в коммите abc123» (видно сразу) |
| Ручной деплой на сервер | Автоматический деплой по нажатию кнопки |
Когда разработчик делает git push, CI/CD делает примерно вот что (упрощенно):
yaml
# Это упрощенный пример, код не обязателен для понимания
steps:
- name: Скачать код
run: git clone ...
- name: Восстановить зависимости
run: dotnet restore
- name: Собрать проект
run: dotnet build
- name: Запустить тесты
run: dotnet test
Если любой из этих шагов падает — разработчик видит красный значок в интерфейсе GitHub/GitLab и бежит исправлять свою ошибку.
§9.2. CI/CD Pipeline: что это и из чего состоит?
Pipeline (конвейер)
— это последовательность шагов, которые автоматически выполняются при каждом изменении кода.
Мы уже говорили про аналогию с заводским конвейером. Теперь давайте разберем этот конвейер детально — как он устроен, из каких этапов состоит и что на каждом этапе происходит.
Представьте, что вы отправляете посылку. Она проходит через несколько этапов:
1. Приемка (проверка упаковки).
2. Сортировка (определение пункта назначения).
3. Транспортировка (поездка на самолете).
4. Доставка (курьер везет до двери).
Если на любом этапе возникает проблема — посылка возвращается отправителю или задерживается.
Pipeline в CI/CD работает точно так же. Код проходит через этапы, и если на каком-то этапе происходит сбой — конвейер останавливается и сообщает об ошибке.
Обычно pipeline состоит из четырех основных этапов. Не все проекты используют все этапы, но в большинстве случаев они выглядят так:
Этап №1. Сборка (Build)
На данном этапе код компилируется в исполняемые файлы. Это нужно, чтобы проверить, что код не содержит синтаксических ошибок и может быть скомпилирован.
Для C#:
bash
dotnet restore # скачиваем библиотеки
dotnet build # компилируем
Для JavaScript:
bash
npm install # скачиваем библиотеки
npm run build # собираем (если нужна компиляция)
Если данный этап упал, то код содержит синтаксические ошибки. Разработчик должен их исправить.
Этап №2. Статический анализ (Lint / Code Quality)
На данном этапе код проверяется на соответствие стандартам, стилю и потенциальные проблемы. Это нужно, чтобы найти проблемы, которые не являются ошибками компиляции, но могут привести к багам в будущем:
- Неиспользуемые переменные.
- Слишком сложные методы.
- Нарушение правил именования.
- Потенциальные уязвимости безопасности.
Это как проверка орфографии в тексте. Ошибки не мешают прочитать текст, но делают его некрасивым и могут запутать читателя.
Примеры инструментов:
- Для C#:
StyleCop,SonarQube. - Для JavaScript:
ESLint,Prettier.
Если данный этап упал, то код написан не по правилам. Нужно исправить стиль или переписать слишком сложные участки.
Этап №3. Тестирование (Test)
На данном этапе запускаются все виды автоматических тестов. Этот этап часто разбивают на подэтапы:
| Подэтап | Что проверяет | Инструменты |
|---|---|---|
| Модульные тесты | Отдельные методы и классы | xUnit, NUnit (C#), Jest (JS) |
| Интеграционные тесты | Взаимодействие с БД, API | Testcontainers, WireMock |
| E2E-тесты | Полные сценарии пользователя | Playwright, Selenium |
Если данный этап упал, то код сломал существующую функциональность. Нужно найти, какой тест упал, и исправить код.
Этап №4. Развертывание (Deploy)
На данном этапе собранное приложение отправляется на сервер и запускается. Это нужно, чтобы код был доступен реальным пользователям (или тестировщикам). Варианты развертывания следующие:
| Вариант | Что происходит | Когда используется |
|---|---|---|
| Dev-среда | Развертывание на тестовый сервер для разработчиков | После каждого push |
| Staging-среда | Развертывание на среду, максимально близкую к боевой | Перед релизом |
| Production | Развертывание на боевой сервер | Только после одобрения релиза |
Если данный этап упал, то приложение не установилось на сервер. Нужно проверить настройки сервера или конфигурацию.
А что запускает конвейер?
Триггер
— это событие, которое запускает конвейер. Без триггера конвейер не начнет работу.
Основные типы триггеров:
1. Push (отправка кода)
Конвейер запускается, когда разработчик отправляет код в репозиторий.
yaml
on:
push:
branches:
- main
- develop
Данный триггер необходимо использовать для всех изменений в основных ветках.
2. Pull Request (создание запроса на вливание)
Конвейер запускается, когда разработчик создает pull request или обновляет его.
yaml
on:
pull_request:
branches:
- main
Его нужно использовать, когда необходимо проверить код перед его вливанием в основную ветку. Это позволяет убедиться, что изменения не сломают существующую функциональность, до того как они попадут в основную ветку.
3. По расписанию (Schedule)
Конвейер запускается по расписанию (например, каждый день в 3:00 ночи).
yaml
on:
schedule:
- cron: '0 3 * * *' # каждый день в 3:00
Это нужно для регулярных проверок (например, запуск нагрузочных тестов по ночам).
4. Вручную (Manual)
Конвейер запускается вручную через интерфейс GitHub/GitLab. Это нужно для развертывания на продакшен (чтобы случайно не выкатить нестабильный код).
Для того, чтобы узнать, как выглядит pipeline, представьте себе ленту конвейера:
wireframe
[Push] → [Сборка] → [Анализ] → [Тесты] → [Развертывание] → [Готово]
↑ ↑ ↑ ↑ ↑
Триггер Этап 1 Этап 2 Этап 3 Этап 4
Если любой этап красный (упал) — конвейер останавливается:
wireframe
[Push] → [Сборка] → [Анализ] → [Тесты] → ✗ ОШИБКА!
↑
Тесты упали
Разработчик смотрит, какой тест упал, исправляет ошибку и отправляет код заново.
Давайте рассмотрим более детально, что происходит на каждом этапе. Данные детали рассматриваются для проекта, написанным на языке программирования C#.
| Этап | Что делает | Как выглядит в коде (упрощенно) |
|---|---|---|
| Сборка | Компилирует код | dotnet build |
| Статический анализ | Проверяет стиль | dotnet format --verify |
| Модульные тесты | Проверяет логику | dotnet test --filter "Category=Unit" |
| Интеграционные тесты | Проверяет БД | dotnet test --filter "Category=Integration" |
| E2E-тесты | Проверяет UI | npx playwright test |
| Развертывание | Отправляет на сервер | scp или kubectl apply |
Порядок этапов не случаен. Он построен по принципу «дешевые проверки сначала, дорогие — потом»:
| Порядок | Почему |
|---|---|
| 1. Сборка | Самая rápida и дешевая проверка. Если код не компилируется — нет смысла запускать тесты. |
| 2. Статический анализ | Тоже быстрый. Проверяет стиль без выполнения кода. |
| 3. Тесты | Дороже (требуют окружение, БД). Но они находят реальные баги. |
| 4. Развертывание | Самое дорогое. Делаем только после того, как все проверки прошли. |
И это звучит логично, ведь в аэропорту сначала проверяют документы (быстро), потом багаж (медленнее), потом сажают в самолет (самое долгое). Если документы не в порядке — не тратят время на проверку багажа и посадку.
Когда разработчик отправляет код, он видит в GitHub/GitLab что-то похожее:
wireframe
✅ Сборка (прошла за 12 секунд)
✅ Стат. анализ (прошла за 5 секунд)
✅ Модульные тесты (прошла за 30 секунд)
✅ Интеграционные (прошла за 2 минуты)
⏳ E2E-тесты (выполняется... 3 минуты)
⬜ Развертывание (ожидает)
Или, если что-то упало:
wireframe
✅ Сборка (прошла)
✅ Стат. анализ (прошла)
❌ Модульные тесты (упали!)
Разработчик кликает на красный этап и видит, какой именно тест упал и какая ошибка произошла.
Более подробный интерфейс представлен ниже на изображении.

§9.3. Инструменты CI/CD: GitHub Actions и GitLab CI
GitHub Actions и GitLab CI
— это встроенные системы непрерывной интеграции, которые работают прямо внутри вашего репозитория.
Если у вас есть проект на GitHub — вы можете использовать GitHub Actions. Если на GitLab — GitLab CI. Обе системы делают одно и то же: автоматически собирают, тестируют и развертывают ваш код.
Это как встроенный конвейер прямо на вашем складе. Вам не нужно никуда ехать, ничего подключать — всё уже есть.
Раньше для CI/CD использовали отдельные серверы. Самым популярным был Jenkins.
Как это выглядело с Jenkins:
1. Вы поднимаете отдельный сервер (или виртуальную машину).
2. Устанавливаете на него Jenkins.
3. Настраиваете плагины (для Git, для .NET, для Docker и т.п.).
4. Настраиваете соединение с репозиторием.
5. Пишете скрипты сборки.
6. Поддерживаете этот сервер (обновления, бэкапы, перезапуски).
Проблемы этого подхода:
| Проблема | Что это значит |
|---|---|
| Сложно настраивать | Jenkins требует много ручной работы |
| Дорого | Нужен отдельный сервер (платить за хостинг) |
| Нужно поддерживать | Сервер нужно обновлять, следить за его работой |
| Плагины ломаются | Плагины не всегда совместимы с новыми версиями |
Что изменилось с GitHub Actions и GitLab CI:
| Что было раньше (Jenkins) | Что стало (GitHub/GitLab CI) |
|---|---|
| Отдельный сервер | Встроено в репозиторий |
| Сложная настройка | Простой YAML-файл |
| Нужно обслуживать сервер | Всё обслуживает GitHub/GitLab |
| Плагины могут конфликтовать | Всё работает «из коробки» |
Простая аналогия:
- Jenkins — это как купить машину, собрать ее из деталей, обслуживать, заправлять, страховать.
- GitHub Actions / GitLab CI — это как каршеринг: машина уже есть, заправлена, застрахована. Вы просто садитесь и едете.
И GitHub Actions, и GitLab CI работают по одному принципу: вы создаете конфигурационный файл, в котором описываете, что должен делать конвейер. Где лежит файл:
| Платформа | Путь к файлу |
|---|---|
| GitHub Actions | .github/workflows/название.yml |
| GitLab CI | .gitlab-ci.yml |
Внутри каждого файла вы описываете:
- Когда запускать конвейер (при push, pull request, по расписанию).
- Что делать (собрать, протестировать, развернуть).
- Где делать (на какой операционной системе).
Это как инструкция для робота. Вы пишете: «Когда увидишь новый код — собери его, запусти тесты, если всё хорошо — отправь на сервер».
Пример YAML-файла (упрощенно):
yaml
# Это НЕ обязательно запоминать, просто чтобы увидеть, как выглядит
name: CI
on: [push] # Триггер: при каждом push
jobs:
build:
runs-on: ubuntu-latest # Где выполнять (Ubuntu)
steps:
- name: Собрать проект
run: dotnet build
- name: Запустить тесты
run: dotnet test
А кто выполняет все наши команды?
Runner
— это виртуальная машина (или контейнер), на которой выполняются ваши команды.
Как это работает:
1. Вы отправляете код в репозиторий.
2. GitHub/GitLab находит свободный Runner.
3. Runner запускает виртуальную машину (например, Ubuntu Linux).
4. Runner выполняет все команды из вашего YAML-файла.
5. После завершения Runner уничтожает виртуальную машину.
Кто предоставляет Runner:
| Платформа | Откуда берется Runner |
|---|---|
| GitHub Actions | GitHub предоставляет бесплатные Runner в облаке |
| GitLab CI | GitLab предоставляет бесплатные Runner в облаке |
Какую операционную систему можно использовать:
| Операционная система | Для чего подходит |
|---|---|
| Ubuntu Linux | Универсальная, быстрая, бесплатная |
| Windows | Для C# проектов, где нужен Windows |
| macOS | Для iOS-приложений |
Бесплатные лимиты:
| Платформа | Бесплатные минуты в месяц |
|---|---|
| GitHub Actions | 2000 минут (около 33 часов) для бесплатных аккаунтов |
| GitLab CI | 400 минут (около 6.5 часов) для бесплатных аккаунтов |
Этого достаточно для учебного проекта и большинства небольших проектов. Теперь давайте сравним GitHub Actions и GitLab CI:
| Характеристика | GitHub Actions | GitLab CI |
|---|---|---|
| Где используется | В проектах на GitHub | В проектах на GitLab |
| Файл конфигурации | .github/workflows/*.yml | .gitlab-ci.yml |
| Бесплатные минуты | 2000 мин/месяц | 400 мин/месяц |
| Экосистема | Огромное количество готовых actions | Много готовых шаблонов |
| Сложность | Проще | Сложнее (больше возможностей) |
Давайте рассмотрим процесс работы от начала до конца:
Шаг №1. Разработчик пишет код
bash
git add .
git commit -m "Добавил новую функцию"
git push
Шаг №2. GitHub/GitLab видит push и запускает конвейер
Система находит свободный Runner и передает ему ваш YAML-файл.
Шаг №3. Runner выполняет команды
wireframe
✅ Скачал код из репозитория
✅ Восстановил зависимости (dotnet restore)
✅ Собрал проект (dotnet build)
✅ Запустил тесты (dotnet test)
✅ Все тесты зеленые!
Шаг №4. Результат виден в интерфейсе
Разработчик видит зеленый значок ✅ «All checks have passed». Если что-то упало: ❌ Тесты упали (1 тест провалился)
Разработчик видит красный значок ❌ и кликает на него, чтобы увидеть, какой тест упал.
Начинающие разработчики могут задать важный вопрос:
Дополнительные меры безопасности, которые принимаются:
1. Секреты (Secrets). Пароли и токены хранятся в зашифрованном виде.
2. Изоляция. Каждый Runner запускается отдельно.
3. Логи. Все логи доступны только владельцу репозитория.
§9.4. Знакомство с YAML: язык, на котором говорят конвейеры
YAML
— это простой язык для создания конфигурационных файлов.
Главное, что нужно запомнить
«YAML — это не код, а данные».
В YAML вы не пишете программу. Вы описываете структуру данных: «У меня есть такой-то параметр с таким-то значением».
YAML
— это как бланк анкеты. Вы заполняете поля: «Имя: Роман», «Фамилия: Максимов», «Возраст: 23», «Город: Казань», «Место работы – КИТ – КАИ». Вы не пишете программу, а просто заполняете форму.
YAML используется в следующих спецификациях:
| Инструмент / Технология | Для чего используется |
|---|---|
| CI/CD (GitHub Actions, GitLab CI) | Описание конвейера сборки |
| Docker Compose | Описание контейнеров |
| Kubernetes | Описание развертывания приложений |
| Ansible | Описание конфигурации серверов |
| OpenAPI / Swagger | Описание API |
Если вы работаете в IT или когда вы будете работать в IT — вы будете встречать YAML постоянно. Это стандарт индустрии.
YAML очень простой. Нужно запомнить всего три правила.
Правило №1. Всё решают отступы (пробелы)
В YAML структура данных определяется отступами. Это самый важный и самый странный для новичков момент. Как это выглядит:
yaml
# Так правильно (пробелы показывают вложенность)
имя: Иван
адрес:
город: Москва
улица: Тверская
дом: 10
Что здесь происходит:
- имя — параметр верхнего уровня.
- адрес — параметр верхнего уровня, который содержит внутри себя три других параметра.
- город, улица, дом — параметры, которые находятся внутри адрес. Они сдвинуты на 2 пробела вправо.
Важные правила про отступы:
| Правило | Пример |
|---|---|
| Используйте пробелы, НЕ табуляцию | ␣␣город: Москва (пробелы) |
| Количество пробелов должно быть одинаковым | Всегда 2 пробела (или 4) — одинаково |
| Отступ показывает вложенность | Чем больше отступ — тем глубже уровень |
Давайте рассмотрим простейшую аналогию. Представьте, что вы пишете список покупок. Вы пишете:
yaml
Продукты:
Молочные:
Молоко
Сыр
Мясные:
Курица
Говядина
Отступы показывают, что «Молоко» и «Сыр» относятся к «Молочным», а «Курица» и «Говядина» — к «Мясным».
Правило №2. Словари (ключ: значение)
Словарь
— это набор пар «ключ: значение».
Это основа YAML. Как выглядит словарь:
yaml
# Словарь с несколькими парами
имя: Иван
возраст: 25
город: Москва
Что здесь происходит:
- имя — ключ.
- Иван — значение.
- Двоеточие и пробел разделяют ключ и значение.
yaml
Словарь внутри словаря (вложенность):
пользователь:
имя: Иван
возраст: 25
адрес:
город: Москва
улица: Тверская
Сравнение с JSON (для понимания):
| JSON | YAML |
|---|---|
{ "имя": "Иван", "возраст": 25 } | имя: Иван |
возраст: 25 | |
{ "пользователь": { "имя": "Иван" } } | пользователь: |
␣␣имя: Иван |
YAML читается как обычный текст, без лишних скобок и кавычек.
Правило №3. Списки (массивы)
Список
— это набор элементов в определенном порядке.
Как выглядит список:
yaml
# Список продуктов
продукты:
- Молоко
- Сыр
- Хлеб
Что здесь происходит:
- продукты: — ключ.
- Молоко— элемент списка.- Дефис и пробел показывают элемент списка.
Список внутри словаря:
yaml
пользователь:
имя: Иван
заказы:
- номер: 1
сумма: 1000
- номер: 2
сумма: 2000
Сравнение с JSON:
| JSON | YAML |
|---|---|
["Молоко", "Сыр", "Хлеб"] | - Молоко |
- Сыр | |
- Хлеб | |
[{"номер": 1}, {"номер": 2}] | - номер: 1 |
- номер: 2 |
А что же проще, JSON или YAML? Давайте рассмотрим их характеристики:
| Характеристика | JSON | YAML |
|---|---|---|
| Читаемость | Сложно (много скобок и запятых) | Просто (как обычный текст) |
| Размер | Больше (лишние символы) | Меньше (нет лишних скобок) |
| Для людей | Трудно читать | Легко читать |
| Для машин | Легко парсить | Тоже легко |
| Где используется | API, передача данных | Конфигурационные файлы |
Пример одного и того же в JSON и YAML. :
{
"имя": "Иван",
"возраст": 25,
"адрес": {
"город": "Москва",
"улица": "Тверская"
},
"хобби": ["футбол", "музыка"]
}
Какой вариант проще прочитать? Конечно, YAML. Именно поэтому его используют для конфигураций — вы должны понимать, что написано, без лишних усилий.
Теперь давайте посмотрим, как выглядит реальный файл для GitHub Actions.
Файл .github/workflows/build.yml:
yaml
# Имя конвейера (отображается в интерфейсе)
name: Build and Test
# Триггер: когда запускать
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# Jobs: что делать
jobs:
# Job с именем build
build:
# На какой ОС запускать
runs-on: ubuntu-latest
# Steps: шаги внутри job
steps:
# Шаг 1: скачать код
- name: Checkout code
uses: actions/checkout@v3
# Шаг 2: установить .NET SDK
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0'
# Шаг 3: восстановить зависимости
- name: Restore dependencies
run: dotnet restore
# Шаг 4: собрать проект
- name: Build
run: dotnet build --no-restore
# Шаг 5: запустить тесты
- name: Run tests
run: dotnet test --no-build --verbosity normal
Теперь давайте разберем данный код по частям:
| Часть | Что делает | Синтаксис |
|---|---|---|
name: | Имя конвейера | Строка |
on: | Триггеры | Словарь |
jobs: | Список заданий | Словарь со списками |
runs-on: | Операционная система | Строка |
steps: | Шаги внутри задания | Список |
- name: | Имя шага | Строка |
uses: | Использовать готовое действие | Строка |
run: | Выполнить команду | Строка |
На русском это читается следующим образом: «Конвейер с именем Build and Test. Запускайся при push в ветку main или при создании pull request в main. В задании build используй Ubuntu. Выполни следующие шаги: скачай код, установи .NET SDK, восстанови зависимости, собери проект, запусти тесты.».
Теперь рассмотрим пример для GitLab CI. В GitLab синтаксис похожий, но чуть другой:
yaml
# stages: этапы конвейера
stages:
- build
- test
# Job: сборка
build:
stage: build
image: mcr.microsoft.com/dotnet/sdk:8.0
script:
- dotnet restore
- dotnet build
# Job: тесты
test:
stage: test
image: mcr.microsoft.com/dotnet/sdk:8.0
script:
- dotnet test
Рассмотрим разницу между GitHub Actions и GitLab CI:
| GitHub Actions | GitLab CI |
|---|---|
jobs: — список заданий | stages: — этапы |
runs-on: — операционная система | image: — Docker-образ |
steps: — шаги внутри задания | script: — команды внутри задания |
Как уже было сказано ранее, самая частая ошибка новичков в YAML — неправильные отступы.
yaml
# ❌ НЕПРАВИЛЬНО (разные отступы)
steps:
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build
# ✅ ПРАВИЛЬНО (одинаковые отступы)
steps:
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build
Как проверить, что отступы правильные:
1. Все элементы одного уровня должны иметь одинаковый отступ.
2. Вложенные элементы должны иметь больший отступ.
3. Используйте только пробелы (не табуляцию).
Большинство редакторов кода (VS Code, Visual Studio) подсвечивают ошибки в YAML. Это помогает заметить проблему до того, как вы отправите код.
§9.5. Структура CI-конвейера: собираем и тестируем
Теперь, когда мы знаем, что такое YAML и как он работает, давайте напишем наш первый конвейер.
Что должно происходить при git push:
1. Скачать код из репозитория.
2. Восстановить зависимости (NuGet/npm).
3. Собрать проект.
4. Запустить тесты.
5. Показать результат (зеленый/красный).
Это минимальный набор действий для любого CI-конвейера. Если тесты проходят — всё хорошо. Если падают — разработчик должен исправить ошибку.
Давайте напишем конвейер для проекта на C#. Он будет запускаться при каждом git push в ветку main. В корне репозитория создайте папку и файл:
text
.github/workflows/build.yml
Структура любого YAML-файла для CI/CD:
yaml
# 1. Имя конвейера
name: Название
# 2. Триггер: когда запускать
on: [push]
# 3. Задания: что делать
jobs:
имя_задания:
runs-on: ubuntu-latest
steps:
- name: Описание шага
run: команда
Теперь заполним каждую часть конкретными значениями. Вот полный файл для GitHub Actions, который собирает и тестирует C# проект:
yaml
# Имя конвейера (показывается в интерфейсе GitHub)
name: Build and Test
# Триггер: запускаем при каждом push в main и develop
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
# Задания: что делаем
jobs:
# Задание с именем "build"
build:
# На какой ОС запускать (Ubuntu — самая популярная)
runs-on: ubuntu-latest
# Шаги внутри задания
steps:
# Шаг 1: скачать код из репозитория
- name: Checkout code
uses: actions/checkout@v4
# Шаг 2: установить .NET SDK (версия 8.0)
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0'
# Шаг 3: восстановить NuGet-пакеты
- name: Restore dependencies
run: dotnet restore
# Шаг 4: собрать проект в режиме Release
- name: Build project
run: dotnet build --configuration Release --no-restore
# Шаг 5: запустить все тесты
- name: Run tests
run: dotnet test --configuration Release --no-build --verbosity normal
Давайте разберем этот код по частям:
| Часть | Что делает |
|---|---|
name: Build and Test | Имя конвейера. Отображается в интерфейсе GitHub. |
on: push: branches: [ main, develop ] | Запускается при push в main или develop. |
on: pull_request: branches: [ main ] | Запускается при создании pull request в main. |
runs-on: ubuntu-latest | Выполняется на свежей версии Ubuntu. |
uses: actions/checkout@v4 | Скачивает код из репозитория (готовое действие от GitHub). |
uses: actions/setup-dotnet@v4 | Устанавливает .NET SDK (готовое действие от GitHub). |
run: dotnet restore | Восстанавливает пакеты. |
run: dotnet build | Собирает проект. |
run: dotnet test | Запускает тесты. |
Что означают флаги в dotnet-командах:
| Команда | Что делает |
|---|---|
dotnet restore | Скачивает все NuGet-пакеты. |
dotnet build --configuration Release | Собирает проект в режиме Release. |
dotnet build --no-restore | Не восстанавливает пакеты перед сборкой (мы уже сделали это ранее). |
dotnet test --configuration Release --no-build | Запускает тесты без пересборки (использует уже собранные файлы). |
Для JavaScript проект на GitHub Actions будет выглядеть так:
yaml
name: Build and Test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
Давайте рассмотрим разницу между двумя YAML – кодами:
| Что | C# | JavaScript |
|---|---|---|
| Установка языка | actions/setup-dotnet | actions/setup-node |
| Версия | dotnet-version: '8.0' | node-version: '18' |
| Восстановление зависимостей | dotnet restore | npm ci |
| Сборка | dotnet build | Может отсутствовать (или tsc) |
| Тесты | dotnet test | npm test |
GitLab использует похожий синтаксис, но с другими ключевыми словами:
yaml
# stages: определяем порядок этапов
stages:
- build
- test
# Job для сборки
build:
stage: build
image: mcr.microsoft.com/dotnet/sdk:8.0
script:
- dotnet restore
- dotnet build --configuration Release
# Job для тестов
test:
stage: test
image: mcr.microsoft.com/dotnet/sdk:8.0
script:
- dotnet test --configuration Release --no-build
После отправки кода разработчик видит в интерфейсе GitHub/GitLab процесс выполнения. В GitHub Actions процесс выполнения выглядит примерно следующим образом:
wireframe
✅ Build and Test / build (pull_request)
✅ Checkout code (1s)
✅ Setup .NET (5s)
✅ Restore dependencies (3s)
✅ Build project (10s)
✅ Run tests (15s)
✅ All checks have passed
GitLab CI:
wireframe
text
✅ build (passed)
✅ test (passed)
Pipeline passed
Если тесты упали:
wireframe
❌ Build and Test / build (pull_request)
✅ Checkout code (1s)
✅ Setup .NET (5s)
✅ Restore dependencies (3s)
✅ Build project (10s)
❌ Run tests (15s) ← здесь ошибка
1 test failed: UserServiceTests.Login_InvalidPassword_ReturnsError
Разработчик кликает на упавший тест и видит ошибку.
Что нужно запомнить при работе с CI в GitHub Actions или GitLab CI:
1. Конвейер запускается автоматически при каждом push или pull request.
2. Любой конвейер состоит из:
- Триггер (on: или stages:).
- Задания (jobs: или stages:).
- Шаги (steps: или script:).
3. Главное правило: если тесты упали — код не попадает в основную ветку.
4. Для C# проекта нужны: .NET SDK, dotnet restore, dotnet build, dotnet test.
5. Для JavaScript проекта нужны: Node.js, npm ci, npm test.
§9.6. Дополнительные возможности CI/CD
В предыдущем параграфе мы научились писать простой конвейер, который собирает код и запускает тесты. Этого достаточно для базовой проверки, но в реальных проектах нужны дополнительные возможности.
Какие задачи возникают:
| Задача | Проблема |
|---|---|
| Пароли и токены | Как использовать секретные данные, не показывая их в коде? |
| Результаты тестов | Как посмотреть отчеты, если тесты упали? |
| Уведомления | Как узнать, что сборка сломалась, не заходя в репозиторий? |
Рассмотрим каждую задачу по порядку.
1. Secrets (Секреты) – как хранить пароли и токены
В вашем коде могут быть секретные данные:
- Пароли к базе данных.
- API-ключи (например, для отправки email).
- Токены доступа к внешним сервисам.
- Строки подключения к БД.
Что будет, если захардкодить их в коде:
yaml
# ❌ НИКОГДА ТАК НЕ ДЕЛАЙТЕ!
steps:
- name: Deploy to server
run: |
ssh user@server.com
password: my_secret_password_123 # Пароль в открытом виде!
Чем это опасно:
- Любой, у кого есть доступ к репозиторию, увидит пароль.
- Пароль будет в истории коммитов навсегда.
- Злоумышленники могут использовать эти данные.
Для решения используется Secrets (Секреты).
Secrets
— это зашифрованные переменные, которые хранятся в настройках репозитория. Они доступны только во время выполнения конвейера.
Как это работает:
1. Вы добавляете секрет в настройках GitHub/GitLab.
2. В YAML-файле вы обращаетесь к нему через специальный синтаксис.
3. Конвейер использует секрет, но не показывает его в логах.
Как добавить секрет в GitHub:
1. Зайдите в репозиторий на GitHub.
2. Нажмите Settings → Secrets and variables → Actions.
3. Нажмите New repository secret.
4. Введите имя (например, DATABASE_PASSWORD) и значение (пароль).
5. Нажмите Add secret.
Как использовать секрет в YAML:
yaml
steps:
- name: Run tests with database
env:
DB_PASSWORD: ${{ secrets.DATABASE_PASSWORD }} # Подставляем секрет
run: dotnet test --environment DB_PASSWORD=$DB_PASSWORD
Как добавить секрет в GitLab:
1. Зайдите в проект на GitLab.
2. Нажмите Settings → CI/CD.
3. Разверните раздел Variables.
4. Нажмите Add variable.
5. Введите ключ (например, DATABASE_PASSWORD) и значение.
6. Выберите Protect variable (защитить) и Mask variable (скрыть в логах).
7. Нажмите Add variable.
Как использовать секрет в YAML (GitLab):
yaml
variables:
DB_PASSWORD: $DATABASE_PASSWORD # Подставляем секрет
job:
script:
- echo "Running tests with DB_PASSWORD"
- dotnet test
2. Artifacts (Артефакты): как сохранять результаты тестов
Тесты упали, но вы не знаете почему. В логах только сообщение «Test failed». Нужно посмотреть детальный отчет, но после завершения конвейера все временные файлы удаляются.
Для решения используются Artifacts (Артефакты).
Артефакты
— это файлы, которые сохраняются после завершения конвейера. Вы можете скачать их и посмотреть результаты тестов, логи, скриншоты.
Что можно сохранять как артефакты:
| Что сохранять | Зачем |
|---|---|
| Отчеты о тестах (JUnit XML, HTML) | Посмотреть, какие тесты упали |
| Логи сборки | Понять, что пошло не так |
| Скриншоты (E2E-тесты) | Увидеть, как выглядела страница в момент ошибки |
| Собранное приложение | Скачать и запустить локально |
Как сохранить артефакты в GitHub Actions:
yaml
steps:
# ... сборка и тесты ...
# Сохраняем результаты тестов как артефакт
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: test-results
path: TestResults/ # Папка с результатами тестов
retention-days: 7 # Хранить 7 дней
Как же это выглядит для пользователя? После завершения конвейера в интерфейсе GitHub появляется кнопка Artifacts. Вы кликаете на нее и скачиваете файлы.
Пример для .NET (xUnit):
yaml
# Настройка вывода тестов в файл
- name: Run tests with output
run: dotnet test --logger "trx;LogFileName=test_results.trx"
# Сохраняем результат
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: test-results
path: TestResults/*.trx
Как сохранить артефакты в GitLab CI:
job:
script:
- dotnet test --logger "trx;LogFileName=test_results.trx"
artifacts:
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
paths:
- TestResults/*.trx
expire_in: 7 days
3. Уведомления: как понять, что сборка сломалась
Вы отправили код и ушли пить кофе. Через 10 минут сборка упала, но вы не знаете об этом. Через час вы возвращаетесь и видите красный значок. Время потеряно.
Для решения используются Уведомления. CI/CD может отправлять уведомления о результатах сборки:
- Email.
- Telegram.
- Slack.
- Встроенные уведомления GitHub/GitLab.
GitHub сам отправляет уведомления на email, если вы подписаны на изменения в репозитории.
1. Зайдите в репозиторий.
2. Нажмите Watch → Custom.
3. Выберите Actions.
4. Теперь вы будете получать уведомления о всех запусках конвейеров.
Уведомления в Telegram (GitHub Actions):
yaml
steps:
# ... сборка и тесты ...
# Отправка уведомления в Telegram
- name: Send Telegram notification
if: always() # Отправить всегда (успех или неудача)
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_TOKEN }}
message: |
${{ github.repository }}
Status: ${{ job.status }}
Commit: ${{ github.sha }}
Уведомления в Slack (GitHub Actions):
yaml
- name: Send Slack notification
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,commit,author,message
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
В GitLab уведомления настраиваются через интеграции:
1. Зайдите в проект → Settings → Integrations.
2. Выберите нужный сервис (Slack, Telegram, Mattermost).
3. Настройте webhook-URL.
4. Включите события для уведомлений (Push, Tag push, Pipeline).
Давайте разберем, что можно делать с артефактами и секретами:
| Возможность | Что делает | Пример использования |
|---|---|---|
| Артефакты между джобами | Передать файлы из одной джобы в другую | Собрать → передать на тестирование |
| Секреты для разных сред | Разные пароли для dev/staging/prod | Dev: test password, Prod: real password |
| Временные секреты | Сгенерировать и использовать одноразовый токен | Использовать и забыть |
§9.7. Что должно быть в CI-конвейере для проекта
В §9.5 мы написали простой конвейер, который собирает проект и запускает тесты. Но в реальном проекте этого недостаточно. Вам нужно не просто запустить тесты, а запустить все виды тестов, которые вы писали.
Что должно быть в полноценном конвейере:
| Этап | Что проверяет |
|---|---|
| 1. Восстановление зависимостей | Скачать все библиотеки |
| 2. Сборка проекта | Проверить, что код компилируется |
| 3. Статический анализ | Проверить стиль и качество кода |
| 4. Модульные тесты | Проверить отдельные методы |
| 5. Интеграционные тесты | Проверить работу с БД |
| 6. E2E-тесты | Проверить полные сценарии |
Этап №1. Восстановление зависимостей
Если ваш проект использует сторонние библиотеки. Перед сборкой их нужно скачать и установить. Для C# (.NET):
yaml
- name: Restore dependencies
run: dotnet restore
dotnet restore скачивает все NuGet-пакеты, указанные в файле .csproj.
Для JavaScript (Node.js):
yaml
- name: Install dependencies
run: npm ci
npm ci (clean install) устанавливает все пакеты из package-lock.json. Это быстрее и надежнее, чем npm install.
Этап №2. Сборка проекта (Build)
Это нужно, чтобы проверить, что код компилируется без ошибок. Если проект не собирается — нет смысла запускать тесты.
Для C# (.NET):
yaml
- name: Build project
run: dotnet build --configuration Release --no-restore
Для JavaScript (TypeScript): eсли вы используете TypeScript, нужно проверить, что типы корректны:
yaml
yaml
- name: Type check
run: npx tsc --noEmit
--noEmit означает «не создавать файлы, только проверить типы».
Если вы используете сборщик (Webpack, Vite):
yaml
- name: Build project
run: npm run build
Этап №3. Статический анализ кода (Linter)
Linter
— это инструмент, который проверяет код на:
- Стилистические ошибки (отступы, пробелы).
- Неиспользуемые переменные и методы.
- Потенциальные баги.
- Нарушение правил оформления.
Linter
— это как проверка орфографии в тексте. Текст можно прочитать и без нее, но ошибки делают его некрасивым и могут запутать читателя.
Для C# (.NET) есть несколько вариантов:
1. dotnet format (встроенный):
yaml
- name: Check code style
run: dotnet format --verify-no-changes
--verify-no-changes проверяет, что код уже отформатирован правильно. Если есть нарушения — конвейер упадет.
2. StyleCop (анализатор стиля): добавьте в проект пакет StyleCop.Analyzers. Ошибки будут отображаться как предупреждения при сборке.
3. SonarQube (полный анализ):
yaml
- name: Run SonarQube analysis
run: |
dotnet tool install --global dotnet-sonarscanner
dotnet sonarscanner begin /k:"project-key"
dotnet build
dotnet sonarscanner end
SonarQube проверяет не только стиль, но и безопасность, сложность кода, дублирование. Для JavaScript (Node.js): ESLint — стандартный линтер для JavaScript/TypeScript:
yaml
- name: Run linter
run: npm run lint
В package.json должен быть скрипт:
json
"scripts": {
"lint": "eslint src/**/*.ts"
}
Этап №4. Запуск модульных тестов
Это нужно, чтобы проверить, что отдельные методы и классы работают правильно. Модульные тесты — это основа пирамиды тестирования. Их должно быть много, и они должны быть быстрыми.
Для C# (.NET):
yaml
- name: Run unit tests
run: dotnet test --configuration Release --no-build --filter "Category=Unit"
--filter "Category=Unit" запускает только тесты с атрибутом [Trait("Category", "Unit")].
Для JavaScript (Node.js):
yaml
- name: Run unit tests
run: npm test
Или с Jest:
yaml
- name: Run unit tests
run: npm run test:unit
Этап №5. Запуск интеграционных тестов
Это нужно, чтобы проверить взаимодействие с реальной базой данных, внешними API и другими системами.
Интеграционные тесты требуют наличия базы данных. В CI окружении её нужно поднять.
Для C# (.NET) это происходит с помощью Testcontainers. Testcontainers поднимает Docker-контейнер с БД прямо во время тестов.
yaml
- name: Run integration tests
run: dotnet test --configuration Release --no-build --filter "Category=Integration"
env:
# Testcontainers автоматически использует Docker
TESTCONTAINERS_RYUK_DISABLED: true
Для JavaScript (Node.js) с Testcontainers, аналогично, поднимаем Docker-контейнер с БД:
yaml
- name: Run integration tests
run: npm run test:integration
Этап №6. Запуск E2E-тестов
Это нужно, чтобы проверить полные пользовательские сценарии через реальный браузер.
Для C# (.NET) с Playwright:
yaml
- name: Install Playwright browsers
run: pwsh ./playwright.ps1 install --with-deps
- name: Run E2E tests
run: dotnet test --configuration Release --no-build --filter "Category=E2E"
Для JavaScript (Node.js) с Playwright:
yaml
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npm run test:e2e
Чтобы все этапы работали, нужно правильно настроить окружение на CI-раннере.
Для C# (.NET):
yaml
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0'
Для JavaScript (Node.js):
yaml
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
Для Docker (нужен для Testcontainers). В GitHub Actions Docker уже установлен на Ubuntu-раннерах по умолчанию. Ничего делать не нужно. В GitLab CI Docker тоже доступен.
Для Playwright (браузеры):
yaml
- name: Install Playwright dependencies
run: |
npx playwright install --with-deps
В конце стоит сравнить настройку CI для C# и JavaScript:
| Этап | C# (.NET) | JavaScript (Node.js) |
|---|---|---|
| Установка окружения | actions/setup-dotnet | actions/setup-node |
| Версия | dotnet-version: '8.0' | node-version: '18' |
| Восстановление зависимостей | dotnet restore | npm ci |
| Сборка | dotnet build | npx tsc --noEmit или npm run build |
| Статический анализ | dotnet format --verify-no-changes | npm run lint (ESLint) |
| Модульные тесты | dotnet test --filter "Category=Unit" | npm run test:unit |
| Интеграционные тесты | dotnet test --filter "Category=Integration" | npm run test:integration |
| E2E-тесты | dotnet test --filter "Category=E2E" | npm run test:e2e |
| Playwright установка | pwsh ./playwright.ps1 install --with-deps | npx playwright install --with-deps |
| Docker | Нужен для Testcontainers | Нужен для Testcontainers |
§9.8. Антипаттерны в CI/CD: чего делать НЕ надо
CI/CD
— это мощный инструмент, но только если вы используете его правильно.
Есть несколько типичных ошибок, которые сводят на нет всю пользу от автоматизации.
Главная мысль этого параграфа заключается в том, что CI/CD — это не просто «написал и забыл». Это инструмент, который требует внимания и правильной настройки.
Антипаттерн №1. «Написал конвейер и забыл»
Разработчик настроил CI/CD, увидел зеленый значок один раз и больше никогда не заглядывает в результаты. Конвейер работает, тесты проходят — и все довольны.
Пример:
yaml
# Конвейер работал 6 месяцев назад
- name: Setup .NET
uses: actions/setup-dotnet@v2 # Старая версия!
with:
dotnet-version: '6.0' # Устаревшая версия
.NET 6.0 больше не поддерживается, setup-dotnet@v2 устарел. Конвейер падает при каждом запуске, но никто не обращает внимания.
Что нужно делать:
1. Смотреть на результаты после каждого push. Если конвейер красный — разбираться сразу, а не откладывать.
2. Регулярно обновлять конфигурацию. Версии инструментов, плагинов и языков обновляются — нужно следить за этим.
3. Настроить уведомления. Если конвейер упал — вы должны узнать об этом сразу (см. §9.6).
Антипаттерн №2. «Долгий конвейер»
Конвейер работает 2 – 3 часа. Разработчики отправляют код и идут пить чай, смотреть сериалы или заниматься другими делами. Обратная связь приходит слишком поздно. Почему это плохо:
| Проблема | Последствия |
|---|---|
| Долгая обратная связь | Разработчик переключается на другую задачу и забывает про код |
| Снижение продуктивности | За день можно сделать только 2 – 3 итерации вместо 20 |
| Очереди в CI | Если 10 разработчиков отправляют код, конвейеры стоят в очереди |
Почему конвейер становится долгим:
| Причина | Что делать |
|---|---|
| Слишком много тестов (все E2E) | Вынести E2E в отдельный конвейер (запускать только в main) |
| Тесты на разных языках последовательно | Запускать параллельно (Matrix strategy) |
| Подготовка данных через UI | Использовать API для подготовки данных |
| Долгая сборка | Использовать кеширование зависимостей |
А как ускорить конвейер? Например, использовать параллельный запуск тестов (Matrix).
yaml
jobs:
test:
strategy:
matrix:
test_group: [Unit, Integration, E2E]
runs-on: ubuntu-latest
steps:
- name: Run ${{ matrix.test_group }} tests
run: dotnet test --filter "Category=${{ matrix.test_group }}"
Следующим шагом, можно использовать кеширование зависимостей.
yaml
- name: Cache NuGet packages
uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
Так же можно каждый конвейер разделять на несколько этапов.
yaml
# Быстрый конвейер (3 минуты)
name: Quick Check
on: push
jobs:
quick:
steps:
- dotnet restore
- dotnet build
- dotnet test --filter "Category=Unit"
где, полный конвейер (запускается только в main)
yaml
name: Full Check
on: push to main
jobs:
full:
steps:
- dotnet restore
- dotnet build
- dotnet test --filter "Category=Unit"
- dotnet test --filter "Category=Integration"
- dotnet test --filter "Category=E2E"
Вот представьте, что Вы ждете пиццу. Если курьер едет 2 часа — вы заказываете в другом месте. Чем быстрее обратная связь — тем быстрее вы исправляете ошибки.
Антипаттерн №3. «Секреты в коде»
Разработчик хранит пароли, токены и строки подключения прямо в YAML-файле. Пример как делать НЕ нужно:
yaml
# ❌ СТРОГО ЗАПРЕЩЕНО!
steps:
- name: Deploy to database
run: |
connection_string="Server=prod-server;Database=Shop;User Id=admin;Password=SuperSecret123!"
dotnet run --connection-string "$connection_string"
Почему это катастрофа:
| Что произойдет | Последствия |
|---|---|
| Пароль виден всем, у кого есть доступ к репозиторию | Любой разработчик может увидеть пароль |
| Пароль остается в истории коммитов навсегда | Даже если вы удалите его, он останется в Git |
| Если пароль скомпрометирован | Нужно менять пароль во всех системах |
| Злоумышленники могут украсть пароль | Если репозиторий публичный — пароль у всех |
Для того, чтобы делать все правильно, нужно следовать следующим шагам:
Шаг №1. Использовать Secrets (GitHub Actions):
yaml
# ✅ ПРАВИЛЬНО
steps:
- name: Deploy to database
run: dotnet run --connection-string "${{ secrets.DB_CONNECTION_STRING }}"
Шаг №2. Использовать Variables (GitLab CI):
yaml
# ✅ ПРАВИЛЬНО
variables:
DB_CONNECTION_STRING: $DB_CONNECTION_STRING # Из настроек проекта
Шаг №3. Использовать переменные окружения:
yaml
# ✅ ПРАВИЛЬНО
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
Зачем это нужно? Представьте, что вы пишете пароль от банковской карты на стикере и приклеиваете его на монитор. Все, кто проходит мимо, видят его. Тот, кто получает доступ к вашему компьютеру — получает доступ к деньгам.
Антипаттерн №4. «Зеленый конвейер, но всё сломалось»
Все тесты проходят (зеленый значок), но после релиза система падает. Конвейер показывает успех, а пользователи не могут работать.
Почему это возможно:
| Причина | Что проверяет тесты | Что НЕ проверяет тесты |
|---|---|---|
| Нет тестов на новый код | Только старую функциональность | Новые методы не покрыты тестами |
| Нет тестов на интеграцию | Отдельные модули | Взаимодействие между модулями |
| Нет тестов на регрессию | Только позитивные сценарии | Крайние случаи и ошибки |
| Нет E2E-тестов | API и сервисы | Интерфейс пользователя |
Давайте рассмотрим следующим пример:
csharp
// Разработчик добавил новый метод
public async Task<User> GetUserAsync(int id)
{
// Новый метод, но тестов для него нет!
return await _context.Users.FindAsync(id);
}
Конвейер зеленый (тесты проходят), потому что:
- Старые тесты не проверяют новый метод.
- Интеграционных тестов нет.
- E2E-тестов нет.
В продакшене метод GetUserAsync вызывается, падает с ошибкой, пользователи не могут войти в систему.
Для того, чтобы это исправить, нужно следовать следующим шагам:
Шаг №1. Проверять покрытие тестами (Code Coverage): нужно добавить в конвейер проверку, что тесты покрывают хотя бы 80% кода.
yaml
- name: Run tests with coverage
run: dotnet test --collect:"XPlat Code Coverage"
- name: Check coverage threshold
run: |
reportgenerator -reports:TestResults/*.cobertura.xml -targetdir:coveragereport
coverage=$(cat coveragereport/Summary.xml | grep -oP 'lineRate="\K[0-9.]+')
if (( $(echo "$coverage < 0.8" | bc -l) )); then
echo "❌ Coverage is $coverage, below 80%!"
exit 1
fi
Шаг №2. Добавить мутационное тестирование. Инструменты типа Stryker проверяют, найдут ли тесты ошибку, если изменить код.
Шаг №3. Писать тесты до кода (TDD). Сначала пишете тест (который падает), потом пишете код (который делает тест зеленым).