====== 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: * minimalizovat náklady na testování, * maximalizovat rychlost zpětné vazby, * zvýšit spolehlivost testovací sady, * a podpořit rychlý vývoj s vysokou kvalitou. ===== 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 ==== * **Co testují**: Izolované jednotky kódu (např. funkce, metody, třídy). * **Rychlost**: Milisekundy – spouští se běžně stovky až tisíce za sekundu. * **Náklady na údržbu**: Nízké. * **Stabilita**: Vysoká – selhávají jen při změně logiky. * **Doporučený podíl**: **70 % a více** všech automatizovaných testů. > 💡 **Dobrý jednotkový test** je rychlý, izolovaný, deterministický a snadno čitelný. ==== 2. Integrační testy (Integration Tests) – střední vrstva ==== * **Co testují**: Interakce mezi více komponentami nebo systémy (např. volání databáze, API, služby třetích stran). * **Rychlost**: Desítky milisekund až sekundy. * **Náklady na údržbu**: Střední – závisí na složitosti integrací. * **Stabilita**: Střední – mohou selhat kvůli externím závislostem. * **Doporučený podíl**: **20–25 %** testů. > 💡 Integrační testy ověřují, že části systému „spolu hrají“ správně. ==== 3. End-to-end (E2E) testy – vrchol pyramidy ==== * **Co testují**: Kompletní uživatelské scénáře napříč celou aplikací (např. „uživatel se přihlásí, přidá zboží do košíku a zaplatí“). * **Rychlost**: Sekundy až minuty. * **Náklady na údržbu**: Vysoké – citlivé na změny UI. * **Stabilita**: Nízká – často selhávají kvůli dočasným problémům (časování, síť). * **Doporučený podíl**: **5–10 %** testů. > 💡 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: * **Vertikální vrstvy** (jednotkové → integrační → E2E), * **Horizontální dimenzi** (testování uvnitř služby vs. mezi službami). | Ú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? ===== * **Rychlá zpětná vazba**: Jednotkové testy selžou během sekund, nikoli minut. * **Nižší náklady**: Údržba 1000 jednotkových testů je levnější než 10 E2E testů. * **Lepší laditelnost**: Když selže jednotkový test, víte přesně, kde je chyba. * **Vyšší pokrytí**: Lze snadno pokrýt okrajové případy (edge cases). * **Bezpečný refaktoring**: Spolehlivá síť jednotkových testů umožňuje bez obav upravovat kód. ===== Běžné antipatterny ===== ==== Testovací kužel (Ice-Cream Cone) ==== * Příliš mnoho manuálních nebo E2E testů, * Nedostatek jednotkových testů, * Důsledek: Pomalé buildy, nestabilní testy, nízká důvěra v automatizaci. ==== Testovací kobliha (Testing Cupcake) ==== * Tým má jen E2E testy a žádné jednotkové/integrační, * Vývoj je pomalý, chyby se hledají hodiny. ==== Přehnaná automatizace UI ==== * Každý požadavek se testuje E2E testem, * Vede k „testovací dlužné pasti“ – testy jsou dražší než kód samotný. ===== 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 ===== * [[TDD]] * [[BDD]] * [[CI/CD]] * [[Testování softwaru]] * [[Jednotkový test]] * [[End-to-end testování]] * [[Testovací dluh]] * [[Mikroslužby]] ===== Externí odkazy ===== * Martin Fowler – Test Pyramid: https://martinfowler.com/bliki/TestPyramid.html * Mike Cohn – Original article: https://mountaingoatsoftware.com/blog/the-forgotten-layer-of-the-test-automation-pyramid * Google Testing Blog – Just Say No to More End-to-End Tests: https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html * Pact.io – Contract testing for microservices: https://pact.io ===== Viz také ===== * [[Testovací strategie]] * [[Testovací coverage]] * [[QA v agilním týmu]] * [[Živá dokumentace]]