Modul 9 — CI/CD
44. Automatizované testování v pipeline
Jaké typy automatických testů patří do CI/CD pipeline, jak je organizovat podle testovací pyramidy a jak reportovat pokrytí a výsledky.
Úvod a kontext
Pipeline bez testů automatizuje jen build a nasazení rozbitého kódu o
něco rychleji. Skutečnou hodnotu CI/CD přináší až spolu s testy, které
dokážou samy odhalit regresi dřív, než se dostane k uživatelům. Tato
lekce se věnuje tomu, jaké druhy testů do pipeline patří, v jakém
pořadí je spouštět a jak z výsledků udělat srozumitelnou zpětnou vazbu
pro tým.
Teorie
Testovací pyramida
Testovací pyramida popisuje doporučený poměr různých typů testů podle
jejich rychlosti a nákladů na údržbu:
▲
/ \ E2E testy (málo, pomalé, křehké)
/---\
/ \ Integrační testy (víc, středně rychlé)
/-------\
/ \ Unit testy (nejvíc, rychlé, levné)
/-----------\
- Unit testy — testují jednu funkci/třídu izolovaně (bez databáze,
bez sítě). Jsou extrémně rychlé (tisíce za sekundu) a tvoří základ
pyramidy — jich by mělo být nejvíc. - Integrační testy — ověřují spolupráci víc komponent (např.
aplikace + skutečná databáze v kontejneru). Pomalejší, ale odhalí
chyby, které unit testy izolovaně nevidí (např. špatný SQL dotaz). - End-to-end (E2E) testy — simulují reálného uživatele procházejícího
celou aplikací (typicky přes prohlížeč). Nejpomalejší, nejkřehčí
(citlivé na drobné změny UI), proto by jich mělo být nejméně —
pokrývají jen klíčové uživatelské scénáře.
Pipeline, která má hodně pomalých E2E testů a málo rychlých unit testů
("obrácená pyramida"), je pomalá a testy se často "flaky" (nespolehlivě
padají bez skutečné chyby v kódu) — cílem je udržet poměr blízký
pyramidě.
Kde v pipeline testy spouštět
Testy se řadí od nejrychlejších/nejlevnějších k nejpomalejším/
nejdražším, aby pipeline selhala co nejdřív a nejlevněji:
lint (sekundy) → unit testy (sekundy až desítky sekund)
→ integrační testy (minuty) → E2E testy (minuty až desítky minut)
Pokud lint selže, nemá smysl čekat na dlouhé E2E testy — pipeline by
se měla zastavit hned po prvním selhání (--maxfail=1 u pytest, nebo
prostě default chování většiny CI nástrojů, kde neúspěšný krok
zastaví job).
Testovací report a pokrytí kódu
Kromě prostého "prošlo/neprošlo" je užitečné mít strukturovaný výstup:
- name: Testy s pokrytím
run: pytest --cov=myapp --cov-report=xml --junitxml=report.xml
- name: Nahrání reportu pokrytí
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml
--cov-report=xml vytvoří strojově čitelný report pokrytí kódu testy
(kolik procent řádků bylo testy skutečně provedeno), který lze nahrát
do nástrojů jako Codecov nebo zobrazit přímo v pull requestu. Pokrytí
samo o sobě negarantuje kvalitu testů (lze mít 100 % pokrytí a přesto
netestovat správné chování), ale nízké pokrytí spolehlivě ukazuje na
netestovaný kód.
Testování v izolovaném prostředí s závislostmi
Integrační testy často potřebují reálnou databázi. CI runnery umožňují
spustit pomocné služby jako kontejnery vedle testovacího jobu:
jobs:
integration-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: testpass
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Instalace závislostí
run: pip install -r requirements.txt
- name: Integrační testy
env:
DATABASE_URL: postgresql://postgres:testpass@localhost:5432/postgres
run: pytest tests/integration/
Direktiva services spustí PostgreSQL kontejner na pozadí a čeká, než
projde health check (pg_isready), než spustí samotné testy — bez
tohoto čekání by testy mohly selhat kvůli ještě nenastartované
databázi.
Selhávající (flaky) testy
Test, který občas projde a občas selže bez skutečné změny kódu, je
"flaky" — obvykle kvůli závislosti na časování, síti nebo sdíleném
stavu mezi testy. Flaky testy jsou nebezpečnější než chybějící testy,
protože podkopávají důvěru týmu v CI ("test zase jen tak spadl, spusť
to znovu") — je lepší je opravit nebo dočasně vyřadit a sledovat, než
je ignorovat.
Praktický příklad
Kompletní testovací fáze kombinující lint, unit a integrační testy s
postupným selháváním:
name: Testy
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install flake8
- run: flake8 .
unit-tests:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt
- run: pytest tests/unit/ --cov=myapp --cov-report=term
integration-tests:
needs: unit-tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: testpass
ports: ["5432:5432"]
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt
- env:
DATABASE_URL: postgresql://postgres:testpass@localhost:5432/postgres
run: pytest tests/integration/
Díky needs proběhne lint první (nejrychlejší kontrola), pak unit
testy, a teprve nakonec pomalejší integrační testy s databází — pokud
cokoliv dřívějšího selže, zbytek se vůbec nespustí a šetří se čas
runneru.
Shrnutí
Testovací pyramida doporučuje mít nejvíc rychlých unit testů, méně
integračních a nejméně pomalých/křehkých E2E testů. V pipeline se
testy řadí od nejrychlejších po nejpomalejší, aby selhání bylo zjištěno
co nejdřív a nejlevněji. CI runnery umí spouštět pomocné závislosti
(např. databázi) jako doprovodné kontejnery pro integrační testy.
Report pokrytí kódu ukazuje, které části kódu testy vůbec nezasahují,
a flaky testy je potřeba aktivně řešit, jinak podkopávají důvěru v celý
CI proces.
Kontrolní otázky
- Proč se v pipeline typicky spouští nejdřív lint a unit testy a až
pak pomalejší integrační/E2E testy? - Co znamená, že je test "flaky", a proč je to horší než mít test,
který chybí úplně? - K čemu slouží direktiva
servicesv GitHub Actions a proč
integrační test na databázi bez ní pravděpodobně selže? - Mini cvičení: k existujícímu projektu s unit testy přidejte krok
generující report pokrytí (pytest --cov) a zjistěte, které
moduly mají pokrytí pod 50 % — napište, jaké testy by bylo potřeba
doplnit.
Lekce na sebe nejsou zamčené — libovolnou lekci můžete otevřít i označit jako hotovou v jakémkoliv pořadí.