Matrix testing — одна из самых мощных техник для обеспечения работы вашего приложения в различных окружениях, конфигурациях и платформах. В современных CI/CD pipelines matrix testing позволяет автоматически запускать один и тот же набор тестов в разных комбинациях переменных. Это комплексное руководство проведет вас через внедрение стратегий matrix testing, которые масштабируются вместе с вашим рабочим процессом разработки.

Что такое Matrix Testing?

Matrix testing, также известное как комбинаторное тестирование, — это техника, при которой вы запускаете тесты по нескольким измерениям конфигурационных переменных одновременно. Вместо того чтобы тестировать каждую конфигурацию отдельно, вы создаете матрицу всех возможных комбинаций и выполняете тесты для каждой перестановки.

Например, если вам нужно протестировать ваше приложение на:

  • 3 операционных системах (Linux, macOS, Windows)
  • 3 версиях языка программирования (Node.js 16, 18, 20)
  • 2 версиях базы данных (PostgreSQL 13, 14)

Традиционное тестирование потребовало бы 18 отдельных ручных конфигураций. Matrix testing автоматизирует весь этот процесс, запуская все комбинации параллельно.

Предварительные требования

Перед внедрением matrix testing убедитесь, что у вас есть:

Необходимые инструменты:

  • CI/CD платформа (GitHub Actions, GitLab CI, Jenkins или CircleCI)
  • Система контроля версий (Git)
  • Docker (опционально, но рекомендуется для воспроизводимых окружений)
  • Базовые знания YAML для конфигурации CI

Технические знания:

  • Понимание концепций CI/CD pipeline
  • Знакомство с выбранной CI платформой
  • Базовые навыки скриптинга (Bash, Python или JavaScript)

Настройка окружения:

  • Доступ к вашему репозиторию с включенным CI/CD
  • Соответствующие разрешения для изменения конфигураций pipeline
  • Уже реализованный набор тестов

Шаг 1: Определите измерения вашей тестовой матрицы

Начните с выявления переменных, которые нужно тестировать с различными значениями. Общие измерения включают:

Инфраструктурные переменные:

  • Операционные системы (ubuntu-latest, macos-latest, windows-latest)
  • Архитектура (x86, ARM64)
  • Облачные провайдеры (AWS, Azure, GCP)

Переменные приложения:

  • Версии языка программирования
  • Версии фреймворков
  • Версии зависимостей
  • Системы баз данных и версии

Конфигурационные переменные:

  • Типы окружений (development, staging, production)
  • Feature flags
  • Endpoints интеграции

Создайте документ с перечислением критических измерений:

# matrix-config.yaml
dimensions:
  os: [ubuntu-22.04, ubuntu-20.04, macos-13, windows-2022]
  node_version: [16, 18, 20]
  database: [postgres:13, postgres:14, postgres:15]

Контрольная точка: Проверьте ваши измерения с командой. Приоритизируйте комбинации, которые представляют реальные пользовательские окружения. Избегайте тестирования комбинаций, которые не существуют в production.

Шаг 2: Внедрите Matrix Testing в GitHub Actions

GitHub Actions предоставляет встроенную поддержку стратегии матрицы. Вот как это реализовать:

# .github/workflows/matrix-tests.yml
name: Matrix Testing

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

jobs:
  test:
    name: Test on ${{ matrix.os }} with Node ${{ matrix.node }}
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node: [16, 18, 20]
        include:
          # Добавьте специфичные комбинации с дополнительной конфигурацией
          - os: ubuntu-latest
            node: 20
            experimental: true
        exclude:
          # Пропустите проблемные комбинации
          - os: macos-latest
            node: 16

      fail-fast: false  # Продолжайте тестирование других комбинаций, даже если одна упала
      max-parallel: 5   # Ограничьте одновременные задачи

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          flags: ${{ matrix.os }}-node-${{ matrix.node }}

Ожидаемый результат: Эта конфигурация создает 8 задач (3 OS × 3 версии Node - 1 исключенная комбинация). Каждая задача выполняется независимо, и вы увидите результаты для каждой комбинации в UI GitHub Actions.

