Тестирование производительности и нагрузки (Performance & Load Testing)

Как тестировать производительность, находить узкие места (Bottlenecks) и обеспечивать стабильность под нагрузкой


Performance Testing

📍 Вы здесь:

[✓] Статья 1: Основы QA (QA Fundamentals)
[✓] Статья 2: Практика QA (QA Practice)
[✓] Статья 3: Структуры данных и алгоритмы (DSA for QA)
[✓] Статья 4: Фреймворки автоматизации (Automation Frameworks)
[✓] Статья 5: Непрерывная интеграция (CI/CD)
[✓] Статья 6: Техники тест-дизайна (Test Design Techniques)
[→] Статья 7: Тестирование производительности (Performance Testing) ← Сейчас читаете
[ ] Статья 8: Как получить работу в Apple (Landing Your Dream Job)

Прогресс: 87% ✨

Ваше приложение работает отлично на вашем ноутбуке. Но что произойдет, когда 10,000 пользователей зайдут одновременно? Когда база данных вырастет до 10 миллионов записей? Когда API будет получать 1000 запросов в секунду?

Добро пожаловать в тестирование производительности (Performance Testing) — критический навык, который отличает Senior QA от Middle.

Из реальной вакансии Apple Software Quality Engineer:

“Опыт тестирования и анализа производительности (Experience with performance testing and analysis)”

В этой статье вы научитесь:

  • Понимать метрики производительности
  • Проводить нагрузочное (Load) и стресс-тестирование (Stress Testing)
  • Использовать JMeter, K6, Gatling
  • Находить и анализировать узкие места (Bottlenecks)
  • Интегрировать тесты производительности в CI/CD

📋 Содержание

  1. Типы тестирования производительности (Performance Testing Types)
  2. Ключевые метрики производительности (Key Performance Metrics)
  3. JMeter: Отраслевой стандарт (Industry Standard)
  4. K6: Современный JavaScript подход (Modern JavaScript Approach)
  5. Gatling: Мощь Scala (The Power of Scala)
  6. Реальные сценарии (Real-World Scenarios)
  7. Анализ результатов и поиск узких мест (Bottleneck Analysis)
  8. Тестирование производительности в CI/CD (Performance Testing in CI/CD)
  9. Лучшие практики (Best Practices)
  10. Вопросы на собеседовании (Interview Questions)

🎯 Типы тестирования производительности

1. Нагрузочное тестирование (Load Testing)

Цель: Проверить, как система ведёт себя под ожидаемой нагрузкой

Пример:

  • Обычно: 1,000 одновременных пользователей
  • Black Friday: 50,000 одновременных пользователей

Вопросы:

  • Выдержит ли система планируемую нагрузку?
  • Какое response time при нормальной нагрузке?
// K6 Load Test Example
import http from 'k6/http';
import { sleep, check } from 'k6';

export const options = {
    stages: [
        { duration: '2m', target: 100 },  // Рост до 100 пользователей
        { duration: '5m', target: 100 },  // Держим 100 пользователей
        { duration: '2m', target: 0 },    // Снижение до 0
    ],
    thresholds: {
        http_req_duration: ['p(95)<500'], // 95% запросов под 500ms
        http_req_failed: ['rate<0.01'],   // <1% ошибок
    },
};

export default function() {
    const res = http.get('https://api.example.com/products');
    
    check(res, {
        'status is 200': (r) => r.status === 200,
        'response time < 500ms': (r) => r.timings.duration < 500,
    });
    
    sleep(1);
}

2. Стресс-тестирование (Stress Testing)

Цель: Найти точку отказа системы

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

Вопросы:

  • Когда система начинает деградировать?
  • Как она восстанавливается после падения?
  • Какие компоненты падают первыми?
// K6 Stress Test
export const options = {
    stages: [
        { duration: '2m', target: 100 },   // Нормальная нагрузка
        { duration: '5m', target: 200 },   // Около точки отказа
        { duration: '2m', target: 300 },   // За пределами возможностей
        { duration: '5m', target: 400 },   // Сильно за пределами
        { duration: '10m', target: 0 },    // Восстановление
    ],
};

export default function() {
    const res = http.get('https://api.example.com/products');
    
    check(res, {
        'status is not 500': (r) => r.status !== 500,
    });
    
    sleep(1);
}

3. Тестирование пиковой нагрузки (Spike Testing)

