0Pricing
DevOps Bootcamp · Урок

Запуск тестов Shell в CI-конвейерах

Подключайте ShellCheck и Bats к GitHub Actions, чтобы каждая правка Shell проходила проверку только при успешных результатах

«Запуск тестов Shell в CI-конвейерах» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 4 уроков всего.

Зачем нужны CI для скриптов оболочки

Скрипты оболочки — это код, и, как любой код, они заслуживают автоматических проверок качества. Без CI опечатка в скрипте развёртывания может незаметно попасть в рабочую среду и вызвать сбой в три часа ночи.

Надёжный конвейер CI для проектов на Bash обеспечивает две вещи при каждом запросе на включение изменений:

  • Статический анализ с помощью ShellCheck — обнаруживает синтаксические ошибки, небезопасные шаблоны и проблемы переносимости POSIX ещё до запуска скрипта.
  • Модульные и интеграционные тесты с помощью Bats (Bash Automated Testing System) — выполняет Ваши функции и проверяет правильность их работы.

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

Введение в GitHub Actions для проектов со скриптами оболочки

GitHub Actions — это управляемая событиями система CI/CD, встроенная в GitHub. Рабочий процесс представляет собой YAML-файл, хранящийся в каталоге .github/workflows/. Он запускается по событиям (отправка изменений, pull_request и т. д.) и выполняет задания на предоставленных исполнителях.

Основные понятия:

  • on: — событие запуска (например, push, pull_request)
  • jobs: — параллельные единицы работы, каждая на новой VM
  • steps: — последовательные команды оболочки или повторно используемые действия внутри задания
  • runs-on: — образ исполнителя (мы используем ubuntu-latest)

Файлы рабочих процессов необходимо зафиксировать в репозитории. GitHub обнаруживает их автоматически — дополнительная внешняя настройка не требуется.

# Minimal skeleton — .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  shell-checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: echo "Steps go here"

Установка ShellCheck в рабочем процессе

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

Две стратегии установки:

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

Ниже показан подход с фиксированной версией: строка версии хранится в переменной окружения, поэтому для обновления достаточно изменить одну строку.

# .github/workflows/ci.yml  — ShellCheck install step
- name: Install ShellCheck
  env:
    SC_VERSION: v0.10.0
  run: |
    curl -sSfL \
      "https://github.com/koalaman/shellcheck/releases/download/${SC_VERSION}/shellcheck-${SC_VERSION}.linux.x86_64.tar.xz" \
      | tar -xJf - --strip-components=1 -C /usr/local/bin shellcheck-${SC_VERSION}/shellcheck
    shellcheck --version

Запуск ShellCheck для каждого скрипта

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

Важные флаги:

  • -e SC2034 — исключает определённое правило (используйте редко и с комментарием).
  • --severity=warning — завершает работу с ошибкой только при предупреждениях и более серьёзных проблемах, игнорируя рекомендации по стилю.
  • -x — отслеживает директивы source, чтобы анализировать также подключённые файлы.

Если shellcheck обнаруживает проблему, он завершается с ненулевым кодом, что автоматически делает шаг CI неуспешным — дополнительная логика не нужна.

