Automatyzacja testów wydajnościowych w cyklu CI/CD krok po kroku

0
12
4.5/5 - (2 votes)

Nawigacja:

Po co automatyzować testy wydajnościowe w CI/CD i kiedy to ma sens

Jednorazowe „strzelanie z load testów” kontra ciągłe sprawdzanie regresji

Ręczne uruchamianie testów obciążeniowych raz na kilka miesięcy to typowy scenariusz w zespołach, które dopiero zaczynają myśleć o wydajności. Taki „strzał” pozwala złapać ewidentne problemy (np. serwer pada przy kilkudziesięciu równoległych użytkownikach), ale kompletnie nie nadaje się do wykrywania drobnych regresji wydajności w czasie. Każda większa zmiana w kodzie, bibliotekach, konfiguracji bazy czy nawet parametrach JVM/Node/Pythona może wprowadzić subtelne pogorszenia, które pojedynczy test nie wyłapie.

Automatyzacja testów wydajnościowych w cyklu CI/CD zmienia tę dynamikę. Testy uruchamiane są iteracyjnie, przy każdym merge request, przy buildach nocnych albo przed wydaniem release candidate. Zamiast pojedynczego pomiaru powstaje historia wyników, którą można śledzić jak wykresy z monitoringu. Regresja o kilka procent w p95 czy p99 przestaje być „szumem”, a staje się namierzalnym zdarzeniem, które potrafi zablokować wdrożenie. Różnica jest podobna jak między ręcznym smoke testem raz na kwartał a testami jednostkowymi odpalanymi przy każdym commitcie.

Kluczową korzyścią nie jest samo uruchamianie testów, tylko porównywalność i powtarzalność. Jeżeli test ma tę samą definicję, te same parametry, podobne środowisko, a wyniki są gromadzone w jednym miejscu, można odpowiedzieć na proste pytanie: „czy w tej wersji jest lepiej, gorzej czy tak samo”. Jednorazowe load testy odpowiadają raczej na pytanie: „czy system w ogóle się trzyma w przybliżonych warunkach produkcji”. To za mało, żeby zapanować nad wydajnością długofalowo.

Kiedy inwestycja w automatyzację testów wydajnościowych się opłaca

Nie w każdym projekcie rozbudowany pipeline wydajnościowy ma sens. Inaczej wygląda potrzeba w systemie SaaS, który ma tysiące użytkowników dziennie, a inaczej w małej aplikacji backoffice używanej przez kilkanaście osób. Uogólniając, automatyzacja testów wydajnościowych w CI/CD zwykle się zwraca, gdy:

  • system jest wystawiony na nieprzewidywalne lub sezonowe obciążenia (e‑commerce, SaaS B2C/B2B, systemy rezerwacyjne),
  • API jest krytyczne biznesowo i konsumowane przez wielu partnerów lub inne usługi (integracje B2B, platformy płatnicze, huby integracyjne),
  • koszt awarii wydajnościowej jest znaczący – utracone transakcje, SLA zapisane w umowach, wizerunek, kary umowne,
  • system jest rozwijany przez wiele zespołów równolegle i trudno ręcznie ocenić wpływ zmian na wydajność.

Dla małych aplikacji wewnętrznych, wykorzystywanych przez ograniczoną liczbę użytkowników w godzinach pracy, pełna automatyzacja testów wydajnościowych często jest przesadą. W takich przypadkach sens ma prosty sanity check obciążenia na poziomie komponentu (np. kluczowe endpointy API) raz na release, bez rozbudowanej infrastruktury do generowania dużego loadu. Można wtedy bardziej polegać na dobrym monitoringu produkcji i ostrych alertach, niż na ciężkich testach w CI.

Rozsądne kryterium: jeżeli koszt obsługi awarii wydajnościowej (w pracy zespołu, stracie przychodów, reputacji) istotnie przewyższa koszt uruchomienia i utrzymania automatycznych testów, automatyzacja zwykle ma sens. Jeśli odwrotnie – lepiej zacząć od prostych, ręcznych testów plus dobrego monitoringu i dopiero w razie wzrostu skali rozbudowywać pipeline.

Problemy organizacyjne, które rozwiązuje automatyzacja

Automatyzacja performance testingu to nie tylko kwestia technologii. Często rozwiązuje problemy czysto organizacyjne, które inaczej powracają przy każdym większym release:

  • Odpowiedzialność – gdy testy są częścią pipeline, regresja wydajnościowa jest widoczna tam samo jak regresja funkcjonalna. Zespół nie może „zapomnieć” o testach, bo pipeline nie przejdzie.
  • Powtarzalność – scenariusze, dane testowe i konfiguracja są zapisane w repozytorium jako kod. Nie ma sytuacji, że ktoś lokalnie odpala inny scenariusz niż kolega, albo że konfiguracja z zeszłego kwartału „gdzieś zniknęła”.
  • Historia wyników – wyniki są wersjonowane razem z kodem lub przechowywane w centralnym narzędziu (np. InfluxDB, Prometheus, Elastic). Można prześledzić, od którego release’a zaczął rosnąć czas p95 albo liczba błędów 5xx.
  • Standaryzacja języka – gdy w definicji pipeline pojawiają się konkretne metryki i progi, rozmowa o wydajności przestaje być subiektywna („wydaje się wolno”), a staje się mierzalna („p95 > 400 ms dla endpointu X”).

