Podstawy testów wydajnościowych dla codziennego QA

Testowanie wydajności nie jest tylko dla specjalistów. Dowiedz się, kiedy inżynierowie QA powinni przejmować się wydajnością, jak uruchamiać smoke performance testy z k6 i jak myśleć o latencji i.

Wprowadzenie — wydajność to funkcja

Testowanie wydajności często wydaje się cudzą robotą. Jest dedykowany zespół performance, albo inżynier DevOps, który zajmuje się testami obciążeniowymi przed dużymi wdrożeniami, albo po prostu nie jest testowane wcale, dopóki produkcja nie zwalnia do zera.

Ale wydajność nie jest oddzielnym problemem — to funkcja. Funkcja, która działa poprawnie, ale ładuje się 8 sekund, jest zepsutą funkcją. Użytkownicy nie rozróżniają między „funkcja jest poprawna” a „funkcja jest nieużywalnie wolna”. Doświadczają produktu jako wolnego, a wolne produkty tracą użytkowników.

Ten wpis jest dla codziennych inżynierów QA, którzy chcą zintegrować podstawową świadomość wydajności w swoją praktykę testową bez stawania się pełnoetatowymi specjalistami od wydajności. Nauczysz się:

  • Kiedy QA powinien przejmować się wydajnością (i kiedy eskalować do specjalistów)
  • Jak uruchamiać smoke performance testy z k6
  • Jak myśleć o latencji i budżetach błędów
  • Jak interpretować podstawowe metryki wydajności

To nie jest głębokie zanurzenie w rozproszone testy obciążeniowe, analizę percentyli czy teorię kolejek. To praktyczne wprowadzenie do testowania wydajności jako dyscypliny QA.


Kiedy QA powinien przejmować się wydajnością

Nie każda funkcja wymaga dedykowanego testowania wydajności. Skup swój wysiłek tam, gdzie ma to największe znaczenie.

Scenariusze wysokiego priorytetu

Testuj wydajność dla:

  1. Endpointy API używane w ciasnych pętlach — Jeśli frontend wykonuje 50 wywołań do API podczas ładowania strony, nawet 100ms na wywołanie staje się 5 sekundami blokującego czasu.
  2. Operacje użytkownika z SLA — Przetwarzanie płatności, wyniki wyszukiwania, przepływy checkout. Często mają wyraźne cele latencji (np. „wyniki wyszukiwania w <200ms”).
  3. Endpointy o wysokim ruchu — Login, strona główna, generowanie feedu. Małe regresje tutaj wpływają na wielu użytkowników.
  4. Operacje ciężkie bazodanowo — Raporty, dashboardy, eksporty. Te są podatne na zapytania N+1, brakujące indeksy i joiny kartezjańskie.
  5. Po większych refaktorach — Wymiana bazy danych, migracja do nowego API, zmiana strategii cache. Zweryfikuj, że wydajność nie uległa regresji.

Kiedy eskalować do specjalistów

Eskaluj do dedykowanego zespołu performance, gdy:

  • Potrzebujesz rozproszonego testowania obciążeniowego z tysiącami równoczesnych użytkowników w wielu regionach.
  • Testujesz utrzymane obciążenie przez godziny lub dni (soak testing, stress testing).
  • Problem wymaga głębokiego profilowania planów zapytań bazy danych, wycieków pamięci lub tuningu garbage collection.
  • Musisz modelować planowanie pojemności dla przyszłego wzrostu (np. „czy możemy obsłużyć 10× ruch?”).

Jako inżynier QA, twoim zadaniem jest wychwytywanie oczywistych regresji i ustanawianie smoke-level performance baselines. Specjaliści zajmują się głęboką analizą.

:::info[Smoke Performance Testing] Smoke performance testing oznacza uruchamianie lekkiego testu obciążeniowego, aby wychwycić oczywiste problemy: zapytanie, które trwa 5 sekund zamiast 50ms, endpoint, który się crashuje pod 10 równoczesnymi użytkownikami, lub wyciek pamięci w zadaniu w tle. Nie modelujesz obciążenia produkcyjnego — wychwytasz show-stoppery, zanim dotrą do produkcji. :::


Metryki wydajności, które mają znaczenie

Przed pisaniem testów zrozum metryki, które mierzysz.

Latencja (czas odpowiedzi)