Шаг 3: Реализуйте продвинутую матрицу с сервисами баз данных

Приложения реального мира часто требуют тестирования с различными базами данных. Вот как добавить сервисные контейнеры в вашу матрицу:

name: Matrix with Database Testing

jobs:
  integration-test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node: [18, 20]
        database:
          - type: postgres
            version: '13'
            port: 5432
          - type: postgres
            version: '14'
            port: 5432
          - type: mysql
            version: '8.0'
            port: 3306

    services:
      database:
        image: ${{ matrix.database.type }}:${{ matrix.database.version }}
        env:
          POSTGRES_PASSWORD: testpass
          MYSQL_ROOT_PASSWORD: testpass
        ports:
          - ${{ matrix.database.port }}:${{ matrix.database.port }}
        options: >-
          --health-cmd="pg_isready"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=5

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}

      - name: Configure database connection
        run: |
          echo "DATABASE_TYPE=${{ matrix.database.type }}" >> $GITHUB_ENV
          echo "DATABASE_PORT=${{ matrix.database.port }}" >> $GITHUB_ENV

      - name: Run integration tests
        run: npm run test:integration
        env:
          DATABASE_URL: ${{ matrix.database.type }}://user:testpass@localhost:${{ matrix.database.port }}/testdb

Контрольная точка: Запустите этот workflow и проверьте, что каждый сервис базы данных стартует корректно. Проверьте логи, чтобы убедиться, что соединения с базой данных установлены до запуска тестов.

Шаг 4: Оптимизируйте выполнение матрицы с динамическими матрицами

Для больших матриц вы можете генерировать комбинации динамически для снижения избыточности:

jobs:
  prepare-matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}

    steps:
      - uses: actions/checkout@v4

      - name: Generate dynamic matrix
        id: set-matrix
        run: |
          # Генерируйте матрицу на основе измененных файлов или условий
          if [[ "${{ github.event_name }}" == "pull_request" ]]; then
            # Ограниченная матрица для PR
            echo 'matrix={"os":["ubuntu-latest"],"node":[20]}' >> $GITHUB_OUTPUT
          else
            # Полная матрица для ветки main
            echo 'matrix={"os":["ubuntu-latest","macos-latest","windows-latest"],"node":[16,18,20]}' >> $GITHUB_OUTPUT
          fi

  test:
    needs: prepare-matrix
    runs-on: ${{ matrix.os }}
    strategy:
      matrix: ${{ fromJson(needs.prepare-matrix.outputs.matrix) }}

    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: echo "Testing on ${{ matrix.os }} with Node ${{ matrix.node }}"

Ожидаемый результат: Pull requests будут запускать только 1 задачу, в то время как pushes в main будут запускать полную матрицу из 9 задач. Это значительно снижает затраты и время CI для рутинной разработки.

Шаг 5: Внедрите Matrix Testing в GitLab CI

GitLab CI использует параллельные задачи для matrix testing:

# .gitlab-ci.yml
.test_template:
  stage: test
  script:
    - npm ci
    - npm test

test:
  extends: .test_template
  image: node:${NODE_VERSION}
  parallel:
    matrix:
      - NODE_VERSION: ['16', '18', '20']
        OS: ['ubuntu', 'alpine']
        DATABASE:
          - TYPE: 'postgres'
            VERSION: '13'
          - TYPE: 'postgres'
            VERSION: '14'

  services:
    - name: ${DATABASE_TYPE}:${DATABASE_VERSION}
      alias: database

  variables:
    DATABASE_URL: "${DATABASE_TYPE}://user:pass@database:5432/testdb"

  before_script:
    - echo "Testing Node ${NODE_VERSION} on ${OS} with ${DATABASE_TYPE} ${DATABASE_VERSION}"

Контрольная точка: GitLab создаст 12 задач (3 версии Node × 2 OS × 2 версии базы данных). Проверьте граф pipeline для визуализации всех комбинаций.