Цель: Проверить реакцию на внезапный всплеск нагрузки

Пример:

  • Рекламная кампания запустилась
  • Viral post в социальных сетях
  • Flash sale начался
// K6 Spike Test
export const options = {
    stages: [
        { duration: '10s', target: 100 },  // Нормально
        { duration: '1m', target: 2000 },  // SPIKE!
        { duration: '3m', target: 2000 },  // Держим spike
        { duration: '10s', target: 100 },  // Возврат к нормальному
        { duration: '3m', target: 100 },   // Восстановление
    ],
};

4. Тестирование длительной нагрузки (Soak/Endurance Testing)

Цель: Проверить стабильность при длительной нагрузке

Проблемы, которые находит:

  • Утечки памяти (Memory Leaks)
  • Утечки соединений с БД (Connection Leaks)
  • Проблемы с дисковым пространством
  • Рост лог-файлов
// K6 Soak Test - работает часами
export const options = {
    stages: [
        { duration: '2m', target: 400 },    // Рост
        { duration: '3h56m', target: 400 }, // Держим ~4 часа
        { duration: '2m', target: 0 },      // Снижение
    ],
};

📊 Ключевые метрики производительности

1. Время отклика (Response Time)

Определение: Время от отправки запроса до получения ответа

Метрики:

  • Среднее время отклика (Average Response Time) - среднее время
  • Медиана (50-й процентиль) - половина запросов быстрее
  • 90-й процентиль (p90) - 90% запросов быстрее
  • 95-й процентиль (p95) - 95% запросов быстрее
  • 99-й процентиль (p99) - 99% запросов быстрее

Почему процентили важнее среднего:

Пример:
9 запросов: 100ms каждый
1 запрос: 10,000ms (timeout)

Average: 1,090ms (выглядит плохо)
95th percentile: 100ms (реально хорошо для 95% пользователей)

Целевые значения:

Тип операцииЦель
Загрузка страницы< 2 секунд
API GET< 200ms
API POST< 500ms
Поиск< 1 секунда
Запрос к БД< 100ms

2. Пропускная способность (Throughput)

Определение: Количество запросов в единицу времени

Метрики:

  • Запросов в секунду (Requests per second, RPS)
  • Транзакций в секунду (Transactions per second, TPS)
  • Страниц в минуту (Pages per minute)

Пример:

Хорошо: 1000 RPS с 200ms response time
Плохо: 1000 RPS с 5000ms response time

3. Частота ошибок (Error Rate)

Определение: Процент неуспешных запросов

Целевые значения:

  • Production: < 0.1% (99.9% успех)
  • Staging: < 1%
  • Критические API: < 0.01% (99.99% успех)

Типы ошибок:

  • 4xx ошибки (ошибки клиента)
  • 5xx ошибки (ошибки сервера)
  • Timeouts
  • Ошибки соединения

4. Одновременные пользователи (Concurrent Users)

Определение: Количество пользователей, активных одновременно

Важно понимать разницу:

Всего пользователей (Total Users): 10,000
Активных пользователей (Active Users): 3,000 (онлайн сейчас)
Одновременных пользователей (Concurrent Users): 500 (делают запросы СЕЙЧАС)

Формула для расчёта:

Одновременные пользователи = (Всего пользователей × Процент использования) / Время ожидания

5. Индекс удовлетворённости (Apdex Score)

Определение: Индекс производительности приложения (Application Performance Index) — от 0 до 1

Формула:

Apdex = (Удовлетворённые + (Терпеливые / 2)) / Всего запросов

Где:
- Удовлетворённые (Satisfied): Время отклика ≤ T
- Терпеливые (Tolerating): T < Время отклика ≤ 4T
- Разочарованные (Frustrated): Время отклика > 4T

Пример:

T = 500ms (целевое значение)

100 запросов:
- 70 под 500ms (Удовлетворённые)
- 20 между 500ms-2000ms (Терпеливые)
- 10 более 2000ms (Разочарованные)

Apdex = (70 + 20/2) / 100 = 0.8 (Хорошо)

Рейтинг:

  • 1.0 - 0.94 = Отлично
  • 0.93 - 0.85 = Хорошо
  • 0.84 - 0.70 = Удовлетворительно
  • 0.69 - 0.50 = Плохо
  • < 0.50 = Неприемлемо