Czas między wysłaniem żądania a otrzymaniem odpowiedzi. Mierzone w milisekundach (ms).

Dlaczego ma znaczenie: Latencja bezpośrednio wpływa na doświadczenie użytkownika. 2-sekundowe ładowanie strony wydaje się wolne. 200ms ładowanie strony wydaje się natychmiastowe.

Jak mierzyć: Rejestruj czas odpowiedzi dla każdego żądania. Raportuj percentyle, nie tylko średnie.

Percentyle (p50, p95, p99)

Percentyle pokazują dystrybucję latencji:

  • p50 (mediana) — 50% żądań jest szybszych niż to. Typowe doświadczenie użytkownika.
  • p95 — 95% żądań jest szybszych niż to. 5% użytkowników widzi gorszą wydajność.
  • p99 — 99% żądań jest szybszych niż to. 1% użytkowników widzi gorszą wydajność.

Dlaczego percentyle mają znaczenie: Średnie ukrywają wartości odstające. Jeśli 99 żądań trwa 100ms, a 1 żądanie trwa 10 sekund, średnia to 200ms — ale jeden użytkownik miał okropne doświadczenie.

Przepustowość (żądania na sekundę)

Ile żądań twój system może obsłużyć na sekundę pod obciążeniem.

Dlaczego ma znaczenie: Przepustowość ujawnia granice pojemności. Jeśli twoje API może obsłużyć tylko 50 żądań/sekundę, a oczekujesz 500/sekundę w produkcji, masz problem.

Wskaźnik błędów

Procent żądań, które nie powiodły się (HTTP 5xx, timeouty, błędy połączenia).

Dlaczego ma znaczenie: Wysokie obciążenie często powoduje niepowodzenia, zanim spowoduje powolność. Endpoint, który działa dobrze z 10 użytkownikami, może zacząć zwracać błędy 503 z 100 użytkownikami.

:::tip[Skup się na percentylach, nie średnich] Latencja p95 wynosząca 500ms oznacza, że 95% użytkowników czeka 500ms lub mniej. Pozostałe 5% może czekać 2 sekundy, 5 sekund lub dłużej. Średnie ukrywają te wartości odstające. Zawsze raportuj p95 lub p99 dla endpointów użytkownika. :::


Uruchamianie smoke performance testów z k6

k6 to open-source, przyjazne dla developerów narzędzie do testowania wydajności. Skrypty są pisane w JavaScript, wykonanie jest szybkie, a integracja CI/CD jest prosta.

Instalacja

# macOS
brew install k6

# Windows
choco install k6

# Linux
sudo apt-get install k6

Lub pobierz z k6.io.

Prosty smoke test

Utwórz smoke-test.js:

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

export let options = {
  vus: 10,        // 10 wirtualnych użytkowników
  duration: '30s', // Uruchom przez 30 sekund
};

export default function () {
  let 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);
}

Uruchom:

k6 run smoke-test.js

Output:

     data_received..................: 1.2 MB  40 kB/s
     data_sent......................: 8.5 kB  283 B/s
     http_req_duration..............: avg=245ms min=120ms med=230ms max=680ms p(90)=380ms p(95)=450ms
     http_reqs......................: 300     10/s
     checks.........................: 100%

Co to ci mówi:

  • http_req_duration — Średnia 245ms, p95 450ms. Większość żądań jest szybka, ale 5% trwa do 680ms.
  • http_reqs — 10 żądań/sekundę utrzymywanych przez 30 sekund.
  • checks — 100% żądań przeszło sprawdzenia status is 200 i response time < 500ms.

Jeśli ten test nie powiedzie się (np. p95 > 1 sekunda lub sprawdzenia spadną poniżej 100%), wykryłeś regresję.


Projektowanie realistycznych scenariuszy obciążenia

Smoke testy są celowo lekkie. Dla bardziej realistycznych scenariuszy modeluj rzeczywiste zachowanie użytkownika.

Przykład: przepływ checkout e-commerce

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

export let options = {
  stages: [
    { duration: '1m', target: 20 },  // Ramp up do 20 użytkowników przez 1 min
    { duration: '3m', target: 20 },  // Utrzymaj 20 użytkowników przez 3 min
    { duration: '1m', target: 0 },   // Ramp down do 0 użytkowników
  ],
};

