Modern QA2026k6 и Locust: современные инструменты нагрузочного тестирования
Join

Course05 Performance & Chaos Engineering

Cutting-edge · Chapter 05

k6 и Locust: современные инструменты нагрузочного тестирования

Updated Jul 2026

k6 (Grafana k6)

k6 — это ориентированный на разработчиков инструмент нагрузочного тестирования, написанный на Go с возможностью скриптинга на JavaScript. Он стал отраслевым стандартом для команд, практикующих тестирование производительности по принципу shift-left. Его ключевые преимущества — низкое потребление ресурсов, нативная интеграция с CI и модель скриптинга, с которой разработчикам действительно приятно работать.

Почему k6 выигрывает для CI/CD

  • Единый бинарный файл. Без JVM, без зависимостей. Скачайте и запустите.
  • Скриптинг на JavaScript. Знакомый синтаксис для большинства команд разработки.
  • Пороговые значения для прохождения/провала. Определяйте SLO прямо в тесте — при их нарушении код возврата ненулевой, и CI-пайплайн завершается с ошибкой.
  • Встроенный экспорт метрик. Стриминг в Grafana, Datadog, Prometheus или JSON.
  • Движок сценариев. Моделирование нескольких паттернов трафика в одном файле теста.

Полный пример k6: нагрузочный тест интернет-магазина

// k6-load-test.js -- Realistic e-commerce load test with multiple scenarios
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend } from 'k6/metrics';

// Custom metrics for business-specific SLOs
const errorRate = new Rate('errors');
const checkoutDuration = new Trend('checkout_duration');

// Traffic shaping: ramp up, sustain, spike, cool down
export const options = {
  scenarios: {
    // Steady-state browsing traffic (70% of users)
    browse: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 100 },   // ramp up
        { duration: '5m', target: 100 },   // steady state
        { duration: '1m', target: 300 },   // spike
        { duration: '5m', target: 100 },   // back to steady
        { duration: '2m', target: 0 },     // ramp down
      ],
      exec: 'browseProducts',
    },
    // API consumers hitting the search endpoint (30% of traffic)
    api_search: {
      executor: 'constant-arrival-rate',
      rate: 50,            // 50 requests per second
      timeUnit: '1s',
      duration: '15m',
      preAllocatedVUs: 20,
      maxVUs: 100,
      exec: 'searchAPI',
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1500'],  // SLO enforcement
    errors: ['rate<0.01'],                             // <1% error rate
    checkout_duration: ['p(95)<3000'],                 // checkout under 3s
  },
};

export function browseProducts() {
  group('Homepage', () => {
    const res = http.get('https://store.example.com/');
    check(res, {
      'homepage status 200': (r) => r.status === 200,
      'homepage loads under 2s': (r) => r.timings.duration < 2000,
    });
    errorRate.add(res.status >= 400);
  });

  sleep(Math.random() * 3 + 1); // realistic think time: 1-4 seconds

  group('Product Page', () => {
    const productId = Math.floor(Math.random() * 1000) + 1;
    const res = http.get(`https://store.example.com/products/${productId}`);
    check(res, {
      'product page status 200': (r) => r.status === 200,
      'has product title': (r) => r.body.includes('<h1'),
    });
    errorRate.add(res.status >= 400);
  });

  sleep(Math.random() * 5 + 2); // browsing think time: 2-7 seconds
}

export function searchAPI() {
  const queries = ['laptop', 'phone', 'headphones', 'keyboard', 'monitor'];
  const query = queries[Math.floor(Math.random() * queries.length)];

  const res = http.get(`https://api.store.example.com/search?q=${query}`, {
    headers: { 'Authorization': `Bearer ${__ENV.API_TOKEN}` },
  });

  check(res, {
    'search returns 200': (r) => r.status === 200,
    'search returns results': (r) => JSON.parse(r.body).results.length > 0,
    'search under 300ms': (r) => r.timings.duration < 300,
  });
  errorRate.add(res.status >= 400);
}

Запуск k6

# Local execution with default output
k6 run k6-load-test.js

# With environment variables for secrets
k6 run -e API_TOKEN=secret123 k6-load-test.js

# Output results to Grafana Cloud k6
k6 cloud k6-load-test.js

# Output to JSON for CI analysis
k6 run --out json=results.json k6-load-test.js

# Run with a specific scenario only
k6 run --scenario browse k6-load-test.js

Типы исполнителей k6

Понимание исполнителей необходимо для моделирования реалистичного трафика:

Исполнитель Управляет Лучше всего подходит для
shared-iterations Общее число итераций для всех VU Быстрые smoke-тесты
per-vu-iterations Итерации на каждый VU Гарантия выполнения каждым VU N раз
constant-vus Фиксированное число VU Тестирование в устойчивом состоянии
ramping-vus Число VU во времени Паттерны нарастания/спада нагрузки
constant-arrival-rate Фиксированная частота запросов Валидация SLO (на основе RPS)
ramping-arrival-rate Частота запросов во времени Поиск точки отказа
externally-controlled Через REST API Динамическая регулировка нагрузки

Locust (на основе Python)

Locust использует классы Python для определения поведения пользователей. Это предпочтительный выбор для команд с сильными навыками Python, сложной логикой сценариев или при необходимости совместного использования кода с существующей тестовой инфраструктурой на Python.

Почему стоит выбрать Locust

  • Чистый Python. Используйте существующие библиотеки (requests, клиенты баз данных, собственные SDK).
  • Распределённая работа по умолчанию. Встроенная архитектура master/worker для горизонтального масштабирования.
  • Веб-интерфейс в реальном времени. Мониторинг хода тестирования и настройка параметров на лету.
  • Обработчики событий. Глубокая кастомизация жизненного цикла запросов, обработки ошибок и отчётности.

Полный пример Locust

# locustfile.py -- Multi-persona e-commerce load test
from locust import HttpUser, task, between, tag, events
import random
import logging

logger = logging.getLogger(__name__)


class BrowsingUser(HttpUser):
    """Simulates casual browsing behavior -- 70% of traffic."""
    wait_time = between(1, 5)
    weight = 7  # 70% of simulated users

    @task(5)
    def view_homepage(self):
        with self.client.get("/", name="Homepage", catch_response=True) as response:
            if response.status_code != 200:
                response.failure(f"Homepage returned {response.status_code}")
            elif "Welcome" not in response.text:
                response.failure("Homepage missing welcome text")

    @task(3)
    def view_product(self):
        product_id = random.randint(1, 1000)
        self.client.get(f"/products/{product_id}", name="/products/[id]")

    @task(1)
    @tag("checkout")
    def add_to_cart(self):
        self.client.post("/cart", json={"product_id": 42, "quantity": 1})

    def on_start(self):
        """Runs once per simulated user at start."""
        logger.info("BrowsingUser session started")


class APIUser(HttpUser):
    """Simulates API consumer traffic -- 30% of traffic."""
    wait_time = between(0.1, 0.5)
    weight = 3  # 30% of simulated users

    @task
    def search(self):
        query = random.choice(["laptop", "phone", "headphones", "keyboard"])
        self.client.get(f"/api/search?q={query}", name="API Search")

    @task
    def get_inventory(self):
        product_id = random.randint(1, 100)
        self.client.get(f"/api/inventory/{product_id}", name="API Inventory")


# Custom event listener for reporting
@events.request.add_listener
def on_request(request_type, name, response_time, response_length, exception, **kwargs):
    if response_time > 5000:
        logger.warning(f"Slow request: {name} took {response_time}ms")

Запуск Locust

# Local single-process run
locust -f locustfile.py --host=https://staging.example.com

# Headless mode for CI (no web UI)
locust -f locustfile.py --host=https://staging.example.com \
  --headless -u 100 -r 10 --run-time 5m

# Distributed mode: start master
locust -f locustfile.py --master --host=https://staging.example.com

# Distributed mode: start workers (run on multiple machines)
locust -f locustfile.py --worker --master-host=192.168.1.100

# Filter by tag
locust -f locustfile.py --tags checkout --host=https://staging.example.com

k6 vs Locust: фреймворк принятия решений

При выборе между k6 и Locust учитывайте контекст вашей команды:

Фактор Выбирайте k6 Выбирайте Locust
Язык команды JavaScript/TypeScript Python
Основное применение Автоматические шлюзы CI/CD Исследовательское тестирование + CI
Потребности в протоколах HTTP, gRPC, WebSocket HTTP + кастомные протоколы через Python
Бюджет ресурсов Ограниченный (k6 чрезвычайно эффективен) Умеренный
Мониторинг в реальном времени Интеграция с Grafana Встроенный веб-интерфейс
Распределённое выполнение k6-operator на K8s Нативный master/worker
Сложность скриптинга Простой — средний Средний — сложный
Существующая тестовая инфраструктура Экосистема Node.js Экосистема Python

Гибридный подход

Многие зрелые команды используют оба инструмента для разных целей:

  • k6 в CI для автоматических шлюзов производительности (прохождение/провал на основе пороговых значений)
  • Locust для исследовательского тестирования, где блистает интерактивный веб-интерфейс и гибкость Python
  • k6 Cloud для масштабных тестов, когда вам нужны тысячи VU в разных регионах

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