🔨 JMeter: Отраслевой стандарт (Industry Standard)

Почему JMeter?

Преимущества:

  • ✅ Отраслевой стандарт (20+ лет)
  • ✅ Огромное сообщество
  • ✅ Графический интерфейс (GUI) для создания тестов
  • ✅ Поддержка множества протоколов
  • ✅ Бесплатный и с открытым исходным кодом (Open-source)

Недостатки:

  • ❌ На Java (тяжёлый)
  • ❌ Графический интерфейс не подходит для CI/CD
  • ❌ Сложная кривая обучения

Базовая установка

# Скачать JMeter
wget https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.3.tgz
tar -xzf apache-jmeter-5.6.3.tgz
cd apache-jmeter-5.6.3/bin

# Запустить GUI
./jmeter

# Запустить в CLI (для CI/CD)
./jmeter -n -t test-plan.jmx -l results.jtl -e -o report/

Создание простого теста

Структура Test Plan:

Test Plan
├── Thread Group (Users)
│   ├── HTTP Request (API call)
│   ├── HTTP Header Manager
│   ├── JSON Assertions
│   └── Response Time Assertions
├── Listeners (Results)
│   ├── View Results Tree
│   ├── Summary Report
│   └── Aggregate Report

Пример: API Load Test

Настройки Thread Group:

Number of Threads: 100
Ramp-up Period: 60 секунд
Loop Count: 10

HTTP Request:

Server: api.example.com
Port: 443
Protocol: https
Method: GET
Path: /api/v1/products

Assertions:

// Response Code
Response Code: 200

// Response Time
Response Time: <= 500ms

// JSON Body
$.data.length > 0

Продвинутый уровень: Параметризация

CSV Data Set Config:

users.csv:
email,password
user1@test.com,pass123
user2@test.com,pass456
user3@test.com,pass789

HTTP Request с переменными:

POST /api/login
Body:
{
    "email": "${email}",
    "password": "${password}"
}

⚡ K6: Современный JavaScript подход

Почему K6?

Преимущества:

  • ✅ JavaScript (знакомый синтаксис для QA)
  • ✅ Командная строка в приоритете (CLI-first) — отлично для CI/CD
  • ✅ Лёгкий и быстрый
  • ✅ Встроенные результаты в JSON
  • ✅ Облачная интеграция (Cloud Integration)

Недостатки:

  • ❌ Нет графического интерфейса (GUI)
  • ❌ Меньше протоколов чем JMeter
  • ❌ Относительно молодой (2017)

Установка

# macOS
brew install k6

# Linux
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

# Windows
choco install k6

# Docker
docker pull grafana/k6

Базовый тест

simple-test.js:

import http from 'k6/http';
import { sleep, check } from 'k6';

export const options = {
    vus: 10,        // Виртуальные пользователи
    duration: '30s', // Длительность теста
};

export default function() {
    const res = http.get('https://api.example.com/products');
    
    check(res, {
        'status is 200': (r) => r.status === 200,
        'response time < 200ms': (r) => r.timings.duration < 200,
    });
    
    sleep(1);
}

Запуск:

k6 run simple-test.js

Продвинутый уровень: Сценарии

multi-scenario.js:

import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
    scenarios: {
        // Сценарий 1: Просмотр продуктов
        browse: {
            executor: 'constant-vus',
            vus: 50,
            duration: '5m',
            exec: 'browseProducts',
        },
        
        // Сценарий 2: Поиск
        search: {
            executor: 'ramping-vus',
            startVUs: 0,
            stages: [
                { duration: '2m', target: 20 },
                { duration: '5m', target: 20 },
                { duration: '2m', target: 0 },
            ],
            exec: 'searchProducts',
        },
        
        // Сценарий 3: Оформление заказа (тяжелая операция)
        checkout: {
            executor: 'constant-arrival-rate',
            rate: 10,
            timeUnit: '1s',
            duration: '5m',
            preAllocatedVUs: 50,
            exec: 'checkoutFlow',
        },
    },
    
    thresholds: {
        'http_req_duration{scenario:browse}': ['p(95)<500'],
        'http_req_duration{scenario:search}': ['p(95)<1000'],
        'http_req_duration{scenario:checkout}': ['p(95)<2000'],
        'http_req_failed': ['rate<0.01'],
    },
};

export function browseProducts() {
    http.get('https://api.example.com/products');
    sleep(1);
}

