Реализация Continuous Load Testing в CI/CD пайплайне

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация Continuous Load Testing в CI/CD пайплайне
Средний
~3-5 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1358
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1250
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    956
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    947

Непрерывное нагрузочное тестирование в CI/CD

При каждом деплое в продакшен риск внести регрессию производительности — высок. Один неоптимальный SQL-запрос или забытая N+1 проблема могут увеличить время ответа на 50%, и пользователь уйдёт к конкурентам. Мы интегрируем автоматические нагрузочные тесты прямо в ваш CI/CD пайплайн, чтобы ловить такие регрессии до попадания в master. Результат — вы уверены, что каждый коммит не ломает SLA по latency и throughput.

Настроим smoke-тесты с пороговыми значениями, которые превращают пайплайн в gatekeeper: если p95 latency превысил 500ms или процент ошибок выше 1% — пайплайн падает, и разработчик получает уведомление. Это не замена полноценному нагрузочному тестированию, а быстрый предохранитель.

Проблемы, которые решаем

  • Регрессия производительности после деплоя — даже микроизменение в коде может замедлить критический endpoint. Без автоматических тестов вы узнаете о проблеме только после жалоб пользователей или алертов мониторинга. Мы настраиваем baseline-сравнение: каждый тест запускается против стабильной ветки, и при отклонении >20% пайплайн блокируется.
  • Неэффективные запросы и узкие места — наши скрипты эмулируют типичные пользовательские сценарии (просмотр списка, создание поста, поиск). Если запрос начал выполняться дольше — мы видим это на графиках метрик.
  • Отсутствие культуры performance-first — разработчики часто не думают о производительности на этапе code review. Continuous Load Testing делает performance видимым: каждый PR сопровождается комментарием с результатами тестов (p95, error rate).

Инструменты и их место в CI

k6 — лучший выбор для CI: JS-скрипты, встроенная статистика, threshold-based pass/fail, нативная интеграция с GitHub Actions и GitLab CI. Подробнее в k6 documentation.

Artillery — YAML-конфигурация, удобен для описания сценариев без кода.

Gatling — Scala/Java, детальные HTML-отчёты, удобен для Java-команд.

Базовый k6 скрипт

// tests/performance/api-smoke.js
import http from 'k6/http'
import { check, sleep } from 'k6'
import { Rate, Trend } from 'k6/metrics'

// Кастомные метрики
const errorRate = new Rate('errors')
const postCreateDuration = new Trend('post_create_duration')

export const options = {
  // Профиль нагрузки для CI: быстро, не разрушительно
  stages: [
    { duration: '30s', target: 10 },  // разогрев
    { duration: '1m',  target: 10 },  // устойчивая нагрузка
    { duration: '10s', target: 0 },   // остывание
  ],

  // Пайплайн сломается если порог не достигнут
  thresholds: {
    http_req_duration: [
      'p(95)<500',   // p95 < 500мс
      'p(99)<1000',  // p99 < 1000мс
    ],
    errors: ['rate<0.01'],            // ошибок < 1%
    http_req_failed: ['rate<0.01'],   // HTTP ошибок < 1%
    post_create_duration: ['p(95)<800'],
  }
}

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000'
const AUTH_TOKEN = __ENV.AUTH_TOKEN

export function setup() {
  // Один раз: получить токен или подготовить данные
  const res = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
    email: '[email protected]',
    password: 'testpassword'
  }), { headers: { 'Content-Type': 'application/json' } })

  return { token: res.json('token') }
}

