Full stack testing: Objav dôležitosť fullstack testovania v IT

Full stack testing ti umožňuje otestovať celú infraštruktúru aplikácie naraz – od používateľského rozhrania až po úložisko dát. Prečo sa oplatí zaradiť tento prístup, ktoré nástroje zvoliť a ako si nastaviť postupy tak, aby testovanie prinieslo požadované výsledky? V nasledujúcich riadkoch sme pre teba spísali najdôležitejšie tipy.

Full stack testing: Objav dôležitosť fullstack testovania v IT
Full stack testing: Objav dôležitosť fullstack testovania v IT

V článku sa dozvieš:

    Pri vývoji aplikácie každé kliknutie používateľa spúšťa sériu akcií, ktoré sa šíria cez frontend, API brány, rôzne mikroservisy až po databázu. V takomto podelenom systéme už nestačí len overiť, či tlačidlo vyzerá správne, alebo či funkcia vráti správny výsledok. Potrebuješ si byť istý, že celý reťazec komponentov spolu bez problémov komunikuje a vytvára funkčný, zrozumiteľný a stabilný používateľský zážitok (UX). Presne na tieto účely slúži full stack testing.

    Ako funguje full-stack testovanie?

    Full-stack testing spája viacero typov testovania – od unit testov cez integračné a kontraktové testy až po end-to-end scenáre.

    Testovanie v reálnom prostredí

    Všetky vrstvy sú súčasťou jednej CI/CD pipeline. Vďaka tomu testuješ aplikáciu v prostredí, ktoré sa čo najviac podobá na reálne produkčné prostredie.

    Komplexné (paralelné) testovanie

    Nástroje na testovanie softvéru ako Cypress ti umožňujú paralelne testovať komponenty aj správanie v prehliadači, pričom výsledky vidíš v jasne štruktúrovaných reportoch. Playwright dokáže automaticky vypnúť celý beh, ak sa odhalí nejaký „flaky“ test – teda test, ktorý niekedy prejde a inokedy nie. Takto ťa prinúti vyriešiť problém okamžite.

    Tento prístup je už nevyhnutnosťou. 85 % veľkých firiem používa architektúru postavenú na mikroservisoch a mnohé z nich považuje ich integráciu za minimálne „dostatočne úspešnú“. Ale „dostatočne“ veľakrát nestačí. Potrebuješ testy, ktoré dokážu overiť celý priebeh.

    Prečo testovať celý reťazec?

    Ak testuješ iba frontend, nezistíš, že backend poslal zle formátovaný dátový typ. Ak sa sústredíš len na backend, môžeš prehliadnuť, že chyba sa v UI nezachytí a používateľ zostane bez spätnej väzby.

    Testovaním full-stack tak môžeš prejsť celým procesom:

    • simulovaný používateľ vloží produkt do košíka,
    • frontend cez „intercept“ overí, či API vrátilo správny JSON,
    • backendový test zase overí uloženie objednávky
    • databázový test v PostgreSQL (napr. cez Testcontainers) potvrdí jej existenciu.

    V prostredí CI/CD sa všetky tieto testy dajú paralelizovať a vďaka využitiu cache kontajnerov nezaberú hodiny, ale len pár minút. Tým sa skracuje čas medzi commitom a spätnou väzbou.

    Full stack tester takto dokáže včas zachytiť chyby, ktoré by pri samostatných testoch frontendovej alebo backendovej vrstvy unikli. Tým výrazne znižuje riziko, že sa chyba prejaví až u koncového používateľa.

    No mikroservisy nie sú jedinou výzvou. Postupný rast mobilných appiek znamená, že to isté rozhranie aplikácie musíš testovať aj na iOS a Android frontende. Nové verzie sa nasadzujú prakticky po každom zlúčení, testy musia byť plne automatizované, spoľahlivé a rýchle. Fullstack testing teda už nemá byť len výhodou, ale štandardom.

    Typy full-stack testovania

    Typy full-stack testovania

    V prvom kroku si riadne premysli, čo má každý typ testu overiť a prečo ho vôbec píšeš (v kontexte full-stack testovania). Pozri si základnú pyramídu typov testov s ich funkciami:

    1. Unit testy

    Úplne najnižšie v tejto pyramíde sú unit testy. Zameriavajú sa na jednotlivé funkcie alebo triedy a testujú ich izolovane, bez prepojenia na databázu, API alebo iné systémy. Zaujíma ťa len to, či konkrétny vstup vráti očakávaný výstup. Sú extrémne rýchle a práve preto sú vhodné na priebežné spúšťanie počas vývoja. Vieš ich napojiť na ukladanie súboru alebo IDE a hneď vidíš, či si niečo nerozbil v základnej logike aplikácie.

    2. Integračné testy

    O úroveň vyššie sú integračné testy. Tu už spájaš dva alebo viac reálnych komponentov dokopy – napríklad službu, ktorá zapisuje objednávku. Nezaujíma ťa už len čistý výstup funkcie, ale to, či spolupracuje s ostatnými časťami systému.

    Overuješ, či SQL migrácie sú v poriadku, či tabuľky majú správne typy a či kód komunikuje s databázou tak, ako má. Tieto testy trvajú síce o niečo dlhšie, ale pokrývajú chyby, ktoré by si v unit testoch nenašiel. Môže ísť napríklad o zle nastavený index, chýbajúce pole alebo nekompatibilitu knižníc.

    3. Kontraktové testy (contract tests)

    Ak máš v appke zavedené REST API, nevyhneš sa tzv. „kontrakt“ (contract) testom. Tieto testy overujú, že dohoda medzi dvoma systémami – napríklad backendom a frontendom zostáva v platnosti. Ak frontend očakáva, že pole „price“ bude typu „number“, tak backend nemôže zrazu poslať string.

    Používajú sa nástroje ako Pact, ktoré vytvoria JSON „zmluvu“ medzi poskytovateľom a konzumentom API a porovnávajú očakávaný výstup s reálnym. Ak niečo nesedí, test spadne a vydanie sa stopne ešte predtým, než by sa chyba dostala k používateľovi.

    4. End-to-end testy (E2E)

    End-to-end testy predstavujú najvyššiu vrstvu. Tu už testuješ aplikáciu tak, ako ju používa koncový užívateľ – cez prehliadač alebo mobilný emulátor. Simuluješ reálny tok: prihlásenie, pridanie do košíka, platbu, zápis do databázy, atď… Tieto testy sú najvernejšie realite, ale zároveň najpomalšie a najcitlivejšie na tzv. „flaky“ správanie. Používaš ich iba na overenie najdôležitejších oblastí, napríklad raz za deň alebo pred nasadením do produkcie.

    5. Smoke testy

    A nakoniec sú tu smoke testy – jednoduché, rýchle kontroly hneď po nasadení. Overíš, že API odpovedá na /health alebo či aplikácia nabehne a nehodí chybu hneď na začiatku. Ak smoke test neprejde, release sa rovno vracia späť – nemusíš čakať, kým padnú komplikované testy, pretože vieš, že niečo základné už zlyhalo.

    Príklad

    Najprv označíš triedu ako konzument frontend a poskytovateľa catalog-service. Metóda katalogProdukt opisuje „zmluvu“. Ak klient vyšle GET /products/42, očakáva HTTP 200 a JSON s číselným id, textovým „name“ a číselným „price“. Pri behu sa spustí lokálny mock server na porte 8080, ktorý vracia práve tento JSON. Ak backend zmení typ poľa „price“ na string alebo vypustí kľúč, verifyInteraction vyhodí chybu a deployment sa zastaví ešte skôr, než by frontend dostal nekompatibilné API.

    // Contract test s Pact v JUnit 5: konzument definuje očakávaný JSON a provider sa overí v pipeline
    
    @Consumer("frontend")
    @Provider("catalog-service")
    @PactTestFor(port = "8080")
    class CatalogContractTest {
    
        @Pact
        RequestResponsePact katalogProdukt(PactDslWithProvider builder) {
            return builder
                .given("Produkt 42 existuje")
                .uponReceiving("GET /products/42")
                .path("/products/42").method("GET")
                .willRespondWith()
                .status(200)
                .body(new PactDslJsonBody()
                    .numberValue("id", 42)
                    .stringType("name", "Notebook")
                    .numberType("price", 999.99))
                .toPact();
        }
    
        @Test
        void preverSchemas(PactVerificationContext ctx) {
            HttpResponse<String> rsp = Unirest.get("http://localhost:8080/products/42").asString();
            ctx.verifyInteraction(); // ak provider vráti inú schému, test spadne
            Assertions.assertEquals(200, rsp.getStatus());
        }
    }

    Full-stack testovanie v praxi

    Z pohľadu frameworkov sa v súčasnosti najčastejšie využívajú nástroje ako Cypress, Playwright a Appium.

    • Cypress poskytuje intuitívne API a vizualizáciu v reálnom čase pri vykonávaní testov, čo značne uľahčuje debugging. Jeho architektonický prístup, kde testy bežia priamo v prehliadači, prináša vyššiu spoľahlivosť oproti tradičným nástrojom.
    • Playwright od Microsoftu ponúka multiplatformovú podporu (Chrome, Firefox, Safari) s jedným API, vhodné aj pre pokročilé scenáre.
    • Pre mobilné aplikácie je Appium štandardom, ktorý umožňuje automatizované testovanie natívnych, hybridných aj webových aplikácií na Android a iOS platformách.

    V praxi rozlišujeme tri roviny testovania, s ktorými sa môže full stack tester stretnúť: frontend, backend + API a databázy. Každá z nich používa iné nástroje a má iný cieľ, no spolu tvoria celok, ktorý tvorí full-stack prístup k testovaniu.

    Frontend: web, mobil, desktop

    Pri testovaní používateľského rozhrania (UI) ide hlavne o to, aby si mal istotu, že dôležité súčasti appky, ako napríklad registrácia, nákupný košík alebo platba, fungujú bez problémov. Najrýchlejšie sa tieto testy píšu pomocou nástrojov ako Playwright alebo Cypress, pretože sa spúšťajú priamo v skutočnom prehliadači, automaticky zachytia sieťové volania a vedia počkať, kým sa stránka naozaj načíta a ustáli.

    Dôležité však je, aby si sa v týchto testoch nezasekol na zbytočných detailoch. Nemá zmysel testovať každý pixel alebo farbu ikony. Ak to začneš robiť, celá pipeline sa ti spomalí a testy budú náchylné na chyby pri každej drobnej vizuálnej zmene. Zameraj sa radšej na to, čo robí používateľ: zadáva e-mail, vypĺňa formuláre, atď… Vizuálne zmeny, ktoré neovplyvňujú správanie, rieš cez tzv. vizuálnu regresiu.

    Na to existujú šikovné nástroje ako Percy alebo trace mode v nástroji Playwright, ktoré urobia screenshoty pred zmenou a po zmene, porovnajú ich a automaticky vyhodnotia, či je rozdiel viditeľný. Ak je rozdiel menší než je nastavený prah, test prejde. Nemusíš tak ručne kontrolovať, či sa nadpis neposunul o dva pixely – systém to spraví za teba.

    Backend a API

    Hneď za používateľským rozhraním sa nachádza API vrstva. Práve tu začínaš s písaním unit testov pre biznis logiku, a to ideálne v JUnit, PyTeste alebo Mocha, podľa toho, v akom jazyku pracuješ. Testuješ napríklad výpočty DPH, overovanie platnosti zľavových kódov, agregáciu rôznych metrík – skrátka všetko, čo musí prebehnúť rýchlo, bez potreby zapojenia databázy. A keď to máš pokryté, prichádzajú na rad testy samotných REST alebo GraphQL endpointov. V prostredí Java ti dobre poslúži RestAssured, s ktorým spustíš Spring Boot na náhodne vygenerovanom porte, odošleš POST na /orders a overíš, že odpoveď nesie stavový kód 201 a telo (body) odpovede sedí s očakávanou JSON štruktúrou..

    Keďže mikroservisy veľmi často komunikujú s externými systémami ako sú platobné brány alebo poskytovatelia e-mailových služieb, musíš tieto závislosti počas testovania izolovať. Na to dobre poslúžia nástroje ako WireMock či MockServer, ktoré ti nahradia reálne API „falošným“ (fake) serverom. Ten potom vracia predvídateľné, vopred definované odpovede podľa requestu, čím dosiahneš, že testy zostanú rýchle a nezávislé od externých sietí či služieb.

    Databázy a perzistentné vrstvy

    S každou novou verziou služby prichádzajú aj SQL migrácie, ktoré je potrebné bezpečne nasadiť. Aby si sa vyhol chybám – napríklad zlému indexu alebo nekompatibilnej zmene – odporúčame ti spúšťať migrácie pomocou Flyway alebo Liquibase už počas integračného testu a to priamo proti novému Postgres kontajneru vytvorenému pomocou Testcontainers. Tieto nástroje zabezpečia, že všetky skripty sa vykonajú v správnom poradí a databázová schéma zostane konzistentná a nepoškodená.

    Ak máš na projekte ORM ako Hibernate či TypeORM, testovanie pokračuje ďalej – preveruješ správnosť samotných dopytov. Na to sa ti hodí in-memory databáza, napríklad H2 alebo SQLite, ktorú naplníš seed dátami a následne cez „repository“ metódy overíš, že výsledky sedia. Po skončení testu buď zrušíš transakciu rollbackom, alebo úplne zničíš databázový kontajner, aby každý ďalší test bežal v čistom prostredí. Takto eliminuješ riziko, že predošlé dáta narušia aktuálny test, čo by mohlo viesť k už spomínaným „flaky“ testom.

    Príklad

    Test spustí kontajner s Postgres databázou pomocou anotácie @Container, pričom štart a vypnutie kontajnera zabezpečuje Testcontainers automaticky. V metóde init() si nadviažeš DataSource, spustíš Flyway migrácie a vytvoríš implementáciu OrderRepository. V samotnom scenári následne uložíš objednávku, načítaš ju späť a porovnáš hodnotu total. Tým overíš, že:

    1. migrácie správne vytvorili tabuľku aj stĺpce so správnymi typmi
    2. JDBC vrstva správne mapuje BigDecimal hodnoty
    3. business logika metód save a findById funguje aj v plnohodnotnom prostredí.
    // Java integration test, ktorý kombinuje backend a databázu v Testcontainers
    @Testcontainers
    class OrderRepositoryIT {
    
        @Container
        static PostgreSQLContainer<?> db =
            new PostgreSQLContainer<>("postgres:16")
                .withDatabaseName("shop")
                .withUsername("test")
                .withPassword("secret");
    
        private static HikariDataSource datasource;
        private static OrderRepository repo;
    
        @BeforeAll
        static void init() {
            datasource = new HikariDataSource();
            datasource.setJdbcUrl(db.getJdbcUrl());
            datasource.setUsername(db.getUsername());
            datasource.setPassword(db.getPassword());
    
            // spustí Flyway migrácie proti čerstvej databáze
            Flyway.configure().dataSource(datasource).load().migrate();
    
            repo = new JdbcOrderRepository(datasource);
        }
    
        @Test
        void ulozenieObjednavky() {
            Order o = new Order(null, 42L, BigDecimal.valueOf(99.9));
            Long id = repo.save(o);
            Order fromDb = repo.findById(id).orElseThrow();
            assertEquals(BigDecimal.valueOf(99.9), fromDb.total());
        }
    }

    Celý test zaberie len pár sekúnd, beží kompletne izolovane v kontajneri a zachytí chybu ešte predtým, než by mohla zasiahnuť produkciu pri prvej reálnej objednávke.

    Výzvy a vybrané osvedčené postupy

    Full-stack testovanie prináša skvelé pokrytie, no zároveň aj nové výzvy. Jednou z najčastejších je nestabilita testov a ich nespoľahlivé správanie.

    Nestabilita testov a flaky správanie

    Ak ti počet full-stack testov narastie z pár desiatok na stovky, tak ich najväčším problémom sa stáva nestabilita. „Flaky“ testy raz prejdú, inokedy spadnú a ty postupne ale iste neveríš ani testovaciemu reportu. Predísť tomu môžeš písaním tzv. idempotentných testov, ktoré sa nikdy nesmú spoliehať na systémový stav ani na aktuálny systémový čas. V UI testoch preto vždy čakáš na konkrétnu podmienku, ako napríklad await page.locator(…).isVisible(), namiesto pevnej pauzy sleep(2). Na API úrovni zas používaš deterministický seed pri generovaní náhodných hodnôt a v databázovej vrstve transakciu, ktorú po teste navrátiš do predchádzajúceho stavu.

    Testovateľnosť a závislosti

    Aby bol kód testovateľný, musíš ho písať s ohľadom na možnosť injekcie závislostí. Ak „service“ priamo v konštruktore vytvára new HttpClient(), v integračnom teste túto závislosť jednoducho nenahradíš. Preto je lepšie používať DI kontajnery a rozhrania. Vtedy môžeš počas testu vložiť stub, ktorý vracia fixné hodnoty bez potreby volať externé služby. Testy by mali byť modulárne: každý scenár by mal mať svoj vlastný fixture.

    Ak napríklad test A mení objednávku, test B potrebuje vlastné, izolované dáta. Seedovanie riešiš pomocou Factories – namiesto ručného písania SQL vytváraš entitu cez OrderFactory.paid(), ktorá automaticky vyplní všetky potrebné stĺpce.

    Optimalizácia testovacej pipeline

    Udržať rozumnú rovnováhu medzi pokrytím testami a rýchlosťou pipeline je vždy do určitej miery kompromis. Pomôže ti caching závislostí, paralelizácia jednotlivých „jobov“ a pravidlo „najnákladnejší scenár spúšťame len raz denne“. Ak ti napríklad Playwright test trvá osem minút, má zmysel ho spúšťať  len na vetve „main“, zatiaľ čo unit a contract testy bežia pri každom „pushi“.

    Čitateľnosť a debugging

    Testy by si mal písať rovnako čitateľne ako aj produkčný kód. Každý scenár by mal mať jasne rozdelené bloky „Arrange-Act-Assert“, pomenované page objekty a stručný popis účelu. Ak test padne, výstup (napr. screenshot) musí jasne ukázať, čo sa vlastne pokazilo. V tomto ti pomôžu nástroje ako ReportPortal alebo Allure, ktoré k reportu pridajú screenshoty, logy s network trafficom a videozáznam z testu. Debugging bude následne otázkou len pár minút.

    15 min.Bugzilla je najpoužívanejší bug tracker pre testerov zadarmo

    Bugzilla je najpoužívanejší bug tracker pre testerov zdarma

    Bugzilla je výkonný open source bug tracker - nástroj na sledovanie chýb, ktorý široko používajú testeri a vývojári softvéru. Objav jeho funkcie a výhody.

    Príklad

    Scenár využíva Playwright s nastavením „retries: 2“, takže ak zlyhá kvôli sieťovej odchýlke, automaticky sa spustí znova. Žiaden krok pritom nestojí na pevne nastavenom waitForTimeout. Namiesto toho test aktívne čaká na zobrazenie toast správy a následne overuje URL po presmerovaní. Ak sa toast nezobrazí v rámci predvoleného timeoutu, test padne s jasnou chybou „element not visible“. Takto vieš znížiť mieru „flaky“ správania.

    // Playwright test s retry a explicitným čakaním – stabilné UI bez flaky
    test.describe.configure({ retries: 2 });
    
    test('používateľ dokončí nákup', async ({ page }) => {
      await page.goto('https://shop.localhost');
      await page.locator('text=Notebook').click();
      await page.locator('button', { hasText: 'Pridať do košíka' }).click();
    
      // čakáš na toast, nie na pevnú pauzu
      await expect(page.locator('.toast-success')).toHaveText(/pridaný/);
    
      await page.locator('a[href="/cart"]').click();
      await page.locator('button', { hasText: 'Zaplatiť' }).click();
    
      // explicitne čakáš na presmerovanie
      await page.waitForURL(/\/order\/\d+\/done/);
      await expect(page.locator('h1')).toHaveText('Ďakujeme za nákup');
    });

    FAQ – Často kladené otázky o full stack testovaní

    Je full-stack testing vhodný aj pre menšie IT projekty?

    Áno, ale s mierou. Pri malých projektoch často stačí kombinácia unit a integračných testov. Full-stack testing má najväčší prínos v zložitých systémoch s viacerými službami, repozitármi a závislosťami, kde každá zmena môže mať širší dopad.

    Ako často mám spúšťať fullstack testy?

    Závisí od náročnosti. Drahšie E2E scenáre stačí spúšťať na vetve „main“ alebo pri nasadzovaní. Unit a integračné testy môžu bežať pri každom „pushi“ alebo merge requeste. Kľúčom je nájsť rovnováhu medzi spätnou väzbou a rýchlosťou pipeline.

    Aký je rozdiel medzi integračným a kontraktovým testom?

    Integračný test overuje reálnu spoluprácu komponentov (napr. volanie databázy), zatiaľ čo kontraktový test overuje, že API medzi dvoma službami dodržiava dohodnutý formát – napríklad typy hodnôt v JSON odpovedi.

    Aké nástroje sú najlepšie na začiatok s fullstack testingom?

    Pre frontend: Playwright alebo Cypress.
    Pre backend: JUnit, PyTest, Mocha, RestAssured.
    Pre kontrakty: Pact.
    Pre databázy: Testcontainers, Flyway, Liquibase.
    Začni s tým, čo už používate v tíme – nech je vstupná bariéra čo najnižšia.

    Čo robiť, keď mám flaky testy, ale neviem ich stabilizovať?

    Prvým krokom je identifikovať dôvod – často ide o nečakané oneskorenia, závislosti od systémového času alebo dát. Pomôcť ti môže trace mode (napr. v Playwright), detailné logy a vizualizácia testu. Skús použiť „await“namiesto pevných timeoutov a izoluj dáta v databáze pre každý test.

    Odporúčania pre úspešné full-stack testovanie

    Full-stack testovanie si zvoľ vtedy, keď tvoja appka má viac ako jeden repozitár a zlyhanie v jedinom module môže narušiť celý systém. Nezabudni, odporúčame najskôr začať s unit testami, pridaj integračné testy s využitím Testcontainers a až nakoniec rieš pomalšie UI testy. Sleduj odporúčania z ThoughtWorks či komunitu Testing-Gym, kde nájdeš mnoho updatov a testerov, ktorí sa zaujímajú o full-stack testovanie.

    Zdroje:

    O autorovi

    Michaela Kojnoková

    Agile Test Engineer

    Po štúdiu informatiky na ŽU a TUKE som sa najviac ponorila do oblasti automatizácie testovania. Okrem toho sa venujem tvorbe webov, databázam, dátovej analytike, umelej inteligencii a strojovému učeniu. Mám rada cestovanie, šport a najviac si užívam čas strávený v prírode s mojimi blízkymi. LinkedIn

    Daj nám o sebe vedieť