export function searchProducts() {
    http.get('https://api.example.com/products/search?q=laptop');
    sleep(2);
}

export function checkoutFlow() {
    // Логин
    const loginRes = http.post('https://api.example.com/auth/login', {
        email: 'test@example.com',
        password: 'pass123',
    });
    
    const token = loginRes.json('token');
    
    // Добавить в корзину
    http.post('https://api.example.com/cart', 
        JSON.stringify({ productId: 123, quantity: 1 }),
        { headers: { 'Authorization': `Bearer ${token}` } }
    );
    
    // Оформление заказа
    http.post('https://api.example.com/checkout',
        JSON.stringify({ paymentMethod: 'card' }),
        { headers: { 'Authorization': `Bearer ${token}` } }
    );
    
    sleep(3);
}

Кастомные метрики

import http from 'k6/http';
import { Trend, Counter } from 'k6/metrics';

// Кастомные метрики
const loginDuration = new Trend('login_duration');
const checkoutErrors = new Counter('checkout_errors');

export default function() {
    const start = Date.now();
    const res = http.post('https://api.example.com/login', {...});
    const duration = Date.now() - start;
    
    loginDuration.add(duration);
    
    if (res.status !== 200) {
        checkoutErrors.add(1);
    }
}

K6 в CI/CD

GitHub Actions:

name: Performance Tests

on: [push]

jobs:
  k6-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Run K6 test
        uses: grafana/k6-action@v0.3.1
        with:
          filename: performance-tests/load-test.js
          flags: --out json=results.json
      
      - name: Upload results
        uses: actions/upload-artifact@v3
        with:
          name: k6-results
          path: results.json

🎯 Gatling: Мощь Scala (The Power of Scala)

Почему Gatling?

Преимущества (Advantages):

  • ✅ Очень высокая производительность (Very High Performance)
  • ✅ Отличные отчеты (Excellent Reports) - лучшие в индустрии
  • ✅ Отлично подходит для HTTP/WebSocket
  • ✅ Переиспользуемый код симуляций (Reusable Simulation Code)

Недостатки (Disadvantages):

  • ❌ Scala - сложнее для QA без навыков программирования (Difficult for non-programmers)
  • ❌ Меньше протоколов (Fewer Protocols)
  • ❌ Сообщество меньше чем у JMeter (Smaller Community)

Установка

# Скачать
wget https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/3.10.3/gatling-charts-highcharts-bundle-3.10.3-bundle.zip
unzip gatling-charts-highcharts-bundle-3.10.3-bundle.zip
cd gatling-charts-highcharts-bundle-3.10.3

# Запустить recorder (для записи сценария)
./bin/recorder.sh

# Запустить симуляцию
./bin/gatling.sh

Базовая симуляция

BasicSimulation.scala:

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class BasicSimulation extends Simulation {
  
  val httpProtocol = http
    .baseUrl("https://api.example.com")
    .acceptHeader("application/json")
    .userAgentHeader("Gatling Performance Test")
  
  val scn = scenario("Basic Load Test")
    .exec(
      http("Get Products")
        .get("/api/products")
        .check(status.is(200))
        .check(jsonPath("$.data").exists)
        .check(responseTimeInMillis.lte(500))
    )
    .pause(1)
  
  setUp(
    scn.inject(
      rampUsers(100).during(60.seconds) // 100 пользователей за 60 секунд
    ).protocols(httpProtocol)
  ).assertions(
    global.responseTime.max.lt(5000),
    global.successfulRequests.percent.gt(95)
  )
}

🔍 Реальные сценарии (Real-World Scenarios)

Сценарий 1: E-commerce Black Friday (Чёрная Пятница)

Требования (Requirements):

  • Обычный режим: 5,000 одновременных пользователей (Concurrent Users)
  • Чёрная Пятница: 100,000 одновременных пользователей
  • Ожидается пиковая нагрузка (Spike) в 0:00

Стратегия тестирования (Testing Strategy):