export default function(data) {
  const headers = {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${data.token || AUTH_TOKEN}`
  }

  // Сценарий 1: список постов (70% трафика)
  const postsList = http.get(`${BASE_URL}/api/posts?limit=20`, { headers })
  check(postsList, {
    'posts list: status 200': (r) => r.status === 200,
    'posts list: has items': (r) => r.json('data').length > 0
  })
  errorRate.add(postsList.status !== 200)

  sleep(Math.random() * 0.5)  // случайная пауза 0-500мс

  // Сценарий 2: создание поста (20% трафика)
  if (Math.random() < 0.2) {
    const start = Date.now()
    const createPost = http.post(`${BASE_URL}/api/posts`, JSON.stringify({
      title: `Test post ${Date.now()}`,
      content: 'Load test content'
    }), { headers })

    postCreateDuration.add(Date.now() - start)
    check(createPost, {
      'create post: status 201': (r) => r.status === 201,
    })
    errorRate.add(createPost.status !== 201)
  }

  sleep(0.3)
}

Как настроить пороги производительности в k6?

Пороги (thresholds) — ключевой механизм для автоматического fail пайплайна. В опциях скрипта мы задаём допустимые границы: например, http_req_duration: ['p(95)<500', 'p(99)<1000']. Это означает, что 95% запросов должны выполняться быстрее 500 мс, а 99% — быстрее 1 секунды. Если порог нарушен — тест завершается с ошибкой, и CI останавливает деплой.

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

GitHub Actions интеграция

# .github/workflows/performance.yml
name: Performance Tests

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

jobs:
  performance:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_DB: testdb
          POSTGRES_PASSWORD: testpass
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s

    steps:
      - uses: actions/checkout@v4

      - name: Start application
        run: |
          docker compose -f docker-compose.test.yml up -d api
          npx wait-on http://localhost:3000/health --timeout 60000

      - name: Run k6 smoke test
        uses: grafana/[email protected]
        with:
          filename: tests/performance/api-smoke.js
          flags: --out json=results.json
        env:
          BASE_URL: http://localhost:3000
          K6_PROMETHEUS_RW_SERVER_URL: ${{ secrets.PROMETHEUS_URL }}

      - name: Parse results
        if: always()
        run: |
          # Показать summary в PR комментарии
          jq -r '.metrics | {
            p95: .http_req_duration["p(95)"],
            p99: .http_req_duration["p(99)"],
            errors: .http_req_failed.rate
          }' results.json

      - name: Comment PR with results
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs')
            const results = JSON.parse(fs.readFileSync('results.json'))
            const p95 = results.metrics.http_req_duration['p(95)'].toFixed(0)
            const errorRate = (results.metrics.http_req_failed.rate * 100).toFixed(2)

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `## Performance Test Results\n\n| Metric | Value | Threshold |\n|--------|-------|-----------|\n| p95 latency | ${p95}ms | <500ms |\n| Error rate | ${errorRate}% | <1% |`
            })

Baseline сравнение между деплоями

#!/bin/bash
# scripts/compare-performance.sh

CURRENT_BRANCH=$(git branch --show-current)
BASELINE_BRANCH="main"

# Тест текущего кода
k6 run --out json=current.json tests/performance/api-smoke.js

# Переключиться на baseline
git stash
git checkout $BASELINE_BRANCH
docker compose up -d --build api
sleep 10
k6 run --out json=baseline.json tests/performance/api-smoke.js

# Сравнение
node - <<'EOF'
const current = require('./current.json')
const baseline = require('./baseline.json')

const metrics = ['http_req_duration']
for (const m of metrics) {
  const cp95 = current.metrics[m]['p(95)']
  const bp95 = baseline.metrics[m]['p(95)']
  const delta = ((cp95 - bp95) / bp95 * 100).toFixed(1)

  if (cp95 > bp95 * 1.2) {  // регрессия > 20%
    console.error(`REGRESSION: ${m} p95 degraded by ${delta}%`)
    process.exit(1)
  }
  console.log(`${m} p95: ${cp95}ms vs ${bp95}ms baseline (${delta}%)`)
}
EOF

# Вернуться на текущую ветку
git checkout $CURRENT_BRANCH
git stash pop

Artillery для описания сценариев

# tests/performance/user-journey.yml
config:
  target: "{{ $processEnvironment.BASE_URL }}"
  phases:
    - duration: 60
      arrivalRate: 5
      rampTo: 20
      name: "Ramp up"
    - duration: 120
      arrivalRate: 20
      name: "Sustained load"

  ensure:
    thresholds:
      - http.response_time.p95: 500
      - http.request_rate: 15

scenarios:
  - name: "Browse and purchase"
    weight: 70
    flow:
      - get:
          url: "/api/products"
          expect:
            - statusCode: 200
      - post:
          url: "/api/cart"
          json:
            productId: "{{ $randomInt(1, 100) }}"
            quantity: 1

  - name: "Search only"
    weight: 30
    flow:
      - get:
          url: "/api/search?q={{ $randomString(5) }}"

Сравнение инструментов нагрузочного тестирования

Характеристика k6 Artillery Gatling
Язык скриптов JavaScript YAML Scala/Java
Нативная CI-интеграция Да (GitHub Actions, GitLab CI) Да (через NPM) Да (Maven/Gradle)
Встроенные threshold Да Да (через ensure) Да
Генерация отчётов JSON, Prometheus, HTML JSON, HTML HTML (детальные)
Производительность Высокая (Go) Средняя (Node.js) Высокая (JVM)
Лицензия Open Source (AGPL) Open Source (MPL) Open Source (ALv2)

Почему стоит использовать k6 для CI?

