GitHub

CI для Qt-проекта: автоматический запуск тестов в GitHub Actions

Введение

Написанные тесты приносят пользу только если они регулярно выполняются — в идеале автоматически при каждом push и pull request. GitHub Actions — широко используемый сервис непрерывной интеграции, тесно встроенный в GitHub, позволяющий описать пайплайн сборки и тестирования декларативно в YAML-файле. В статье настроим CI-пайплайн, который собирает Qt-проект и запускает тесты на нескольких платформах.

Концепция

GitHub Actions выполняет workflow — последовательность шагов (steps) внутри job, запускаемых на виртуальной машине (runner) с указанной ОС. Для Qt-проектов типичный пайплайн включает: установку Qt (через готовый action jurplel/install-qt-action, который скачивает и кеширует нужную версию Qt без необходимости собирать его из исходников), конфигурацию и сборку через CMake, и запуск тестов через ctest (стандартный тестовый раннер CMake, который автоматически обнаруживает тесты, зарегистрированные через add_test()).

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

Пример кода

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build-and-test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        build_type: [Debug, Release]

    runs-on: ${{ matrix.os }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install Qt
        uses: jurplel/install-qt-action@v4
        with:
          version: '6.7.0'
          cache: true

      - name: Configure CMake
        run: |
          cmake -B build -S . -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}

      - name: Build
        run: cmake --build build --config ${{ matrix.build_type }}

      - name: Run tests
        working-directory: build
        run: ctest --output-on-failure -C ${{ matrix.build_type }}

      - name: Upload test results on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ matrix.os }}-${{ matrix.build_type }}
          path: build/Testing/Temporary/LastTest.log

Пояснения к коду

Секция strategy.matrix запускает пайплайн в шести независимых комбинациях (три ОС, две конфигурации сборки), каждая на отдельном runner — GitHub Actions выполняет их параллельно, что значительно сокращает общее время по сравнению с последовательным запуском. Шаг jurplel/install-qt-action устанавливает и кеширует указанную версию Qt отдельно для каждой ОС, избавляя от необходимости вручную писать логику установки Qt под разные платформы.

ctest --output-on-failure запускает все тесты, зарегистрированные через add_test() в CMakeLists.txt, и выводит подробный лог только для тех тестов, которые провалились — это даёт компактный вывод при успехе и полную диагностику при сбое. Последний шаг загружает лог тестов как артефакт CI только в случае сбоя (if: failure()), что облегчает последующий разбор причин падения без необходимости воспроизводить проблему локально.

Подводные камни

  • Headless-окружение CI без графического дисплея для тестов, требующих QApplication (виджеты, QML) — на Linux runner нужно запускать тесты через xvfb-run (виртуальный X-сервер) или использовать платформенный плагин offscreen (QT_QPA_PLATFORM=offscreen), иначе тесты с GUI-зависимостями завершатся с ошибкой инициализации платформы.
  • Долгая установка Qt без кеширования. Без cache: true в install-qt-action каждый запуск CI заново скачивает и распаковывает несколько гигабайт Qt SDK, что заметно увеличивает время каждого прогона — кеширование между запусками экономит значительное время.
  • Несовпадение версии Qt в CI и у разработчиков локально. Если в install-qt-action зафиксирована конкретная версия Qt, а локально разработчики используют другую, специфичные для версии особенности поведения могут проявляться по-разному — стоит синхронизировать версии или явно документировать минимально поддерживаемую.
  • Падение одного теста матрицы не должно автоматически останавливать остальные, если не настроено иначе (fail-fast: false в стратегии) — по умолчанию GitHub Actions может отменить оставшиеся задания матрицы при первом сбое, что скрывает информацию о состоянии на других платформах.