export default function () {
  // 1. Przeglądaj produkty
  let productsRes = http.get('https://api.example.com/products');
  check(productsRes, { 'products status 200': (r) => r.status === 200 });
  sleep(2);

  // 2. Dodaj do koszyka
  let cartRes = http.post('https://api.example.com/cart', JSON.stringify({
    productId: 123,
    quantity: 1,
  }), { headers: { 'Content-Type': 'application/json' } });
  check(cartRes, { 'cart status 201': (r) => r.status === 201 });
  sleep(1);

  // 3. Checkout
  let checkoutRes = http.post('https://api.example.com/checkout', JSON.stringify({
    cartId: cartRes.json('cartId'),
  }), { headers: { 'Content-Type': 'application/json' } });
  check(checkoutRes, {
    'checkout status 200': (r) => r.status === 200,
    'checkout < 1s': (r) => r.timings.duration < 1000,
  });
  sleep(5);
}

To symuluje realistyczny przepływ użytkownika: przeglądaj → dodaj do koszyka → checkout. Wywołania sleep() symulują think time (użytkownicy nie klikają natychmiast).


Budżety latencji — jak szybko jest wystarczająco szybko?

Budżet latencji to docelowy maksymalny czas odpowiedzi dla danej operacji. Odpowiada na pytanie: „Jak szybko jest wystarczająco szybko?”

Benchmarki branżowe

OperacjaDocelowa latencjaUzasadnienie
Odczyt API (pojedynczy rekord)<100msWydaje się natychmiastowe. Brak zauważalnego opóźnienia.
Zapis API (create/update)<300msAkceptowalne dla akcji inicjowanych przez użytkownika.
Zapytanie wyszukiwania<200msUżytkownicy oczekują natychmiastowych wyników wyszukiwania.
Ładowanie strony (odpowiedź serwera)<500msPozwala na czas renderowania po stronie klienta.
Zadanie w tle (async)Sekundy do minutNie skierowane do użytkownika. Optymalizuj dla niezawodności nad prędkością.

To są wytyczne, nie wartości bezwzględne. Złożony raport może zająć 2 sekundy i nadal być akceptowalny, jeśli użytkownicy rozumieją, że to ciężka operacja.

Ustalanie własnego budżetu latencji

  1. Zmierz obecną wydajność — Uruchom testy k6 przeciwko swojemu API. Zapisz latencję p95.
  2. Zdefiniuj akceptowalne progi — „p95 < 500ms dla wszystkich endpointów” lub „checkout < 1 sekunda”.
  3. Wymuś w CI — Fail builds, jeśli latencja przekracza budżet.

Przykład progu k6:

export let options = {
  thresholds: {
    'http_req_duration': ['p(95)<500'], // Fail, jeśli p95 > 500ms
    'http_req_failed': ['rate<0.01'],   // Fail, jeśli >1% żądań się nie powiedzie
  },
};

Jeśli te progi są naruszone, k6 kończy się z kodem niezerowym, powodując niepowodzenie buildu CI.

:::warning[Nie testuj przeciwko produkcji] Uruchamiaj testy wydajności przeciwko stagingowi lub dedykowanemu środowisku performance. Testowanie obciążenia produkcji może powodować realny wpływ na użytkowników i naruszać warunki usług dla API stron trzecich. :::


Budżety błędów — równoważenie prędkości i niezawodności

Budżet błędów to maksymalny akceptowalny wskaźnik niepowodzeń dla usługi w okresie czasu.

Przykład: 99.9% uptime SLA

99.9% uptime oznacza, że 0.1% żądań może się nie powieść. W ciągu miesiąca z 10 milionami żądań masz budżet 10,000 nieudanych żądań.

Jeśli wydasz cały budżet w pierwszym tygodniu (zły deploy, awaria bazy danych), nie masz pozostałej tolerancji dla niepowodzeń przez resztę miesiąca. Zespół musi priorytetyzować stabilność nad nowymi funkcjami.

Budżety błędów dla wydajności

Zastosuj tę samą koncepcję do latencji. Zdefiniuj budżet błędów dla wolnych żądań:

  • „99% żądań musi być szybszych niż 500ms” oznacza, że 1% może być wolniejsze.
  • Jeśli 5% żądań jest wolniejszych niż 500ms, przekroczyłeś budżet.

To zapobiega problemowi „wszystko jest szybkie, z wyjątkiem tych okazjonalnych 10-sekundowych wartości odstających”.


Integracja testowania wydajności w workflow

