CI/CD для C# проектов: GitHub Actions на продвинутом уровне

Онлайн-тренажер Python для начинающих

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

Начать курс

Введение: Почему базового CI/CD недостаточно?

Начальный уровень CI/CD для C# проектов обычно ограничивается простым запуском dotnet build и dotnet test. Однако, когда проект разрастается до микросервисной архитектуры, монорепозитория или требует поддержки нескольких версий .NET, стандартного подхода становится недостаточно. Время сборки увеличивается, тесты дублируются, а процесс деплоя NuGet-пакетов превращается в рутину.

В этой статье мы разберем продвинутые техники настройки GitHub Actions для C# (.NET) проектов. Вы узнаете, как ускорить пайплайны с помощью кэширования, организовать параллельные тесты, реализовать матричные сборки и настроить автоматическую публикацию в NuGet. Все примеры кода готовы к использованию в ваших проектах.

1. Матричная сборка и тестирование нескольких версий .NET

Матрица (matrix) — это мощный инструмент GitHub Actions, позволяющий запускать один и тот же workflow с разными параметрами. Для C# это незаменимо, когда нужно проверить совместимость кода с .NET 6, .NET 8 и .NET 9.

Пример матричной конфигурации

Создайте файл .github/workflows/ci-matrix.yml:

name: Matrix Build and Test

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

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        dotnet-version: ['6.0.x', '8.0.x', '9.0.x']
        configuration: ['Release', 'Debug']
      fail-fast: false  # Продолжить сборку, даже если одна версия упала

    steps:
    - uses: actions/checkout@v4
    
    - name: Setup .NET ${{ matrix.dotnet-version }}
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: ${{ matrix.dotnet-version }}
    
    - name: Restore dependencies
      run: dotnet restore
    
    - name: Build
      run: dotnet build --configuration ${{ matrix.configuration }} --no-restore
    
    - name: Run tests
      run: dotnet test --configuration ${{ matrix.configuration }} --no-build --verbosity normal

Ключевые моменты:

  • fail-fast: false — позволяет увидеть результаты по всем версиям, даже если одна из них упала.
  • Матрица может включать не только версии .NET, но и ОС (например, os: [ubuntu-latest, windows-latest]).
  • Для монорепозитория можно добавить параметр project-path и тестировать разные модули.

2. Кэширование зависимостей: ускоряем сборку в 3 раза

Каждый запуск GitHub Actions начинается с чистой среды. Без кэширования dotnet restore будет каждый раз скачивать все NuGet-пакеты заново. Для крупных проектов это может занимать 2-5 минут. Решение — кэширование папки ~/.nuget/packages.

Продвинутая настройка кэша

Используйте action actions/cache с правильным ключом, который учитывает файл блокировки:

steps:
  - uses: actions/checkout@v4

  - name: Cache NuGet packages
    uses: actions/cache@v4
    with:
      path: ~/.nuget/packages
      # Ключ: хеш файла блокировки + имя ОС
      key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
      # Fallback на предыдущий ключ, если файл блокировки не изменился
      restore-keys: |
        ${{ runner.os }}-nuget-

  - name: Setup .NET
    uses: actions/setup-dotnet@v4
    with:
      dotnet-version: '8.0.x'

  - name: Restore dependencies
    run: dotnet restore

Совет: Если в проекте несколько .csproj файлов, используйте hashFiles('**/*.csproj', '**/packages.lock.json') для более точного хеша.

3. Параллельный запуск тестов и генерация отчетов

Когда в проекте сотни тестов, последовательный запуск может занимать десятки минут. GitHub Actions поддерживает распараллеливание на уровне джобов. Но для одного проекта можно использовать встроенные возможности dotnet test с флагом --parallel.

Разделение тестов по группам

Создайте workflow, который разбивает тесты на несколько джобов с помощью динамической матрицы:

jobs:
  # Сначала собираем проект один раз
  build:
    runs-on: ubuntu-latest
    outputs:
      test-count: ${{ steps.get-test-count.outputs.count }}
    steps:
      - uses: actions/checkout@v4
      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'
      - name: Build
        run: dotnet build --configuration Release
      - name: Get test count
        id: get-test-count
        run: echo "count=4" >> $GITHUB_OUTPUT  # Замените на реальное количество групп

  # Параллельный запуск тестов
  test:
    needs: build
    runs-on: ubuntu-latest
    strategy:
      matrix:
        group: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'
      - name: Run tests (Group ${{ matrix.group }})
        run: |
          dotnet test --configuration Release --filter "Category=Group${{ matrix.group }}" --logger "trx;LogFileName=test-results.trx"
      
      - name: Upload test results
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ matrix.group }}
          path: '**/TestResults/*.trx'
          retention-days: 7

Для более гибкого подхода используйте dotnet test --filter "FullyQualifiedName~Tests.Unit" или разбивайте тесты по папкам.

4. Автоматический деплой NuGet-пакетов

Публикация библиотек в NuGet вручную — частая причина ошибок (забыли обновить версию, не подписали пакет). Автоматизируем этот процесс с помощью GitHub Actions.

Workflow для публикации при создании тега

Создайте файл .github/workflows/publish-nuget.yml:

name: Publish to Nu                

Блоги

Рекомендаций по книгам