Bez automatyzacji zespoły są często zdane na ad‑hoc’owe load testy prowadzone przez jedną lub dwie osoby „od wydajności”. To generuje kolejki, wąskie gardła, a czasem polityczne spory o to, kto zawinił. Pipeline wydajnościowy wymusza bardziej inżynierskie podejście: zdefiniowane kryteria, powtarzalne testy, jasne reguły blokowania wdrożeń.

Czego automatyzacja testów wydajnościowych nie załatwi

Automatyzacja nie jest panaceum. Istnieje kilka obszarów, gdzie nawet najlepiej zbudowany pipeline nie pomoże, jeśli fundamenty są słabe:

  • Brak profilowania – testy pokażą, że jest wolno, ale nie powiedzą, dlaczego. Bez profilera aplikacyjnego, narzędzi APM i możliwości głębszej diagnostyki zespół szybko stanie w miejscu.
  • Słaba architektura – jeżeli architektura systemu nie jest w stanie skasować więcej niż N żądań z powodu ograniczeń strukturalnych (np. sekwencyjne przetwarzanie, monolityczny lock w bazie), load testy będą tylko powtarzać ten sam wniosek.
  • Brak obserwowalności – bez sensownie zebranych metryk, logów i trace’ów z każdej warstwy, analiza wyników sprowadzi się do patrzenia w średnie czasy odpowiedzi, co szybko prowadzi do błędnych wniosków.
  • Złe dane testowe – jeśli testujesz na pustej bazie, scenariuszach idealnych i danych z generatorka, nie dowiesz się, jak system zachowa się na realnym, zaśmieconym, rozproszonym zestawie danych.

Automatyzacja testów wydajnościowych w cyklu CI/CD warto więc traktować jako część szerszego ekosystemu: z dobrą architekturą, instrumentacją, monitoringiem i dojrzałym procesem reagowania na wyniki. Bez tego pipeline będzie jedynie generował czerwone buildy bez sensownego dalszego ciągu.

Realistyczne cele automatyzacji testów wydajnościowych

Zamiast obiecywać „zero problemów z wydajnością”, lepiej przyjąć kilka umiarkowanych, ale weryfikowalnych celów:

  • Szybkie wyłapywanie regresji – każda zmiana, która istotnie pogarsza czasy odpowiedzi, throughput lub stabilność pod obciążeniem, powinna zostać wykryta przed wdrożeniem na produkcję.
  • Weryfikacja budżetu wydajnościowego – system ma narzuconą budżetowo lub kontraktowo wydajność (np. X rps przy Y ms p95). Pipeline ma potwierdzać, czy ten budżet jest jeszcze spełniany.
  • Przewidywalność zachowania – dla typowych scenariuszy loadu zespół chce wiedzieć, jakie są granice systemu, jak reaguje na nagłe skoki, i czy przyrost użytkowników nie doprowadzi do zaskoczenia.
  • Budowanie wiedzy – wyniki gromadzone w czasie stają się podstawą do decyzji o skalowaniu, refaktoryzacjach czy zmianie architektury. Zamiast „wydaje się wolno” są twarde liczby.

Jeżeli pipeline wydajnościowy systematycznie odpowiada na te cztery obszary, można uznać, że inwestycja w automatyzację jest dobrze ulokowana.

Zardzewiałe rury i zawory przemysłowe w starej hali fabrycznej
Źródło: Pexels | Autor: Pixabay

Podstawowe pojęcia: rodzaje testów wydajnościowych i ich miejsce w cyklu CI/CD

Kluczowe rodzaje testów wydajnościowych a automatyzacja

Pod hasłem „testy wydajnościowe” kryje się kilka odmiennych kategorii, które różnie nadają się do automatyzacji w CI/CD:

  • Testy obciążeniowe (load) – sprawdzają zachowanie systemu przy spodziewanym lub nieco podwyższonym obciążeniu, zbliżonym do typowej produkcji. To najczęstszy kandydat do automatyzacji, zwłaszcza w wersji skróconej (np. 5–15 minut).
  • Testy stresowe (stress) – próbują doprowadzić system do granic i poza nie, stopniowo zwiększając load, aż do pojawienia się błędów, dużych opóźnień lub załamania. Zwykle są dłuższe i bardziej inwazyjne; w pełnej formie lepiej uruchamiać je rzadziej (np. przed ważnym release).
  • Testy długotrwałe (soak/endurance) – badają zachowanie systemu przez wiele godzin lub dni pod umiarkowanym obciążeniem, ujawniając wycieki pamięci, narastanie kolejek, fragmentację zasobów. Są trudne do uruchamiania przy każdym commitcie; zwykle lądują w osobnym, nocnym pipeline lub w ramach testów pre‑release.
  • Testy spike – symulują nagły skok obciążenia (np. kampania marketingowa, flash sale), a następnie spadek. Często da się je zautomatyzować w pipeline, pod warunkiem skrócenia czasu trwania i rozsądnego poziomu loadu.
  • Testy skali (capacity/scalability) – mają odpowiedzieć na pytanie, jak zmienia się wydajność przy zwiększaniu zasobów (CPU, RAM, instancje) lub przy wzroście użytkowników. Zwykle mają charakter eksperymentów i są trudniejsze do pełnej automatyzacji, choć częściowo można je odwzorować w CI (np. testy przy różnych rozmiarach klastrów).

