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.

Odhadovaná délka studia: 55 minut · Stav: nehotovo

Technologie: CI/CD

Ú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

  1. Proč se v pipeline typicky spouští nejdřív lint a unit testy a až
    pak pomalejší integrační/E2E testy?
  2. Co znamená, že je test "flaky", a proč je to horší než mít test,
    který chybí úplně?
  3. K čemu slouží direktiva services v GitHub Actions a proč
    integrační test na databázi bez ní pravděpodobně selže?
  4. 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í.