// K6 Black Friday Simulation
export const options = {
    stages: [
        // До полуночи: нормальный трафик
        { duration: '30m', target: 5000 },
        
        // Полночь - spike!
        { duration: '2m', target: 100000 },
        
        // Держим пик
        { duration: '1h', target: 100000 },
        
        // Постепенное снижение
        { duration: '30m', target: 50000 },
        { duration: '1h', target: 20000 },
        { duration: '2h', target: 5000 },
    ],
    
    thresholds: {
        'http_req_duration{critical:yes}': ['p(99)<1000'], // Критические страницы
        'http_req_duration{critical:no}': ['p(99)<5000'],  // Некритические
        'http_req_failed': ['rate<0.1'],                   // 0.1% ошибок OK
    },
};

Сценарий 2: API Rate Limiting

Требования:

  • Лимит API: 1000 запросов/минуту на пользователя
  • Нужно проверить работу rate limiting
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
    scenarios: {
        // Нормальное использование - должно работать
        normal: {
            executor: 'constant-arrival-rate',
            rate: 900,                    // 900 req/min (под лимитом)
            timeUnit: '1m',
            duration: '5m',
            preAllocatedVUs: 50,
            exec: 'normalRequest',
        },
        
        // Чрезмерное использование - должен быть rate limit
        excessive: {
            executor: 'constant-arrival-rate',
            rate: 1500,                   // 1500 req/min (над лимитом!)
            timeUnit: '1m',
            duration: '5m',
            preAllocatedVUs: 100,
            exec: 'excessiveRequest',
        },
    },
};

export function normalRequest() {
    const res = http.get('https://api.example.com/data');
    check(res, {
        'normal request succeeds': (r) => r.status === 200,
    });
}

export function excessiveRequest() {
    const res = http.get('https://api.example.com/data');
    check(res, {
        'excessive request rate limited': (r) => r.status === 429,
        'has retry-after header': (r) => r.headers['Retry-After'] !== undefined,
    });
}

📈 Анализ результатов и поиск узких мест (Bottleneck Analysis)

Чтение отчётов тестирования производительности (Performance Testing Reports)

Ключевые разделы для анализа (Key Sections):

  1. Сводная статистика (Summary Statistics)
Requests: 10,000
Duration: 5 минут
Success Rate: 98.5%
Avg Response Time: 450ms
p95 Response Time: 850ms
p99 Response Time: 2,150ms
Max Response Time: 5,200ms

Интерпретация:

  • ✅ 98.5% success rate (хорошо, > 99% идеально)
  • ✅ Average 450ms (приемлемо для большинства API)
  • ⚠️ p99 2.15s (1.5% пользователей ждут > 2s)
  • ❌ Max 5.2s (требует расследования!)
  1. Распределение Response Time
0-100ms:    15% (1,500 запросов)
100-300ms:  45% (4,500 запросов)
300-500ms:  25% (2,500 запросов)
500-1000ms: 10% (1,000 запросов)
1000-2000ms: 3% (300 запросов)
2000ms+:     2% (200 запросов) ← Исследовать!
  1. Распределение ошибок
200 OK:        9,850 (98.5%)
400 Bad Request:  50 (0.5%)
429 Rate Limit:   75 (0.75%)
500 Server Error: 20 (0.2%) ← Критично!
504 Timeout:       5 (0.05%) ← Критично!

Типичные паттерны Bottlenecks

Паттерн 1: Постепенная деградация производительности

Симптом:

Минута 1: p95 = 300ms
Минута 2: p95 = 350ms
Минута 3: p95 = 450ms
Минута 4: p95 = 650ms
Минута 5: p95 = 950ms

Вероятные причины:

  • Утечка памяти
  • Пул соединений не освобождается
  • Кэш не очищается
  • Деградация планов запросов БД

Как диагностировать:

# Мониторинг использования памяти
kubectl top pods --namespace=production

# Проверка соединений с БД
SELECT count(*) FROM pg_stat_activity;

# Мониторинг garbage collection
jstat -gc <pid> 1000

Паттерн 2: Внезапный рост ошибок

Симптом:

Минуты 1-3: 0% ошибок
Минута 4: 15% ошибок (500 Internal Server Error)
Минута 5: 35% ошибок

Вероятные причины:

  • Пул потоков исчерпан
  • Пул соединений с БД полон
  • Выход за пределы памяти
  • Сработал circuit breaker

Паттерн 3: Бимодальное время ответа

Симптом:

80% запросов: 100-200ms
20% запросов: 5000-6000ms

Нет постепенного распределения!

Вероятные причины:

  • Cache hit vs cache miss
  • Отставание read replica
  • Cold start (serverless)
  • Проблемы с DNS