Reguła praktyczna: do stałej automatyzacji w CI/CD najczęściej wybiera się skrócone, deterministyczne testy obciążeniowe z elementami spike. Pełne testy stresowe, soak i rozbudowane testy skali działają lepiej jako cykliczne, dedykowane pipeline’y (np. nocne lub tygodniowe) albo jako osobny proces inżynierii wydajności.

Poziomy testów wydajnościowych: od API do całego systemu

Automatyzacja performance testingu może dotyczyć różnych poziomów systemu:

  • Poziom API/komponentu – testy kierowane bezpośrednio w endpointy REST/gRPC/GraphQL jednej usługi. Łatwiejsze do zautomatyzowania, szybsze, tańsze. Dają dobre sygnały o regresjach w stanie „laboratoryjnym”, ale nie uwzględniają pełnego łańcucha (front, CDN, integracje).
  • Poziom usługi + zależności – testowane API wywołuje inne usługi, bazy, kolejki, ale nadal w środowisku testowym, odizolowanym od frontu lub części integracji zewnętrznych. To kompromis między realnością a sterowalnością.
  • Poziom całego systemu (end‑to‑end) – symulacja rzeczywistych użytkowników: frontend + backend + integracje. Najbardziej zbliżone do produkcji, ale też najdroższe w utrzymaniu, wrażliwe na problemy w zewnętrznych systemach i najmniej deterministyczne.

W praktyce sensowny pipeline wydajnościowy używa miksu tych poziomów. Na poziomie CI (na merge request) często opłaca się stosować krótkie testy API kluczowych endpointów. Na poziomie release candidate – dłuższe testy na poziomie usługi. E2E performance testy zwykle są uruchamiane rzadziej, bo wymagają dostępności wielu zależności i są bardziej podatne na fluktuacje.

Mapowanie rodzajów testów na etapy CI/CD

W cyklu CI/CD można wyróżnić kilka naturalnych miejsc na testy wydajnościowe:

  • CI (na każdy commit / merge request) – szybkie sanity performance testy: krótki ramp‑up, kilka minut stałego loadu na krytyczne endpointy. Celem jest wykrycie oczywistych regresji bez znaczącego wydłużania pipeline.
  • Pipeline nocne / okresowe – dłuższe testy obciążeniowe, czasem z elementami stresu lub krótkiego soak (np. 1–2 godziny), uruchamiane raz dziennie lub kilka razy w tygodniu. Służą do budowania historii wyników i wykrywania problemów, które nie wychodzą w 5‑minutowych testach.
  • Pre‑release / staging – rozbudowane testy obciążeniowe, fragmenty testów soak, scenariusze spike – zwykle na środowisku zbliżonym konfiguracją do produkcji. Celem jest ocena, czy dany release może trafić na produkcję bez naruszenia SLO.
  • Eksperymenty i testy skali – osobne pipeline’y lub zadania uruchamiane ręcznie, gdy trzeba odpowiedzieć na pytania o pojemność i skalowalność, zwykle poza standardowym flow CI/CD.

Największą pułapką jest próba „wsadzenia wszystkiego wszędzie”. W efekcie pipeline CI robi się wielogodzinny, a testy długotrwałe są powtarzane zbyt często, co i tak nie zwiększa wartości informacji. Lepiej jasno rozdzielić: krótki sanity load w CI, dłuższe testy nocne, a ciężkie scenariusze – na dedykowane pipeline’y.

Metryki: throughput, latency, percentyle i sygnały failowania pipeline

Bez zrozumienia podstawowych metryk trudno sensownie zdefiniować kryteria akceptacji wydajności. Najczęściej pojawiają się:

Kluczowe metryki i jak ich używać w pipeline

Same nazwy metryk niewiele dają, jeśli nie są powiązane z konkretnymi decyzjami w pipeline. Kilka podstawowych pojęć ma tu szczególne znaczenie:

  • Throughput (RPS/RPM) – liczba żądań na sekundę/minutę, które system obsługuje przy danych zasobach. Przydaje się głównie do porównań historycznych i oceny, czy test rzeczywiście „docisnął” system.
  • Latency – czas obsługi żądania. Średnia (mean) jest myląca, bo maskuje ogon rozkładu; więcej mówią percentyle.
  • Percentyle (p50, p90, p95, p99)p95 to czas, poniżej którego mieści się 95% żądań. Dla user experience zwykle kluczowe są p95/p99, bo to one odzwierciedlają „najgorsze, ale jeszcze typowe” przypadki.
  • Error rate – procent odpowiedzi z kodem 5xx, 4xx (zależnie od konwencji) lub błędnych odpowiedzi funkcjonalnych. Przydaje się jako twardy sygnał failowania.
  • Resource utilization – CPU, pamięć, IO, GC, długość kolejek. Te metryki zwykle nie są wprost sygnałem failowania pipeline, ale służą do diagnozy, gdy test nie spełnia kryteriów.

Schemat decyzji często wygląda podobnie: test generuje określony throughput, a pipeline porównuje kluczowe percentyle latencji i error rate z ustalonymi progami (budżet wydajnościowy). Jeżeli np. p95 dla endpointu /checkout przekroczy określoną wartość przy zadanym RPS, job jest oznaczany jako failed.

