Введение: Почему базового 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