k6 имеет встроенную поддержку CI/CD: он работает как CLI-утилита, не требует графического интерфейса, а результаты можно выводить в JSON для дальнейшей обработки. В отличие от Gatling (требует Scala/Java и генерации HTML-отчётов) или Artillery (YAML-конфиги, но меньше метрик), k6 предоставляет гибкие метрики и простую интеграцию с GitHub Actions через готовые action. Для команд, уже использующих JavaScript, порог входа минимален.

Процесс работы

  1. Аналитика — определяем критические эндпоинты и пользовательские сценарии.
  2. Проектирование — разрабатываем нагрузочные сценарии, настраиваем профили нагрузки (ramp-up, sustained).
  3. Реализация — пишем скрипты (k6, Artillery или Gatling), интегрируем их в CI.
  4. Тестирование — запускаем smoke-тесты на staging, корректируем thresholds.
  5. Деплой — включаем тесты в пайплайн, настраиваем автоматические комментарии в PR.

Сроки и что входит

Настройка базового набора k6 smoke-тестов с порогами и интеграцией в CI занимает от 1 до 2 рабочих дней. Если требуется сравнение с baseline и кастомные метрики — расширяем до 3-5 дней. В состав работ входит:

  • Скрипты нагрузочных тестов (2-3 сценария)
  • Конфигурация thresholds
  • Интеграция с GitHub Actions или GitLab CI (YAML pipeline)
  • PR-комментарий с результатами тестов
  • Документация по запуску и поддержке
  • Консультация команды по интерпретации результатов

Мы гарантируем, что тесты не будут мешать основному процессу разработки: они запускаются параллельно и занимают не более 5 минут.

Почему выбирают нас

  • Более 5 лет опыта в нагрузочном тестировании и CI/CD
  • 50+ успешных проектов — от стартапов до enterprise
  • Используем только проверенные инструменты (k6, Grafana, Prometheus)
  • Предоставляем гарантию на корректную работу тестов в течение месяца после внедрения

Свяжитесь с нами, чтобы обсудить ваш проект: получите консультацию по интеграции Continuous Load Testing в ваш пайплайн.

Почему юнит-тесты важны, но не панацея?

Баг, найденный юнит-тестом, стоит минуты исправления. Тот же баг в продакшене — часы инцидента, компенсации и потеря доверия. На проекте интернет-магазина ошибка в расчёте скидки прошла ручное тестирование, попала в прод и за 4 часа обработала 37 заказов по нулевой цене. Автотест на граничные случаи расчёта поймал бы её при первом же push. Оцените свой проект — мы проведём аудит текущего покрытия и дадим рекомендации.

Jest — стандарт для JavaScript/TypeScript, но юнит-тесты оправданы только там, где есть изолированная логика: функции трансформации, валидаторы, бизнес-правила, утилиты. Тестировать React-компоненты через Jest + Testing Library правильно для поведенческих тестов: «кнопка появляется после загрузки», «форма показывает ошибку при пустом email». Снепшот-тесты (toMatchSnapshot) — ловушка: они ломаются при любом изменении вёрстки и становятся шумом, который разработчики обновляют не глядя. Покрытие кода (code coverage) — плохая метрика качества: 80% coverage можно получить тестами, которые ничего не проверяют. Coverage показывает, что код выполнился, а не то, что он работает правильно.

Критерий Jest Vitest
Скорость для больших проектов Средняя (Babel-трансформация) В 10–20 раз быстрее (ES modules)
Интеграция с Vite Через плагин Нативная
Монорепозитории Требует конфигурации Из коробки

Vitest как альтернатива Jest для Vite-проектов: в 10–20 раз быстрее за счёт нативных ES modules без трансформации через Babel. Для монорепозиториев с тысячами тестов разница в скорости ощутима. Подробнее о юнит-тестировании.

Как настроить E2E тесты, которые не будут flaky?

Playwright обошёл Cypress по ключевым параметрам: нативная поддержка multi-tab, multi-origin, iframe; параллельное выполнение на уровне тестов; WebKit, Firefox, Chromium из коробки; нет iframe для приложения — тесты работают в реальном браузере.

Playwright codegen записывает действия и генерирует тест — хорошая точка старта, но сгенерированный код нужно рефакторить. Локаторы по text content хрупки: getByRole('button', { name: 'Оформить заказ' }) — устойчивее, чем locator('.btn-primary').

Page Object Model — стандарт организации E2E тестов. Каждая страница — отдельный класс с методами вместо прямых локаторов. Когда кнопка переехала из хедера в сайдбар — меняем в одном месте, не ищем по всем тестам.