Шаг 6: Добавьте Matrix Testing для Jenkins

Jenkins требует более программного подхода с использованием Groovy:

// Jenkinsfile
pipeline {
    agent none

    stages {
        stage('Matrix Build') {
            matrix {
                axes {
                    axis {
                        name 'OS'
                        values 'linux', 'windows', 'mac'
                    }
                    axis {
                        name 'NODE_VERSION'
                        values '16', '18', '20'
                    }
                    axis {
                        name 'DATABASE'
                        values 'postgres:13', 'postgres:14', 'mysql:8.0'
                    }
                }

                excludes {
                    exclude {
                        axis {
                            name 'OS'
                            values 'mac'
                        }
                        axis {
                            name 'NODE_VERSION'
                            values '16'
                        }
                    }
                }

                stages {
                    stage('Test') {
                        agent {
                            docker {
                                image "node:${NODE_VERSION}"
                                label "${OS}"
                            }
                        }

                        steps {
                            sh 'npm ci'
                            sh 'npm test'
                        }
                    }
                }
            }
        }
    }

    post {
        always {
            publishHTML([
                reportDir: 'coverage',
                reportFiles: 'index.html',
                reportName: "Coverage Report - ${OS}-${NODE_VERSION}-${DATABASE}"
            ])
        }
    }
}

Ожидаемый результат: Jenkins создает визуальную матрицу со всеми комбинациями, позволяя вам углубиться в конкретные результаты тестов для каждой конфигурации.

Шаг 7: Внедрите умное Matrix Testing с кэшированием

Оптимизируйте время выполнения матрицы, используя эффективные стратегии кэширования:

name: Optimized Matrix Testing

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

    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        node: [18, 20]

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'

      - name: Cache node modules
        uses: actions/cache@v3
        with:
          path: |
            ~/.npm
            node_modules
          key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-${{ matrix.node }}-
            ${{ runner.os }}-node-

      - name: Install dependencies
        run: npm ci

      - name: Cache build artifacts
        uses: actions/cache@v3
        with:
          path: dist
          key: ${{ runner.os }}-build-${{ matrix.node }}-${{ github.sha }}

      - name: Run tests
        run: npm test

Контрольная точка: Мониторьте время выполнения вашего workflow. Правильно настроенное кэширование должно сократить время выполнения на 30-50% при последующих запусках.

Лучшие практики для Matrix Testing

1. Приоритизируйте критические комбинации

Не все комбинации одинаково важны. Используйте приоритизацию на основе данных:

strategy:
  matrix:
    include:
      # Критичные production конфигурации (высший приоритет)
      - os: ubuntu-latest
        node: 20
        priority: critical

      # Общие пользовательские конфигурации
      - os: ubuntu-latest
        node: 18
        priority: high

      # Граничные случаи и будущая совместимость
      - os: macos-latest
        node: 20
        priority: medium

2. Используйте Fail-Fast стратегически

Контролируйте, как сбои матрицы влияют на ваш pipeline:

strategy:
  fail-fast: false  # Тестируйте все комбинации, даже если некоторые упали
  matrix:
    # ... конфигурация матрицы

Установите fail-fast: true когда:

  • Работаете с ограниченными CI ресурсами
  • Тестируете критические функции, где один сбой указывает на системные проблемы
  • Отлаживаете конкретные комбинации

Установите fail-fast: false когда:

  • Исследуете платформо-специфичные проблемы
  • Генерируете комплексные отчеты о совместимости
  • Некоторые комбинации известны как нестабильные

3. Реализуйте сбор артефактов матрицы

Собирайте и организуйте результаты тестов из всех комбинаций матрицы:

steps:
  - name: Run tests
    run: npm test -- --coverage

  - name: Upload test results
    uses: actions/upload-artifact@v3
    if: always()
    with:
      name: test-results-${{ matrix.os }}-node-${{ matrix.node }}
      path: |
        coverage/
        test-results/
      retention-days: 30

  - name: Generate matrix report
    if: always()
    run: |
      echo "## Test Results: ${{ matrix.os }} - Node ${{ matrix.node }}" >> $GITHUB_STEP_SUMMARY
      echo "Status: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
      echo "$(cat test-results/summary.txt)" >> $GITHUB_STEP_SUMMARY

4. Мониторьте производительность матрицы

Отслеживайте метрики выполнения матрицы для оптимизации затрат и времени:

- name: Report matrix metrics
  run: |
    echo "Matrix combination: ${{ matrix.os }}-${{ matrix.node }}"
    echo "Start time: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
    npm test
    echo "End time: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
    echo "Status: ${{ job.status }}"

Используйте эти метрики для выявления:

  • Самых медленных комбинаций, нуждающихся в оптимизации
  • Часто падающих конфигураций
  • Избыточных или ненужных измерений матрицы

Распространенные ошибки и решения

Ошибка 1: Взрыв матрицы

Проблема: Слишком много измерений создают сотни комбинаций, перегружая CI ресурсы.

Решение: Используйте стратегические исключения и условные матрицы:

strategy:
  matrix:
    os: [ubuntu, macos, windows]
    node: [16, 18, 20]
    database: [postgres, mysql, mongodb]
    # Это создает 27 комбинаций!

  exclude:
    # Удалите маловероятные production комбинации
    - os: windows
      database: mongodb
    - os: macos
      node: 16
    # Теперь сокращено до 23 комбинаций

Ошибка 2: Конкуренция за ресурсы

Проблема: Все задачи матрицы стартуют одновременно, вызывая истощение ресурсов или достижение лимитов.

Решение: Внедрите контролируемый параллелизм:

strategy:
  max-parallel: 4  # Ограничьте одновременные задачи
  matrix:
    # ... конфигурация

Ошибка 3: Несогласованные тестовые окружения

Проблема: Тесты проходят в некоторых комбинациях матрицы, но падают в других из-за различий окружения.

Решение: Используйте Docker для согласованных окружений:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    container:
      image: node:${{ matrix.node }}
      options: --user root

    strategy:
      matrix:
        os: [ubuntu-latest]
        node: [16, 18, 20]

Инструменты и ресурсы

Рекомендуемые инструменты

ИнструментСлучай использованияПлюсыМинусы
GitHub ActionsMatrix testing общего назначенияВстроенная поддержка матрицы, хороший UI, бесплатно для публичных репозиториевОграничено GitHub
GitLab CIКорпоративные окруженияSelf-hosted опция, интегрировано с GitLabКривая обучения для синтаксиса матрицы
JenkinsLegacy системыВысоко кастомизируемый, экосистема плагиновСложная настройка, многословный синтаксис
CircleCIDocker-тяжелые workflowsОтличная Docker поддержка, параллелизмСтоимость может быть высокой для больших матриц

Важные ресурсы

Заключение

Matrix testing трансформирует ваш CI/CD pipeline из тестирования отдельных конфигураций в комплексную кросс-платформенную валидацию. Внедряя стратегии из этого руководства, вы можете:

  • Автоматически тестировать все критические комбинации платформ
  • Обнаруживать окружение-специфичные баги рано
  • Сокращать нагрузку ручного тестирования на 70-80%
  • Строить уверенность в кросс-платформенной совместимости

Ключевые выводы:

  1. Начинайте с минимальной матрицы и расширяйте на основе реальных сбоев
  2. Используйте динамические матрицы для оптимизации затрат и времени выполнения
  3. Приоритизируйте критические комбинации над исчерпывающим покрытием
  4. Мониторьте и постоянно улучшайте производительность матрицы

Следующие шаги:

  • Внедрите базовую матрицу для вашего наиболее критичного набора тестов
  • Мониторьте выполнение матрицы в течение 2-3 недель и собирайте метрики
  • Расширяйте измерения матрицы на основе production проблем
  • Рассмотрите внедрение параллельного выполнения тестов для еще более быстрых результатов

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