Pułapką jest traktowanie pojedynczego przebiegu jak absolutnej prawdy. Wyniki performance potrafią fluktuować z powodu szumów środowiskowych. Rozsądniej używać marginesów (np. „p95 nie gorsze niż o 10% od mediany z ostatniego tygodnia”) niż sztywnych wartości.

Definiowanie kryteriów sukcesu i porównań w czasie

Aby testy automatyczne były naprawdę użyteczne, potrzebują jasno zdefiniowanych kryteriów sukcesu. Najczęstsze podejścia:

  • Porównanie do twardych SLO/SLA – np. „p95 < 300 ms przy 200 RPS, error rate < 0,5%”. Dobre przy systemach o ustalonych kontraktach biznesowych, ale wymaga dojrzałego rozumienia aktualnych możliwości systemu.
  • Porównanie względne do poprzednich buildów – np. „p95 nie może być gorsze niż o 15% od mediany z ostatnich 10 przebiegów przy tym samym scenariuszu”. To bardziej elastyczne, bo akceptuje małe wahania i naturalne zmiany.
  • Porównanie do znanego baseline’u – np. z konkretnej „dobrej” wersji produkcyjnej. Bazowy build staje się punktem odniesienia dla przyszłych zmian.

Porównania względne są praktyczniejsze w CI/CD, ale wymagają sensownego gromadzenia historii wyników. Bez centralnego miejsca na metryki (TSDB, dedykowany storage) i narzędzia do wizualizacji porównania szybko zamieniają się w manualne „zrzuty raportów z JMetera do Excela”.

Niezależnie od wariantu, kryteria powinny być proste do zrozumienia dla zespołu. Jeśli po czerwonym buildzie trzeba uruchamiać zespół śledczy, żeby zrozumieć, co się stało, kryteria są zbyt skomplikowane albo źle dobrane.

Architektura procesu: jak wkomponować testy wydajnościowe w pipeline CI/CD

Rozdzielenie odpowiedzialności: kto za co odpowiada

Bez klarownego podziału ról performance w CI/CD szybko staje się „czyimś dodatkowym hobby”. Praktyczny schemat odpowiedzialności może wyglądać tak:

  • Zespół developerski – utrzymuje scenariusze testów wydajnościowych dla swoich usług, aktualizuje je przy zmianach API, reaguje na regresje wychwycone przez pipeline.
  • Platform/DevOps – dostarcza infrastrukturę do uruchamiania testów (workery, load generators, monitoring), integruje je z systemem CI/CD, pilnuje kosztów i bezpieczeństwa.
  • Specjaliści od wydajności (jeśli są) – projektują bardziej zaawansowane scenariusze, interpretują wyniki złożonych testów, proponują zmiany architektoniczne.
  • Product/Ownerzy usług – definiują priorytety scenariuszy (co jest krytyczne biznesowo), współuczestniczą w ustalaniu SLO.

Bez formalnego zakotwiczenia (np. w Definition of Done) testy wydajnościowe będą traktowane jako „miły dodatek”, a nie jako stały element pipeline’u. Z kolei przesunięcie całej odpowiedzialności do jednej osoby lub małego zespołu skończy się wąskim gardłem.

Oddzielenie środowiska testowego od produkcji

Teoretycznie najlepsze wyniki dają testy na produkcji. Praktyka: w większości organizacji regularne obciążanie produkcji generatorem loadu jest politycznie i technicznie nie do przełknięcia. Typowy kompromis:

  • Oddzielne środowisko perf/staging – konfiguracyjnie maksymalnie zbliżone do produkcji (rozmiar instancji, liczba replik, konfiguracja bazy, cache).
  • Limitowane integracje z systemami zewnętrznymi – zamiast prawdziwych partnerów (płatności, zewnętrzne API) używa się stubów lub environmentów testowych, żeby nie generować kosztów ani ryzyka.
  • Kontrolowane dane testowe – baza odwzorowuje strukturę i objętość danych produkcyjnych, ale bez danych wrażliwych.

Im dalej środowisko perf odbiega od produkcji, tym ostrożniej trzeba interpretować wyniki. Jeżeli staging ma połowę CPU albo inną wersję bazy, wartości bezwzględne tracą sens, a ważniejsze stają się relacje między buildami.

Strategie uruchamiania: kiedy i jak długo testować

Najczęściej spotykane warianty integracji w pipeline:

  • Sanity performance w CI – 2–10 minut testu na małym loadzie. Uruchamiany na merge requestach lub na gałęzi main. Szybko wykrywa regresje typu N+1 czy przypadkowy sleep w kodzie.
  • Rozszerzone testy przed releasem – 30–60 minut na stagingu. Zwykle zawierają ramp‑up, steady state i ramp‑down. Uruchamiane przed wypchnięciem release’u na produkcję.
  • Nocne/tygodniowe testy długotrwałe – 2–8 godzin i więcej. Pomagają wychwycić problemy kumulujące się w czasie.

Trzeba pilnować, by krótkie testy CI nie próbowały stać się „mini soak testami”. Dziesięciominutowy test nie „zobaczy” powolnego wycieku pamięci ani narastania fragmentacji. Jeśli próbować to wyciągać z takiego scenariusza, skończy się fałszywymi alarmami.

Izolacja i powtarzalność testów