# .github/workflows/ci.yml  — ShellCheck lint step
- name: Lint shell scripts
  run: |
    # Find all .sh files and files with a bash/sh shebang
    mapfile -t scripts < <(
      find . -type f -name '*.sh' -not -path './.git/*'
    )
    if [[ ${#scripts[@]} -eq 0 ]]; then
      echo 'No shell scripts found — skipping.'
      exit 0
    fi
    echo "Linting ${#scripts[@]} file(s)..."
    shellcheck --severity=warning -x "${scripts[@]}"

Что такое Bats и как он работает

Bats (Bash Automated Testing System) — совместимая с TAP платформа тестирования Bash. Каждый файл тестов имеет расширение .bats и содержит блоки @test.

Тест считается пройденным, если его тело завершается с кодом 0, и непройденным — если с ненулевым кодом. Bats предоставляет вспомогательные переменные и функции:

  • $status — код завершения последней команды run.
  • $output — объединённый стандартный вывод и вывод ошибок последней команды run.
  • $lines — массив строк вывода.
  • run <cmd> — выполняет команду, не завершая тест с ошибкой при ненулевом коде выхода.

Вспомогательная команда run крайне важна: без неё неуспешная команда прервала бы тест до того, как Вы смогли бы проверить $status.

#!/usr/bin/env bats
# tests/greet.bats

setup() {
  # Runs before every @test block
  source "${BATS_TEST_DIRNAME}/../lib/greet.sh"
}

@test "greet outputs hello with the given name" {
  run greet "Alice"
  [ "$status" -eq 0 ]
  [ "$output" = "Hello, Alice!" ]
}

@test "greet fails when no argument is provided" {
  run greet
  [ "$status" -eq 1 ]
  [[ "$output" == *"Usage"* ]]
}

Установка Bats-Core с помощью подмодуля Git

Канонический способ добавить Bats в проект — использовать подмодуль Git. Это фиксирует конкретную ревизию, обеспечивает одинаковую версию средства запуска локально и в CI и избавляет от зависимости от менеджеров пакетов.

Выполните эти команды один раз локально, затем зафиксируйте результат:

  • git submodule add https://github.com/bats-core/bats-core test/bats
  • git submodule add https://github.com/bats-core/bats-support test/test_helper/bats-support
  • git submodule add https://github.com/bats-core/bats-assert test/test_helper/bats-assert

В CI восстановите подмодули с помощью actions/checkout@v4 и параметра submodules: recursive. Ниже показана полная конфигурация получения кода.

# .github/workflows/ci.yml  — checkout with submodules
- name: Checkout repository
  uses: actions/checkout@v4
  with:
    submodules: recursive   # restores bats-core + helpers

Запуск тестов Bats в CI

Когда Bats доступен (через подмодуль или установку пакета), запуск тестов выполняется одной командой. Укажите каталог, и Bats рекурсивно найдёт каждый файл .bats с флагом --recursive.

Флаг --formatter tap выводит данные в формате TAP (Test Anything Protocol), который многие системы CI анализируют для формирования отчётов о тестах. Форматировщик pretty по умолчанию лучше подходит для чтения человеком в необработанных журналах.

Используйте --timing, чтобы заранее выявлять медленные тесты: тест, выполняющийся более 5 секунд, обычно указывает на нежелательный сетевой вызов или отсутствующую имитацию.

# .github/workflows/ci.yml  — Bats test step
- name: Run Bats tests
  run: |
    # If installed as a submodule:
    ./test/bats/bin/bats \
      --recursive \
      --timing \
      tests/

    # If installed via apt or brew (alternative):
    # bats --recursive --timing tests/

Полный рабочий процесс: ShellCheck + Bats

Теперь объединим всё в один готовый к использованию рабочий процесс. Здесь применены следующие рекомендации:

  • Два отдельных задания (lint и test) выполняются параллельно, обеспечивая более быструю обратную связь.
  • В задании test указано needs: lint, поэтому тесты запускаются только после успешного анализа — это предотвращает расход времени исполнителя на заведомо сломанный код.
  • Зафиксированные версии действий (@v4) предотвращают неожиданные сбои из-за обновлений со стороны поставщиков.
  • Блок permissions: ограничивает токен рабочего процесса минимально необходимыми разрешениями.
# .github/workflows/ci.yml
name: Shell CI

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

jobs:
  lint:
    name: ShellCheck
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run ShellCheck
        run: |
          mapfile -t scripts < <(find . -name '*.sh' -not -path './.git/*')
          [[ ${#scripts[@]} -gt 0 ]] && shellcheck --severity=warning -x "${scripts[@]}"

  test:
    name: Bats Tests
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - name: Run tests
        run: ./test/bats/bin/bats --recursive --timing tests/

Кэширование зависимостей для ускорения запусков

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

Основные правила эффективного кэширования:

  • Используйте ключ кэша, включающий OS, название инструмента и хеш файла блокировки, чтобы кэш автоматически сбрасывался при изменении зависимостей.
  • Запасной вариант restore-keys позволяет рабочему процессу использовать устаревший кэш вместо повторного запуска с нуля, если кэш не найден.
  • Для подмодулей Git кэширование редко необходимо, поскольку получение подмодулей выполняется быстро. Кэш особенно полезен для npm, pip или установки компилируемых инструментов.
# .github/workflows/ci.yml  — cache step example
- name: Cache Bats npm helpers
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-bats-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-bats-

- name: Install helpers
  run: npm ci  # uses cache when available

Защита ветвей: обязательные успешные проверки

Рабочий процесс CI, который не блокирует слияние, в лучшем случае носит рекомендательный характер. Правила защиты ветвей GitHub превращают проверки в обязательные условия.

Чтобы настроить их, откройте Settings → Branches → Add rule для main, а затем включите:

  • Require status checks to pass before merging — выберите проверки ShellCheck и Bats Tests по названию.
  • Require branches to be up to date before merging — это не позволяет включить изменения из запроса, прошедшего проверки на устаревшей основе, если они содержат сломанный код.
  • Do not allow bypassing the above settings — правила применяются даже к администраторам репозитория.

После настройки этих правил единственный путь к слиянию — запрос на включение изменений со всеми успешно выполненными заданиями CI. Именно такую защитную сеть Вам и нужно получить.

Локальная отладка неуспешных шагов CI

Если запуск CI завершается с ошибкой, быстрее всего сначала воспроизвести её локально, а уже потом отправлять новый коммит. Два метода:

  • Выполните точные команды из неуспешного шага в терминале — CI запускает обычную оболочку, поэтому команды можно воспроизвести копированием и вставкой.
  • Используйте act — инструмент, который запускает рабочие процессы GitHub Actions локально внутри Docker, обеспечивая максимально близкое соответствие окружению предоставленного исполнителя.

Распространённый источник ошибок, возникающих только в CI, — несоответствие версий инструментов на Вашем Mac (например, BSD find в macOS и GNU find в Ubuntu). Всегда тестируйте с флагами --posix или используйте act, чтобы локально запускать образ Ubuntu.

#!/usr/bin/env bash
# run_ci_locally.sh  — mimic the CI lint step on your machine
set -euo pipefail

echo '=== ShellCheck ==='
mapfile -t scripts < <(find . -name '*.sh' -not -path './.git/*')
if [[ ${#scripts[@]} -eq 0 ]]; then
  echo 'No .sh files found.'
else
  shellcheck --severity=warning -x "${scripts[@]}"
  echo "Linted ${#scripts[@]} file(s) — OK"
fi

echo '=== Bats ==='
./test/bats/bin/bats --recursive --timing tests/

Проверка знаний: понятия конвейера CI

Проверьте, насколько хорошо Вы понимаете подключение ShellCheck и Bats к GitHub Actions.

Итоги: CI для оболочки с ShellCheck и Bats

В этом уроке Вы создали полный конвейер CI для проектов на Bash с использованием GitHub Actions. Были рассмотрены следующие темы:

  • Основы GitHub Actions — YAML рабочих процессов хранится в .github/workflows/, запуск происходит по событиям push и pull_request, а задания выполняются на исполнителях ubuntu-latest.
  • ShellCheck — предустановлен на исполнителях Ubuntu; используйте find для поиска скриптов и --severity=warning -x для практической проверки качества.
  • Bats через подмодуль — зафиксируйте bats-core и вспомогательные средства как подмодули Git; восстанавливайте их в CI с помощью submodules: recursive в действии получения кода.
  • Порядок заданий — используйте needs:, чтобы тесты запускались только после успешного анализа, обеспечивая быструю обратную связь и избегая напрасного расхода вычислительных ресурсов.
  • Защита ветвей — включите обязательные проверки состояния в настройках GitHub, чтобы ни один PR не попал в ветку без успешно выполненного CI.
  • Локальное воспроизведение — копируйте команды CI непосредственно в терминал или используйте act, чтобы отлаживать ошибки без дополнительных коммитов.

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

Часто задаваемые вопросы

Урок «Запуск тестов Shell в CI-конвейерах» бесплатный?

Да — полный текст урока «Запуск тестов Shell в CI-конвейерах» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.

Чему я научусь в уроке «Запуск тестов Shell в CI-конвейерах»?

Подключайте ShellCheck и Bats к GitHub Actions, чтобы каждая правка Shell проходила проверку только при успешных результатах Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать DevOps Bootcamp?

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

Сколько времени занимает урок «Запуск тестов Shell в CI-конвейерах»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке DevOps Bootcamp?

Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Модульное тестирование функций с Bats-core
  2. Имитация команд и заглушки внешних инструментов
  3. Тестовые данные, временные окружения и покрытие
  4. Запуск тестов Shell в CI-конвейерах
← Назад к DevOps Bootcamp