🔄 Тестирование производительности в CI/CD (Performance Testing in CI/CD)

Стратегия интеграции (Integration Strategy)

Когда запускать тесты производительности (When to Run Performance Tests):

  1. На Pull Request — Дымовые тесты производительности (Smoke Performance Tests)
  2. Ночью (Nightly) — Полные нагрузочные тесты (Full Load Tests)
  3. Перед деплоем в Production — Стресс-тесты (Stress Tests)
  4. По расписанию (Scheduled) — Soak-тесты (еженедельно)

Пример GitHub Actions (GitHub Actions Example)

name: Performance Tests

on:
  pull_request:
    branches: [ main ]
  schedule:
    - cron: '0 2 * * *'  # Каждую ночь в 2 AM
  workflow_dispatch:

jobs:
  smoke-test:
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Install K6
        run: |
          sudo gpg -k
          sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
          echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
          sudo apt-get update
          sudo apt-get install k6
      
      - name: Run smoke test
        run: k6 run --out json=results.json tests/smoke-test.js
      
      - name: Check thresholds
        run: |
          if grep -q '"thresholds".*"failed":true' results.json; then
            echo "❌ Performance thresholds failed!"
            exit 1
          fi

✅ Лучшие практики (Best Practices)

1. Начинайте с малого, масштабируйте постепенно (Start Small, Scale Up)

Неправильно (Wrong Approach):

// Не делайте так!
export const options = {
    vus: 10000,          // Слишком много слишком быстро
    duration: '10s',
};

Правильно (Correct Approach):

// Делайте так!
export const options = {
    stages: [
        { duration: '2m', target: 10 },    // Начинаем с малого
        { duration: '5m', target: 50 },    // Постепенный рост
        { duration: '10m', target: 100 },  // Целевая нагрузка
        { duration: '5m', target: 200 },   // Увеличиваем
        { duration: '5m', target: 0 },     // Снижение (Ramp-down)
    ],
};

2. Используйте реалистичное время ожидания (Use Realistic Think Time)

Неправильно:

export default function() {
    http.get('/api/products');
    http.post('/api/cart', {...});
    http.post('/api/checkout', {...});
    // Нет пауз - нереалистично!
}

Правильно:

export default function() {
    http.get('/api/products');
    sleep(randomBetween(2, 5));  // Пользователь изучает продукты
    
    http.post('/api/cart', {...});
    sleep(randomBetween(1, 3));  // Пользователь проверяет корзину
    
    http.post('/api/checkout', {...});
    sleep(randomBetween(5, 10)); // Пользователь заполняет форму
}

function randomBetween(min, max) {
    return Math.random() * (max - min) + min;
}

3. Мониторьте системные ресурсы

Во время тестов мониторьте:

# Использование CPU
top

# Память
free -h

# Disk I/O
iostat -x 1

# Сеть
iftop

# Логи приложения
tail -f /var/log/application.log

# Соединения с БД
watch 'psql -c "SELECT count(*) FROM pg_stat_activity;"'

4. Тестируйте в Production-подобной среде

Чек-лист:

  • Те же характеристики сервера (CPU, RAM, диск)
  • Та же сетевая задержка
  • Тот же размер базы данных
  • Те же внешние зависимости
  • Та же конфигурация балансировщика
  • Тот же CDN/кэш слой

5. Изолируйте тесты

Не делайте:

// Не смешивайте разные типы тестов
export default function() {
    http.get('/api/products');      // Легкая операция
    http.post('/api/heavy-report'); // Тяжелая операция
    // Результаты будут запутанными!
}

Делайте:

// Разделяйте сценарии
export const options = {
    scenarios: {
        light_operations: {
            exec: 'lightOps',
            executor: 'constant-vus',
            vus: 100,
        },
        heavy_operations: {
            exec: 'heavyOps',
            executor: 'constant-vus',
            vus: 10,  // Меньше VUs для тяжелых операций
        },
    },
};

export function lightOps() {
    http.get('/api/products');
}

export function heavyOps() {
    http.post('/api/heavy-report');
}

❓ Вопросы на собеседовании (Interview Questions)

Вопрос 1: “В чём разница между нагрузочным тестированием (Load Testing) и стресс-тестированием (Stress Testing)?”

Хороший ответ (Good Answer):