Bez izolacji wyniki będą losowe. Kilka praktycznych zasad:

  • Dedykowane okno czasowe – przy dłuższych testach wyłącz inne intensywne joby na tym samym klastrze, żeby CPU nie było współdzielone z masowymi buildami.
  • Stała konfiguracja środowiska – automatyczne provisionowanie (Terraform/Helm) zamiast ręcznie „doklikanych” zmian, które nikt nie pamięta.
  • Reset stanu – jeżeli scenariusz jest wrażliwy na dane, baza lub kolejki powinny być doprowadzane do znanego stanu startowego przed każdym testem.
  • Stała konfiguracja testu – ten sam plan testowy, te same etapy ramp‑up/steady, spójne dane wejściowe (z możliwością kontrolowanej randomizacji).

Nawet przy dobrym poziomie izolacji trzeba liczyć się z pewnym szumem. Rozsądnym rozwiązaniem jest dodanie do pipeline’u odczytu trendów: zamiast patrzeć tylko na ostatni build, UI pokazuje wykres z ostatnich X testów, a sygnał failowania uwzględnia kierunek zmian, nie tylko pojedynczy pik.

Duży przemysłowy rurociąg biegnący przez zielony las
Źródło: Pexels | Autor: Wolfgang Weiser

Wybór narzędzi i stacku: od JMeter i Gatling po k6 i narzędzia chmurowe

Kryteria doboru narzędzia do automatyzacji

Lista narzędzi do testów wydajnościowych jest długa, ale wybór w praktyce sprowadza się do kilku kryteriów:

  • Model definiowania scenariuszy – GUI, DSL w kodzie (Scala, Kotlin, JavaScript), YAML. To decyduje, czy scenariusze będą utrzymywalne w repo obok kodu aplikacji.
  • Integracja z CI/CD – łatwość uruchamiania w trybie non‑GUI, dostępne obrazy Docker, wsparcie pluginów do Jenkins/GitLab/GitHub Actions.
  • Raportowanie i metryki – czy narzędzie samo generuje raporty HTML, czy łatwo eksportuje metryki (Prometheus, InfluxDB, JSON), jak prosto jest zautomatyzować analizę.
  • Obsługiwane protokoły – HTTP(S) to minimum, ale czasem potrzebne są WebSockety, gRPC, MQTT, testy baz danych, JMS.
  • Skalowanie loadu – czy narzędzie dobrze działa przy rozproszonym generowaniu ruchu, czy ma natywne wsparcie dla klastrów workers.
  • Krzywa uczenia i utrzymanie – czy zespół jest w stanie realnie to utrzymywać (język, tooling, społeczność).

Narzędzie, które „robi wszystko”, ale wymaga od zespołu nauki niszowego DSL‑a i własnoręcznego pisania integracji z CI, zwykle skończy jako eksperyment na jednym projekcie.

JMeter: klasyk z bogatym ekosystemem

Apache JMeter to jedno z najstarszych i najczęściej spotykanych narzędzi. Jego zalety i problemy w kontekście CI/CD są dość typowe:

  • Zalety:
    • bogate wsparcie protokołów i pluginów,
    • dojrzała społeczność, sporo materiałów,
    • łatwo dostępne obrazy Docker, integracje z InfluxDB, Grafaną i narzędziami raportowymi,
    • możliwość edycji scenariuszy w GUI, a uruchamiania w trybie non‑GUI w pipeline.
  • Wady:
    • scenariusze w formacie XML są trudne do code review i wersjonowania,
    • łatwo stworzyć zbyt skomplikowane „drzewka” elementów, które nikt poza autorem nie rozumie,
    • skalowanie na wiele maszyn wymaga dodatkowej orkiestracji.

Przy użyciu JMetera w CI/CD dobrze jest narzucić minimalne standardy: scenariusze w repo, unikanie przypadkowej edycji w GUI bez commitowania, konwencję nazewniczą samplerów i logicznych grup, a także skryptowe generowanie raportów jako artefaktów pipeline’u.

Gatling: DSL dla programistów

Gatling opiera się na DSL‑u w Scali (lub Javie/Kotlinie). Dobrze wpisuje się w zespoły, które traktują testy wydajnościowe jak kod:

  • Plusy:
    • scenariusze jako kod – łatwe do wersjonowania, code review, refaktoryzacji,
    • wysoka wydajność samego narzędzia, dobre radzenie sobie z dużym loadem,
    • rozsądne raporty HTML generowane po teście,
    • łatwa integracja z Maven/Gradle, więc też z większością systemów CI.
  • Minusy:
    • wymaga komfortu pracy z JVM i Scalą (co dla części zespołów jest barierą),
    • mniej „wyklikalny” dla testerów manualnych niż JMeter,
    • dla E2E z bogatym protokołem (np. nietypowe WebSockety) trzeba czasem dopisywać własne rozszerzenia.

W pipeline CI/CD Gatling często jest uruchamiany jako testy w projekcie JVM: komenda typu mvn gatling:test lub gradle gatlingRun w dedykowanym jobie. Raporty są archiwizowane jako artefakty, a kluczowe metryki można dodatkowo eksportować w JSON i analizować skryptami.

k6: testy jako kod w JavaScript

