Obsah

Testovací pyramida

Testovací pyramida je koncept v softwarovém inženýrství, který vizualizuje doporučené poměry a hierarchii různých typů testů v softwarovém projektu. Tento model poprvé formuloval Mike Cohn ve své knize *Succeeding with Agile* (2009) a od té doby se stal základním principem pro budování udržitelné a efektivní testovací strategie.

Cílem testovací pyramidy je:

Struktura testovací pyramidy

Pyramida se skládá ze tří hlavních vrstev, uspořádaných od nejrychlejších a nejlevnějších testů (spodek) po nejpomalejší a nejnákladnější (vrchol):

1. Jednotkové testy (Unit Tests) – základna pyramidy

💡 Dobrý jednotkový test je rychlý, izolovaný, deterministický a snadno čitelný.

2. Integrační testy (Integration Tests) – střední vrstva

💡 Integrační testy ověřují, že části systému „spolu hrají“ správně.

3. End-to-end (E2E) testy – vrchol pyramidy

💡 E2E testy simulují skutečného uživatele, ale nejsou náhradou za jednotkové testy!

Vizuální znázornění pyramidy

          ┌───────────────────┐
          │  End-to-End testy │ ← málo, pomalé, drahé
          └───────────────────┘
        ┌───────────────────────┐
        │   Integrační testy    │ ← střední množství
        └───────────────────────┘
┌───────────────────────────────────┐
│        Jednotkové testy           │ ← hodně, rychlé, levné
└───────────────────────────────────┘

Konkrétní příklady testů

Jednotkový test v Pythonu (s pytest)

Soubor: `calculator.py`

def divide(a, b):
    if b == 0:
        raise ValueError("Dělení nulou není povoleno")
    return a / b

Soubor: `test_calculator.py`

import pytest
from calculator import divide
 
def test_divide_positive_numbers():
    assert divide(10, 2) == 5.0
 
def test_divide_by_zero_raises_error():
    with pytest.raises(ValueError, match="Dělení nulou není povoleno"):
        divide(5, 0)
✅ Rychlé, izolované, bez závislostí – ideální jednotkový test.

Jednotkový test v Javě (s JUnit 5)

Soubor: `BankAccount.java`

public class BankAccount {
    private double balance;
 
    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }
 
    public void withdraw(double amount) {
        if (amount > balance) {
            throw new IllegalArgumentException("Nedostatek prostředků");
        }
        balance -= amount;
    }
 
    public double getBalance() {
        return balance;
    }
}

Soubor: `BankAccountTest.java`

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
 
class BankAccountTest {
 
    @Test
    void withdraw_ValidAmount_ReducesBalance() {
        BankAccount account = new BankAccount(100.0);
        account.withdraw(30.0);
        assertEquals(70.0, account.getBalance());
    }
 
    @Test
    void withdraw_InsufficientFunds_ThrowsException() {
        BankAccount account = new BankAccount(50.0);
        assertThrows(IllegalArgumentException.class, () -> account.withdraw(60.0));
    }
}

Integrační test v Pythonu (s databází pomocí Testcontainers)

import pytest
from sqlalchemy import create_engine, text
from myapp.models import User
 
@pytest.fixture(scope="module")
def db_engine():
    # Spustí Docker kontejner s PostgreSQL (přes testcontainers)
    with PostgresContainer("postgres:15") as postgres:
        engine = create_engine(postgres.get_connection_url())
        yield engine
 
def test_user_persistence(db_engine):
    # Vložíme uživatele do DB
    with db_engine.connect() as conn:
        conn.execute(text("INSERT INTO users (name) VALUES ('Alice')"))
        result = conn.execute(text("SELECT name FROM users WHERE name = 'Alice'"))
        assert result.fetchone()[0] == "Alice"

E2E test v Javě (s Playwright)

import com.microsoft.playwright.*;
import org.junit.jupiter.api.*;
 
public class LoginE2ETest {
    static Playwright playwright;
    static Browser browser;
 
    @BeforeAll
    static void launchBrowser() {
        playwright = Playwright.create();
        browser = playwright.chromium().launch();
    }
 
    @Test
    void successfulLogin() {
        Page page = browser.newPage();
        page.navigate("https://mojeaplikace.cz/login");
        page.fill("#username", "admin");
        page.fill("#password", "heslo123");
        page.click("button[type='submit']");
        assertTrue(page.url().contains("/dashboard"));
        assertTrue(page.textContent("h1").contains("Vítejte"));
    }
 
    @AfterAll
    static void closeBrowser() {
        playwright.close();
    }
}

Testovací matice pro mikroslužby

V architektuře mikroslužeb se klasická pyramida rozšiřuje o testovací matici, která zohledňuje:

Úroveň testování Uvnitř služby Mezi službami (síť)
Jednotkové testy Test logiky bez závislostí
Integrační testy Test s databází, cache, externí API Kontraktové testy (Pact), Consumer-Driven Contracts
E2E / Špičkové testy Kompletní workflow přes více služeb (např. objednávka → platba → doručení)
🔑 Klíčový princip: Nejdříve otestujte logiku uvnitř služby, pak ověřte smlouvy mezi službami, a až nakonec spusťte nákladné E2E testy.

Tento přístup minimalizuje závislost na „živém“ prostředí a zvyšuje izolaci testů.

Proč dodržovat testovací pyramidu?

Běžné antipatterny

Testovací kužel (Ice-Cream Cone)

Testovací kobliha (Testing Cupcake)

Přehnaná automatizace UI

Nástroje podle vrstvy

Vrstva Příklady nástrojů
——–——————-
Jednotkové testy JUnit, pytest, NUnit, Jest, Mocha
Integrační testy Testcontainers, Pact, Postman, REST Assured
E2E testy Cypress, Playwright, Selenium, WebdriverIO
BDD testy Cucumber, SpecFlow, Behave (často nad E2E nebo integrační vrstvou)

Související pojmy

Externí odkazy

Viz také