Strategia 1: Smoke performance testy w CI

Dodaj smoke test k6 do swojego pipeline CI. Uruchamiaj go przy każdym PR dla krytycznych endpointów.

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

on: [pull_request]

jobs:
  k6:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install k6
        run: |
          sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
          echo "deb 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 tests/smoke-test.js

Strategia 2: Nocne testy obciążeniowe

Uruchamiaj cięższe testy obciążeniowe nocami przeciwko stagingowi. Raportuj wyniki do dashboardu (Grafana, Datadog).

Strategia 3: Walidacja wydajności przed wydaniem

Przed major releases uruchom pełny test obciążeniowy symulujący oczekiwany ruch produkcyjny. Zweryfikuj, że wydajność spełnia SLA.


Typowe problemy wydajności, które QA może wychwycić

Problem N+1 Query

Twoje API zwraca listę 100 użytkowników. Dla każdego użytkownika wykonuje oddzielne zapytanie do bazy danych, aby pobrać ich zdjęcie profilowe. To 101 zapytań (1 dla listy + 100 dla zdjęć).

Jak wykryć: Uruchom test k6 z małą liczbą użytkowników. Sprawdź logi zapytań bazy danych. Jeśli widzisz setki zapytań dla pojedynczego wywołania API, masz problem N+1.

Rozwiązanie: Użyj joinów bazy danych lub batch loading.

Brakujący indeks bazy danych

Endpoint wyszukiwania skanuje całą tabelę users (1 milion wierszy) przy każdym żądaniu, ponieważ kolumna email nie jest zindeksowana.

Jak wykryć: Czas odpowiedzi wzrasta dramatycznie z wolumenem danych. Zapytanie, które trwa 50ms z 1,000 wierszami, trwa 5 sekund z 1 milionem wierszy.

Rozwiązanie: Dodaj indeks bazy danych.

Niekontrolowany wzrost pamięci

Zadanie w tle przetwarza 10,000 rekordów i ładuje je wszystkie do pamięci naraz. Z 100,000 rekordów kończy się pamięć.

Jak wykryć: Uruchom test obciążeniowy z realistycznym wolumenem danych. Monitoruj użycie pamięci. Jeśli pamięć rośnie nieograniczenie, masz wyciek lub nieograniczoną alokację.

Rozwiązanie: Przetwarzaj dane w partiach.


Narzędzia i ekosystem

NarzędziePrzypadek użyciaNotatki
k6Smoke i load testingNajlepsze dla testowania API i mikroserwisów. Skrypty w JavaScript.
Apache JMeterTestowanie obciążeniowe enterpriseOparte na GUI. Dojrzałe. Obsługuje wiele protokołów.
GatlingLoad testing oparte na ScaliPrzyjazne dla developerów. Doskonałe raporty.
LocustLoad testing oparte na PythonieSkrypty w Pythonie. Dobre dla złożonych scenariuszy.
LighthouseWydajność frontenduAudytuje ładowanie strony, dostępność, SEO. Świetne dla wydajności webowej.

Dla inżynierów QA k6 jest najlepszym punktem startowym: minimalna konfiguracja, świetna integracja CI/CD i przejrzysta dokumentacja.


Podsumowanie

Testowanie wydajności nie wymaga specjalizacji, aby zacząć. Jako codzienny inżynier QA możesz:

  • Uruchamiać smoke performance testy z k6, aby wychwycić oczywiste regresje.
  • Definiować i wymuszać budżety latencji dla krytycznych endpointów.
  • Myśleć o percentylach (p95, p99) zamiast średnich.
  • Wychwytywać typowe problemy, takie jak zapytania N+1 i brakujące indeksy.

Nie musisz modelować obciążenia produkcyjnego, tunować garbage collection JVM ani analizować teorii kolejek. Zostaw to specjalistom. Skup się na wychwytywaniu show-stopperów, zanim dotrą do produkcji.

Wydajność to funkcja. Testuj ją jak taką.

Zadanie na ten tydzień: Zidentyfikuj trzy najbardziej krytyczne endpointy API w swojej aplikacji (np. login, wyszukiwanie, checkout). Napisz smoke test k6 dla każdego z nich. Uruchom go lokalnie z 10 wirtualnymi użytkownikami przez 30 sekund. Zapisz latencję p95. Jeśli przekracza 500ms, zbadaj dlaczego.