k6 zyskał popularność dzięki prostemu modelowi „testy jako kod w JS” i wygodnej integracji z nowoczesnymi pipeline’ami:

  • Zalety:
    • scenariusze w JavaScript – łatwe do ogarnięcia przez większość zespołów,
    • gotowe obrazy Docker, dobre działanie w trybie CLI,
    • wbudowane wsparcie dla eksportu metryk (Prometheus, InfluxDB, JSON),
    • czytelny model scenariusza (stages, thresholds) dobrze pasujący do automatycznego failowania buildów.
  • Ograniczenia:
    • skupienie na HTTP/gRPC – inne protokoły wymagają kombinowania,
    • brak „gotowego” bogatego GUI – trzeba wspierać się np. Grafaną,
    • dla bardzo złożonych scenariuszy trzeba uważać na utrzymanie codebase testów.

Narzędzia chmurowe i SaaS: kiedy lokalne load testy przestają wystarczać

Dla części projektów lokalne uruchamianie JMetera, Gatlinga czy k6 w CI wystarcza na długo. Przy rosnącej skali lub złożonej infrastrukturze pojawia się jednak ściana: ograniczenia sieciowe, brak mocy na runnerach CI, potrzeba symulacji ruchu z wielu regionów. Wtedy na stół wchodzą usługi chmurowe i SaaS.

Najczęściej spotykane kategorie to:

  • Platformy zarządzające scenariuszami open‑source – np. komercyjne fronty do JMetera, Gatlinga, k6. Scenariusze powstają w znanym narzędziu, a platforma ogarnia skalowanie, raporty, zarządzanie użytkownikami.
  • Natywne usługi chmurowe – rozwiązania dostawców IaaS/PaaS (np. AWS, Azure, GCP) integrujące się z ich monitoringiem, możliwością generowania ruchu z wielu regionów i wbudowanymi dashboardami.
  • Rozwiązania on‑premise/self‑hosted – klaster do testów perf zarządzany jak każda inna aplikacja (Kubernetes, helm chart), ale dedykowany tylko do generowania obciążenia.

Nie zawsze jest sens od razu skakać w SaaS. Typowy scenariusz to najpierw kilka miesięcy testów na prostych runnerach CI, a dopiero przy realnych problemach ze skalą – migracja do narzędzia chmurowego. Jeżeli 90% problemów z wydajnością wychodzi przy obciążeniu rzędu kilkudziesięciu wirtualnych użytkowników, wyrafinowany klaster load generatorów jest zbędny.

Pułapka przy SaaS‑ach: łatwo przyjąć ich domyślne dashboardy i metryki jako „prawdę objawioną”. Zdarza się, że domyślne raportowanie skupia się np. na średnim czasie odpowiedzi i 95. percentylu, a zespół w ogóle nie patrzy na tail (p99, p99.9), bo nie ma go w predefiniowanym widoku. To dobry moment, by krytycznie przejrzeć, czy metryki w UI faktycznie odpowiadają wymaganiom biznesowym.

Łączenie narzędzi: kiedy hybryda ma sens

Rzadko kończy się na jednym narzędziu na wszystkie możliwe potrzeby. Bardziel realne są konfiguracje hybrydowe:

  • k6/JMeter w CI + dedykowana platforma do testów dużej skali – drobne sanity w zwykłym pipeline, a cięższe kampanie na środowisku chmurowym.
  • JMeter do API + osobne narzędzie do testów przeglądarkowych – dla aplikacji, gdzie istotny jest również realny czas ładowania front‑endu i wpływ JS.
  • Narzędzie oparte na kodzie (Gatling/k6) + prostsze GUI dla mniej technicznych osób – scenariusze referencyjne w repo, ale możliwość ad‑hocowych „klikanych” testów.

Taki miks bywa rozsądny pod warunkiem, że rola każdego narzędzia jest jasno zdefiniowana. Chaos zaczyna się wtedy, gdy różne zespoły utrzymują kilka zupełnie niezależnych setów scenariuszy, z różnymi definicjami metryk i innymi progami. Trudno wtedy porównywać wyniki między środowiskami i release’ami.

Przemysłowe rurociągi w saudyjskiej fabryce z widocznymi detalami
Źródło: Pexels | Autor: Mumtaz Niazi

Projektowanie scenariuszy testów wydajnościowych z myślą o automatyzacji

Od wymagań niefunkcjonalnych do mierzalnych kryteriów

Testy wydajnościowe w CI/CD bez twardych kryteriów zamieniają się w dekorację pipeline’u. Pierwszy krok to przełożenie ogólnych wymagań (np. „aplikacja ma być szybka”) na coś, co da się automatycznie sprawdzić.

Kilka praktycznych typów kryteriów:

  • Opóźnienia – percentyle czasu odpowiedzi dla kluczowych endpointów (np. p95 < 300 ms dla /checkout przy X RPS).
  • Stabilność – brak istotnego wzrostu czasu odpowiedzi w trakcie testu steady state; brak trendu rosnącego zużycia pamięci/CPU.
  • Błędy – limit błędów 5xx/4xx, ewentualnie ograniczenie rodzajów błędów, które są akceptowalne (np. 429 przy throttlingu).
  • Przepustowość – minimalny poziom RPS przy zachowaniu ustalonych opóźnień i błędów poniżej progu.

To nie muszą być od razu docelowe wartości „produkcyjne”. W CI często stosuje się bardziej konserwatywne progi, które jedynie pilnują, by kolejny build nie był wyraźnie gorszy od poprzedniego. Docelowe SLA/SLO są wtedy weryfikowane w dłuższych testach na stagingu.