“Нагрузочное тестирование (Load Testing) проверяет работу системы при ожидаемой нагрузке — например, тестируем, выдержит ли наш e-commerce сайт 10,000 одновременных пользователей при обычной работе. Стресс-тестирование (Stress Testing) выходит за пределы возможностей системы, чтобы найти точку отказа — мы продолжаем увеличивать нагрузку пока система не упадёт, затем наблюдаем как она деградирует и восстанавливается. Нагрузочное тестирование — это валидация; стресс-тестирование — это поиск лимитов.”

Вопрос 2: “Какие метрики вы мониторите при тестировании производительности (Performance Testing)?”

Хороший ответ (Good Answer):

“Я фокусируюсь на пяти ключевых метриках: Время отклика (Response Time) — особенно перцентили p95 и p99, не только среднее; Пропускная способность (Throughput) — запросов в секунду; Частота ошибок (Error Rate) — должна быть под 1%; Использование ресурсов (Resource Utilization) — CPU, память, disk I/O; и Одновременные пользователи (Concurrent Users). Также отслеживаю кастомные бизнес-метрики вроде времени оформления заказа или задержки поиска.”

Вопрос 3: “Как бы вы интегрировали тесты производительности (Performance Tests) в CI/CD?”

Хороший ответ (Good Answer):

“Я использую многоуровневый подход: быстрые дымовые тесты (Smoke Tests) на каждый PR для поимки очевидных регрессий, ночные нагрузочные тесты (Load Tests) с реалистичными сценариями, еженедельные soak-тесты для поиска утечек памяти. Я настраиваю автоматические пороги (Thresholds) — если p95 время отклика превышает 500ms или частота ошибок выше 1%, пайплайн падает. Результаты постятся в Slack и хранятся для исторического сравнения.”

Вопрос 4: “Вы заметили постепенное увеличение времени отклика (Response Times) во время нагрузочного теста. Что бы вы исследовали?”

Хороший ответ (Good Answer):

“Постепенная деградация обычно указывает на утечки ресурсов (Resource Leaks). Я бы проверил: использование памяти на утечки (Memory Leaks), пул соединений с БД на освобождение соединений (Connection Pool), время выполнения запросов на деградацию планов, и логи приложения на предупреждения. Также проверил бы работу кэширования (Caching) — возможно кэш заполняется или eviction сломан.”


🎯 Ключевые выводы (Key Takeaways)

Сводка типов тестирования производительности (Performance Testing Types Summary)

Тип (Type)Цель (Purpose)Длительность (Duration)Паттерн нагрузки (Load Pattern)
Нагрузочное (Load)Валидация ожидаемой нагрузки10-60 минСтабильная (Steady)
Стрессовое (Stress)Найти точку отказа30-60 минРастущая (Increasing)
Пиковое (Spike)Обработка внезапных всплесков5-15 минВнезапные пики (Sudden Peaks)
Длительное (Soak)Найти утечки памяти (Memory Leaks)4-24 часаПостоянная (Steady)

Сравнение инструментов (Tool Comparison)

Инструмент (Tool)Лучше для (Best For)Кривая обучения (Learning Curve)Готов к CI/CD (CI/CD Ready)
JMeterСложных протоколовСредняя (Medium)⚠️ Нужен CLI
K6Современных web-приложенийЛёгкая (Easy)✅ Нативно
GatlingВысокой производительностиСложная (Hard)✅ Нативно

📍 Что дальше?

Завтра - ФИНАЛ!

🍎 Статья 8: Как получить работу в Apple

  • Анализ требований Apple к кандидатам
  • Оптимизация резюме для FAANG
  • Руководство по GitHub портфолио
  • Процесс собеседований (4 раунда)
  • Переговоры о зарплате ($140K-$200K+)
  • Первые 90 дней в Apple

Это кульминация всего, что мы изучили!


💡 Финальный совет

Performance testing - это не одноразовая активность:

Неделя 1: Базовые тесты
Недели 2-4: Разработка
Неделя 4: Проверка performance регрессий
Неделя 5: Load test
Неделя 6: Stress test
Неделя 8: Soak test
Production: Непрерывный мониторинг

Сделайте это частью цикла разработки, а не afterthought!


Была ли статья полезна? 👏

Вопросы? Пишите в комментариях!


Автор: AAnnayev — Senior SDET

Теги: #PerformanceTesting #LoadTesting #JMeter #K6 #Gatling #QA #SDET