Как избежать flaky тестов? Типичная проблема — flaky tests. Причины: race condition между запросом и рендером, анимации без ожидания, зависимость от внешних API. Решение: `page.waitForResponse()` вместо `page.waitForTimeout()`, мокирование внешних API через `page.route()`.
// Плохо
await page.click('#submit');
await page.waitForTimeout(2000);
await expect(page.locator('.success')).toBeVisible();

// Хорошо
await page.click('#submit');
await page.waitForResponse(resp =>
  resp.url().includes('/api/orders') && resp.status() === 201
);
await expect(page.getByRole('alert', { name: /заказ создан/i })).toBeVisible();

Наши инженеры гарантируют стабильность тестов в CI. Документация Playwright — основной инструмент на проектах с миллионами пользователей.

Нагрузочное тестирование с k6

k6 — инструмент для нагрузочного тестирования с JavaScript API. Сценарии пишутся как код, версионируются в git, запускаются в CI. Три основных сценария:

  • Spike test — резкий рост нагрузки: 0 → 1000 пользователей за 30 секунд. Имитирует запуск рекламной кампании. Показывает способность системы реагировать на пики.
  • Soak test — стабильная нагрузка на 2–4 часа. Выявляет memory leaks, connection pool exhaustion, деградацию производительности.
  • Stress test — нагрузка выше расчётной (150–200% от ожидаемого пика). Показывает точку отказа и graceful degradation.

Пороговые значения:

thresholds: {
  http_req_duration: ['p95<500', 'p99<1000'],
  http_req_failed: ['rate<0.01'],
}

p95 < 500ms означает: 95% запросов отвечают быстрее полусекунды. Если порог не выполняется — k6 завершается с кодом ошибки, CI-пайплайн падает.

На одном проекте интернет-магазина мы выявили деградацию API на 4-й час теста: p95 вырос с 200ms до 2s из-за утечки соединений. После оптимизации клиент сэкономил около $15,000 в год на инцидентах и лишних ресурсах. Получите аналогичный аудит вашего проекта — закажите нагрузочное тестирование.

Как Core Web Vitals влияют на ранжирование?

Google использует Core Web Vitals в ранжировании. Lighthouse CLI в CI-пайплайне: при каждом деплое проверяем, что LCP < 2.5s, CLS < 0.1, INP < 200ms. Подробнее о веб-производительности. Реальные проблемы, которые Lighthouse находит:

  • Hero image без width/height атрибутов: CLS 0.35 при загрузке.
  • JavaScript-бандл 2.1MB синхронно блокирует парсинг: INP 450ms.
  • Шрифты без font-display: swap: невидимый текст до загрузки шрифта (FOIT).
  • Неоптимизированный hero image 4MB: LCP 8.2s.

Lighthouse CI (lhci) сохраняет историю метрик и отправляет комментарий к PR с деградацией. По данным Google, 53% пользователей покидают сайт при загрузке дольше 3 секунд — наши тесты предотвращают такие потери.

Пирамида тестирования в проекте

Уровень Инструмент Количество Скорость
Юнит Vitest/Jest Много (тысячи) <5 мин
Интеграция Vitest + supertest Среднее 5–15 мин
E2E Playwright Немного (happy path) 10–30 мин
Нагрузка k6 По расписанию 30–60 мин
Performance Lighthouse CI При каждом деплое 5 мин

Что входит в работу?

  • Аудит текущего покрытия и определение критических user flows.
  • Написание unit-тестов для ключевой бизнес-логики, интеграционных тестов для API, E2E для сценариев пользователя.
  • Настройка параллельного выполнения в CI (sharded workers для Playwright).
  • Нагрузочное тестирование с отчётом и рекомендациями.
  • Документация по тест-кейсам, обучение вашей команды работе с тестами.
  • Гарантийная поддержка 1 месяц после внедрения.

Процесс работы

  1. Аналитика — аудит текущего тестирования, выявление слабых мест, определение приоритетов.
  2. Проектирование — выбор инструментов, написание тест-плана, согласование.
  3. Реализация — написание тестов, интеграция в CI.
  4. Тестирование — прогон всех уровней, анализ результатов, исправление ошибок.
  5. Деплой — запуск в прод, мониторинг метрик, обучение команды.

Сроки

Настройка полного тест-пайплайна (Jest + Playwright + k6 + Lighthouse CI) с нуля: 2–4 недели. Покрытие E2E-тестами существующего проекта (20–30 сценариев): 3–6 недель. Нагрузочное тестирование с отчётом и рекомендациями: 1–2 недели. Стоимость рассчитывается индивидуально после аудита.

Готовы обсудить ваш проект? Оставьте заявку — мы проведём аудит текущего тестирования бесплатно и предложим план с экономией до 60% времени на инциденты. Получите консультацию по тестированию веб-приложений — напишите нам.