Wybór scenariuszy krytycznych zamiast „strzelania na oślep”

Typowy błąd przy starcie z automatyzacją to próba odwzorowania w testach perf całej aplikacji, od egzotycznych raportów po boczne ścieżki admina. Skończy się to rozproszonym wysiłkiem przy tworzeniu i utrzymaniu, a zyski będą niewielkie.

Zwykle bardziej sensowne jest wybranie kilku głównych przepływów:

  • scenariusze o największym wolumenie (np. logowanie, wyszukiwanie, lista produktów),
  • scenariusze najbardziej krytyczne biznesowo (np. checkout, przelew, finalizacja zamówienia),
  • scenariusze „podejrzane” – obszary, gdzie już historycznie bywały problemy.

Na tych przepływach buduje się „szkielet” testów automatycznych. Reszta może pozostać na poziomie manualnych lub ad‑hocowych testów wydajnościowych uruchamianych przy większych zmianach.

Model obciążenia: użytkownicy wirtualni, RPS i rozkłady

Narzędzia zwykle operują pojęciem wirtualnych użytkowników (VU) oraz liczby zapytań na sekundę (RPS). Bez sensownego modelu można wygenerować ruch, który kompletnie nie przypomina produkcji – wyniki będą wtedy co najwyżej orientacyjne.

Podstawowe pytania, które trzeba sobie zadać:

  • Jak wygląda obecny ruch produkcyjny? – średnia vs. szczyty, godziny największego obciążenia, typowe patterny (np. „flash sale”, raporty odpalane o pełnej godzinie).
  • Jak bardzo mieszają się scenariusze? – jaki procent requestów stanowi wyszukiwanie, ile to operacje modyfikujące (checkout, update), ile to zapytania tła (health checki, cron).
  • Jaki poziom obciążenia chcemy odtworzyć? – 1:1 do produkcji, 150% obecnego szczytu, a może konkretny plan rozwoju (np. podwojenie ruchu w rok).

Następnie zamienia się to na parametry scenariusza: liczba VU, długość ramp‑up, RPS na endpoint itp. W CI zwykle stosuje się skrojone wersje tych modeli: krótsze testy, mniej użytkowników, ale taki sam kształt rozkładu ruchu.

Praca z danymi testowymi: idempotencja, reset i „brudzenie” środowiska

Testy wydajnościowe, zwłaszcza automatyczne, potrafią w krótkim czasie „zalać” środowisko danymi: tysiącami użytkowników, zamówień czy dokumentów. Brak planu na dane kończy się niestabilnymi testami, wzrostem kosztów storage’u i przypadkowymi awariami jobów porządkowych.

Kilka podejść do opanowania danych:

  • Scenariusze idempotentne – operujące na tym samym zbiorze danych, nie tworzące nowych rekordów (np. logowanie do istniejących kont, odczyt istniejących produktów).
  • Dedykowana pula danych – z góry przygotowane konta, koszyki, dokumenty. Scenariusz losuje z puli, ale nie tworzy niczego trwałego albo czyści po sobie.
  • Reset środowiska – pełne przywrócenie bazy / storage’u do znanego stanu, np. przez snapshoty; sensowne przy krótkich, powtarzalnych testach sanity.

Idempotencja jest wygodna, ale nie zawsze realna. Przy scenariuszach typu „zakup” czy „rejestracja użytkownika” trzeba liczyć się z koniecznością cyklicznego sprzątania. Automatyzacja tego sprzątania (skrypty, joby w CI) bywa tak samo ważna jak sam plan testowy.

Parametryzacja i korelacja: jak uniknąć „syntetycznych” błędów

Statyczne scenariusze, które zawsze wysyłają te same payloady i idą po tej samej ścieżce, szybko przestają przypominać realny ruch. Z drugiej strony, nadmierna randomizacja utrudnia analizę i powtarzalność wyników.

Rozsądny kompromis to:

  • Parametryzacja z kontrolą – dane wejściowe (np. ID produktów, zakres dat) są losowane z ograniczonej, znanej puli. Można wtedy odtworzyć test, znając seed lub konkretny zestaw danych.
  • Korelacja dynamicznych wartości – tokeny sesji, ID zamówień, CSRF – zamiast wklejonych na sztywno wartości z pierwszego nagrania scenariusza. Brak korelacji jest prostą drogą do błędów 401/403/500, które nie mają nic wspólnego z realną wydajnością.
  • Wyraźne oddzielenie danych technicznych od logiki scenariusza – np. osobne pliki CSV/JSON, z których scenariusz czyta dane, zamiast mieszania wszystkiego w jednym skrypcie.

W automatyzacji CI/CD korelacja musi być bezobsługowa. Każdy ręczny krok typu „podmień token w pliku” oznacza, że testy w końcu zaczną być omijane przy braku czasu.

Progi (thresholds) i warunki „pass/fail” w pipeline

Sam fakt odpalenia testu wydajnościowego w CI niewiele daje, jeśli wynik nie wpływa na status pipeline’u. Mechanizm „pass/fail” bywa jednak drażliwy, bo szum pomiarów łatwo generuje fałszywe czerwone buildy.

Najczęściej stosowane podejścia to:

  • Progi absolutne – np. p95 < 400 ms przy min. 50 RPS, error rate < 1%. Dobre na początku, przejrzyste, ale czasem zbyt sztywne.
  • Porównanie względem poprzedniego builda – np. brak pogorszenia p95 o więcej niż 10% vs. mediany z ostatnich N testów. Ogranicza wpływ fluktuacji środowiska.
  • Strefy tolerancji – np. zielona (OK), żółta (regresja, ale poniżej progu; generuje tylko ostrzeżenie), czerwona (twardy fail pipeline’u). Pozwala wyłapać trend bez zatrzymywania każdej dostawy.

W praktyce początkowo progi są zbyt ambitne lub źle dopasowane i pipeline zaczyna „palić się na czerwono” przy każdym commitcie. Naturalna reakcja zespołu to ich ignorowanie – co zabija sens całego mechanizmu. Bez kilku iteracji kalibracji się nie obejdzie.

Oddzielanie testów sanity od kampanii obciążeniowych

Jeden z kluczowych elementów projektowania scenariuszy z myślą o CI/CD to jasny podział na szybkie testy sanity i cięższe kampanie obciążeniowe.

Dwa zestawy scenariuszy często rozwiązują 80% problemów:

  • Scenariusze sanity – krótki czas trwania (2–10 minut), umiarkowany load, nastawione na wczesne wykrycie regresji w kodzie lub konfiguracji (np. wolniejsze zapytania, brak cache, błędy 5xx).
  • Scenariusze kampanii – dłuższe (30+ minut), często w osobnym pipeline lub jako manualny trigger na stagingu; testują zachowanie przy docelowym obciążeniu i w horyzoncie dłuższym niż pojedynczy build.

Próba upchania wszystkiego w jeden scenariusz zwykle kończy się źle. Albo trwa za długo jak na CI, albo jest za krótki, żeby realnie powiedzieć coś o stabilności. Rozdzielenie ról scenariuszy ułatwia dyskusję: który test nie przeszedł, na jakim etapie cyklu i co z tego faktycznie wynika.

Modułowość scenariuszy i reużywalność w różnych pipeline’ach

Przy większych systemach scenariusze testów wydajnościowych rosną do kilkuset czy kilku tysięcy linii. Bez modularnego podejścia utrzymanie ich staje się podobnym problemem jak utrzymanie monolitycznego repo testów E2E UI.

Dobrym kierunkiem jest:

  • Wydzielenie „klocków” biznesowych – np. funkcje/komponenty login(), search(), addToCart(), checkout(), które można składać w różne scenariusze.
  • Konfiguracja scenariusza przez parametry – liczba VU, czasy faz, mix scenariuszy sterowane zmiennymi środowiskowymi lub plikami config; ten sam kod, różne profile loadu.
  • Wspólne biblioteki narzędziowe – obsługa auth, generowanie payloadów, logowanie – współdzielone między projektami, zamiast dublowania implementacji.

Takie podejście umożliwia użycie tej samej bazy scenariuszy w kilku miejscach: lekkie sanity w CI, cięższa wersja na stagingu, a czasem jeszcze tryb „really heavy” na odseparowanym perf env. Zmiana w jednym miejscu (np. nowa wersja API) propaguje się wszędzie.

Śledzenie regresji: od pojedynczego testu do trendów

Pojedynczy wynik testu wydajnościowego bywa mylący. Zmiana w infrastrukturze, inne obciążenie współdzielonego klastra, a nawet gorszy dzień konkretnego noda mogą przesunąć p95 o kilkadziesiąt milisekund.

Z tego powodu projektując scenariusze z myślą o CI, dobrze jest od razu pomyśleć o obserwacji w czasie:

Co warto zapamiętać

  • Jednorazowe testy obciążeniowe wykrywają tylko oczywiste problemy, natomiast automatyzacja w CI/CD pozwala wychwytywać drobne, kumulujące się regresje wydajności (np. pogorszenie p95/p99 o kilka procent) przy każdej zmianie.
  • Kluczową wartością nie jest sam fakt uruchamiania testów, ale ich porównywalność: stała definicja scenariuszy, powtarzalne warunki, wspólne miejsce na wyniki i możliwość prostego stwierdzenia, czy jest lepiej, gorzej czy tak samo.
  • Inwestycja w automatyzację ma sens głównie tam, gdzie koszt awarii wydajnościowej (utracone transakcje, SLA, wizerunek) jest wyższy niż koszt utrzymania pipeline’u – typowo w systemach o dużym lub zmiennym obciążeniu i w krytycznych API.
  • Dla małych, wewnętrznych aplikacji często wystarczy prosty sanity check kluczowych endpointów raz na release i solidny monitoring produkcji; pełny pipeline wydajnościowy bywa w takim scenariuszu przerostem formy nad treścią.
  • Automatyzacja rozwiązuje kilka typowych problemów organizacyjnych: jasno rozkłada odpowiedzialność (pipeline blokuje release), wymusza wersjonowanie scenariuszy jako kodu, zapewnia historię wyników i ujednolica język rozmowy o wydajności (konkretne metryki i progi zamiast „wydaje się wolno”).
  • Nawet najlepiej zautomatyzowane testy niewiele dadzą bez podstaw: profilowania i APM do znalezienia przyczyn, sensownej architektury bez oczywistych wąskich gardeł oraz porządnej obserwowalności (metryki, logi, trace’y z każdej warstwy).