
Business & Integration IT konzultant
SafeTest je interný testovací framework využívaný spoločnosťou Netflix, ktorý spája rýchlosť unit testov so spoľahlivosťou integračného overovania. SafeTest vznikol ako odpoveď na limity tradičných end-to-end testov. V priebehu niekoľkých sekúnd dokáže overiť stovky scenárov bez potreby spúšťať databázy, servery či kontajnery. Prečítaj si viac o jeho princípoch, architektúre a o tom, čo z neho robí efektívny nástroj pre moderný vývoj v CI/CD prostredí.
V článku sa dozvieš:
Netflix denne nasadzuje nové verzie svojej aplikácie miliónom používateľov – bez výpadkov, bez chaosu. Ako to zvláda? Bez špičkovej technologickej kultúry by to nebolo možné. Jedným z kľúčových nástrojov je interný testovací framework Netflix SafeTest.
Tím Netflix funguje na princípoch „slobody a zodpovednosti“ a „silného zosúladenia pri voľnej previazanosti“. To znamená, že vývojári majú voľnosť vybrať si nástroje a technológie, ktoré im najviac vyhovujú, a zároveň nesú plnú zodpovednosť za to, že ich kód bude fungovať aj v extrémnych podmienkach. A to vrátane masívneho škálovania a aktualizácií v cykle kratšom než 24 hodín.
V takomto prostredí nie je priestor pre náhodu. Kvalita je extrémne dôležitá. Nestačí sa spoliehať len na klasické end-to-end testy postavené na frameworku Selenium, ktoré bežia desiatky minút a sú citlivé na výkyvy v infraštruktúre. Pri Netflixe vznikol tlak na rýchlejší, spoľahlivejší a predvídateľnejší spôsob testovania, ktorý viedol k vývoju interného frameworku s názvom SafeTest.
V období rokov 2017 – 2020 Netflix vnútorne experimentoval s viacerými prototypmi, ktoré mali jedno spoločné – snahu preniesť rýchlosť a jednoduchosť unit testov aj do testovania komplexnejších integračných scenárov. Spočiatku išlo o jednoduché JUnit wrappery, kde sa pomocou Mockito simulovalo správanie závislostí (dependencies). Neskôr sa testy začali spúšťať v úplne oddelenom prostredí v rámci Bazel buildov (nástroj od Googlu, ktorý zaručuje rovnaké výsledky bez ohľadu na to, kde sa spustí), aby sa zabezpečilo, že sa budú správať rovnako spoľahlivo a predvídateľne na každom počítači aj v CI/CD pipeline.
Koncom roka 2021 sa všetky tieto prototypy zlúčili do jedného projektu s pracovným názvom SafeTest. Cieľ bol celkom ambiciózny: vytvoriť testovací framework, v ktorom sa testy píšu rovnako pohodlne ako v JUnit, spúšťajú sa rovnako rýchlo ako klasické unit testy, ale zároveň overujú aj business logiku a reálne volania HTTP klientov. Dôležitou podmienkou bolo, že testy musia byť úplne deterministické (opakovateľné) v akomkoľvek prostredí, či už na vývojárskom laptope alebo v CI/CD pipeline. Výsledkom je nástroj, ktorý kombinuje rýchlosť, stabilitu a realistické overovanie správania aplikácie bez záťaže klasických end-to-end testov.
Ak pracuješ v podobne veľkom tíme, potrebuješ testovací framework, ktorý je hlavne rýchly, vie overiť viac než len jednu triedu a zároveň sa správa konzistentne. Nezáleží na tom, či ho spustíš raz alebo stokrát. A práve túto dieru medzi klasickým unit testovaním a náročnými end-to-end testovaním SafeTest výborne zaplnil.
SafeTest môže na prvý pohľad vyzerať ako obyčajná nadstavba nad frameworkom JUnit. V skutočnosti ide o úplne nový spôsob, ako písať testy v prostredí, kde appky pozostávajú z viacerých prepojených služieb. SafeTest ti umožní napísať test rovnako jednoducho, ako keby si používal obyčajný „assertEquals“ v JUnit, ale „pod povrchom“ sa toho deje omnoho viac.
SafeTest ti automaticky pripraví izolované prostredie, nasimuluje odpovede backendových služieb a po dokončení testu všetko dôsledne vyčistí. Každý scenár tak začína úplne nanovo, bez zvyškov po predchádzajúcich testoch. SafeTest tak spája rýchlosť unit testov s výhodou, že overuje aj spoluprácu viacerých komponentov naraz bez toho, aby si musel štartovať databázu, API server či ďalšie závislosti.
Tento framework nie je náhradou za JUnit či TestNG. Tie naďalej fungujú ako základ, cez ktorý sa testy spúšťajú. SafeTest sa zameriava na tú úroveň testovania, ktorá sa v praxi často zanedbáva. Ide o medzičlánok medzi čistým unit testom a plnohodnotným end-to-end testovaním. V Netflixe si uvedomili, že veľa chýb sa dá zachytiť práve na tejto strednej vrstve – v testoch, ktoré kontrolujú business logiku a správanie služby spolu s jej najbližšími závislosťami, no bez všetkých reálnych volaní do siete, databázy alebo vonkajších API. Preto SafeTest používa „falošné“ (fake) verzie produkčných komponentov – správajú sa ako skutočné, ale bežia len v pamäti a vracajú presne to, čo očakávaš.
Pri písaní testu v SafeTeste sa nemusíš ručne starať o mockovanie každej triedy. Jednoducho si v konfigurácii povieš, ktoré časti služby chceš ponechať ako sú, ktoré nahradiť a aké nastavenia použiť.
Je to trochu ako keď vo frameworku Spring použiješ „@TestConfiguration“, ale s oveľa vyššou úrovňou izolácie. Každý test beží vo vlastnom JVM prostredí, oddelenom „classloader“, takže si navzájom vôbec neprekážajú, ani keď ich spúšťaš paralelne. Vďaka tomu dosiahneš rovnakú rýchlosť na svojom zariadení aj v CI/CD prostredí, bez obáv z konfliktných stavov medzi testami.
SafeTest sa riadi 4 hlavnými zásadami – štyrmi „R“ (v angličtine):
Pri začiatkoch práce v SafeTest si všimneš, že framework nefunguje na klasickom princípe jedného centrálneho spúšťača testov. Namiesto toho pracuje s filozofiou, že každý test má mať „svoj vlastný malý svet“. Hneď ako zadáš „new TestEnvironment.Builder()“, SafeTest vytvorí úplne samostatné prostredie. Načíta si len tie knižnice, ktoré konkrétny test skutočne potrebuje, oddelí ich do vlastného „classloader-a“ a pomocou Dagger vygeneruje izolovaný graf závislostí (DI). Vďaka tomu sa vyhneš problémom s nečakaným zdieľaním objektov alebo stavov medzi testami – čo je celkom dôležité, ak by si testy chcel paralelizovať, napríklad na 40 vláknach súčasne.
Po zostavení testovacieho prostredia nastupuje komponent s názvom „LifecycleManager“, ktorý má na starosti spustenie, riadenie a ukončenie všetkých falošných (fake) služieb. Napríklad: ak test používa HTTP klienta, „LifecycleManager“ spustí vlastný falošný HTTP server, vyberie mu náhodný voľný port a automaticky ho zapíše do konfigurácie, ktorú používa tvoja služba. Čiže všetko beží hladko bez nadmerného manuálneho nastavovania. Ak si pridal napríklad FakeS3Module, vytvorí sa jednoduchý pamäťový klon S3, ktorý rozumie príkazom PUT a GET a funguje len na HashMap. Po skončení testu sa všetko zbalí a vyčistí – odstaví sa server, zavrie sa event loop a pamäť sa uvoľní. Tvoje ďalšie testy tak nezačínajú so zvyškovým stavom po tých predchádzajúcich.
Pri bežných testoch sa môže stať, že v jeden deň prejdú a ďalší deň zlyhajú len preto, že medzičasom niečo exspiruje. Instant.now() nahrádza SafeClock.now(), ktorý je zamrznutý na konkrétny dátum (napr. 1. január 2025). Ak chceš v teste posunúť čas, urobíš to priamo cez clock.advance(…). Podobne to funguje aj s náhodnými hodnotami – každý test dostane SecureRandom so zafixovaným „seedom“, takže všetky náhodne generované údaje (napr. hash alebo promo kód) budú vždy rovnaké, pokiaľ to výslovne nezmeníš.
Jedným z dôležitých princípov je aj tzv. „single entry, single exit“ – každý test musí mať jasne definované, s čím pracuje. Ak testuješ napríklad CatalogService, SafeTest ti povolí použiť len jeho závislosti – nevpustí ti do testu náhodné komponenty z iných častí appky. Framework týmto prístupom predchádza tomu, aby si omylom testoval niečo, čo si vlastne nechcel.
Z hľadiska paralelizácie je SafeTest veľmi dobre pripravený. Nepoužíva žiadne globálne porty, zdieľané „ObjectMappers“ ani jednoinštančné objekty, ktoré by si testy navzájom prepisovali. Každý test má vlastný HTTP server, vlastnú Jackson inštanciu (na serializáciu JSON) a vlastný DI graf. To znamená, že aj pri spustení 30 či 40 testov naraz nemáš problémy s kolíziami.
Za zmienku stojí aj spôsob, akým pristupuje ku konfigurácii. Namiesto obyčajných „key-value“ dvojíc používa typovo bezpečné API – teda napríklad „ConfigKeys.FEATURE_FLAG_X“ namiesto reťazca „feature_flag_x“. Ak test potrebuje nejakú hodnotu a ty ju nezadáš, builder ti test ani nespustí – čím predídeš ťažko odhaliteľným chybám, ktoré vznikajú, keď sa systém pokúsi použiť predvolenú hodnotu namiesto tej správnej.
Celkovo je architektúra SafeTest postavená tak, aby bol každý test:
Pre veľké a komplexné systémy, kde sa bežne rieši paralelizácia, výpadky a závislosti medzi službami, sú toto vlastnosti, ktoré výrazne znižujú náklady na testovanie.
Príklad: chceš otestovať, že metóda CatalogService.getTitles() vráti prázdny zoznam, keď HTTP klient dostane 404.
@Test
public void shouldReturnEmptyOn404() {
TestEnvironment env = new TestEnvironment.Builder()
.withModule(new FakeHttpModule()
.stubGet("/catalog", 404, "{}"))
.build();
CatalogService service = env.inject(CatalogService.class);
List<Title> titles = service.getTitles();
assertTrue(titles.isEmpty());
}
V pozadí SafeTest vytvorí izolovanú JVM, spustí Dagger graf, zaregistruje falošný (fake) server a po teste všetko vyčistí.
Interné tímy v rámci Netflixu využívajú SafeTest najčastejšie pri testovaní mikroservisov, ktoré zabezpečujú personalizáciu obsahu a výber streamu pre jednotlivých používateľov. V praxi to funguje tak, že pri každom novom pull requeste sa spustí Bazel build, v ktorom bežia stovky SafeTest scenárov – a to všetko do minúty. Ak niektorý test zlyhá, CI/CD pipeline automaticky zastaví nasadzovanie a tester dostane okamžité upozornenie cez Slack aj s detailným reportom, vďaka čomu vie hneď reagovať a opraviť chybu ešte predtým, než sa kód dostane ďalej do procesu.
Netflix SafeTest má tri dominantné „use-cases“:
Napríklad keď „recommendation engine“ kombinuje dáta z rôznych zdrojov – ako sú história vyhľadávania, rebríčky a trendy. Následne si potrebuješ overiť, že ak všetky vstupy sú prázdne, systém správne prepne na záložný scenár a vráti odporúčania typu „Top 10 dnes“. V SafeTest si jednoducho nastavíš tri falošné služby, ktoré vrátia prázdne zoznamy, spustíš test a v podstate hneď máš overený výstup.
Teda knižníc, ktoré komunikujú s inými službami cez HTTP. Všetky interné Java knižnice, ktoré volajú externé API (napríklad metadáta), majú svoj SafeTest „plugin“, kde sa pomocou „stub“ servera – nastaveného podľa OpenAPI špecifikácie – simuluje odpoveď. Test potom overí, či sa dáta správne mapujú do objektov a celá komunikácia prebehne bez reálneho sieťového volania.
Netflix vo veľkej miere používa tzv. „feature flags“ cez systém Archaius, ktoré umožňujú zapínať a vypínať funkcie v reálnom čase. V teste si tak simuluješ, že určitý flag je zapnutý a kontroluješ, či služba v tomto režime nehlási chyby do logu a vracia správny tvar JSON odpovede. Takto vieš bezpečne otestovať správanie appky pri rôznych kombináciách nastavení bez toho, aby si musel manipulovať s konfiguráciou.
V porovnaní s inými nástrojmi má SafeTest niekoľko výhod.
Veľký rozdiel je aj v konfigurácii. Zatiaľ čo v JUnit alebo TestNG odovzdávaš hodnoty do testu ako parametre alebo systémové premenné, v SafeTeste používaš vlastný builder, kde každá hodnota patrí do štruktúrovaného stromu a musí byť previazaná s grafom závislostí. Ak nešpecifikuješ, akú verziu komponentu chceš (produkčnú alebo falošnú), test sa ani nespustí. Tým ťa ochráni pred chybami, ktoré sa beže odhaľujú až pri zlyhaní testu.
Netflix pri internom meraní zistil, že testovanie mikroslužby so zhruba 500 SafeTest scenármi prebehne na MacBook Pro s čipom M2 do 30 sekúnd. Ak by boli rovnaké scenáre napísané ako end-to-end testy napríklad v Playwright, zabrali by viac než 10 minút a vyžadovali aj štyri kontajnery navyše. Výkon SafeTestu spočíva v tom, že všetko beží v rámci jedného JVM procesu – aj falošný HTTP server je vlastne len rýchly handler, ktorý beží v pamäti.
SafeTest je nástroj, ktorý spája jednoduchosť unit testov s istotou integračných testov. Beží rýchlo, vždy rovnako a dovolí ti zachytiť chyby skôr, než sa dostanú do „staging“ fázy. Medzi jeho výhody patria teda aj:
Ak sa rozhodneš pre framework Safetest, určite ťa zaujme hlavne jeho rýchlosť. Scenáre, ktoré by ti v nástroji Playwright alebo Selenium trvali niekoľko minút, SafeTest zvládne otestovať v priebehu sekúnd. Dôvod je jednoduchý – nemusíš štartovať žiadny backend, databázu ani cache server. Všetko beží v rámci jednej JVM a odpovede z falošných komponentov prichádzajú v podstate okamžite. Táto rýchlosť sa pekne premietne do nižších finančných nákladov na infraštruktúru a najmä do toho, že ako tester budeš mať spätnú väzbu prakticky okamžite.
SafeTest je od začiatku navrhnutý tak, aby odstránil náhodné faktory, ktoré spôsobujú tzv. flaky testy – teda také, ktoré raz prejdú, raz nie. Každý falošný (fake) server má svoj vlastný pridelený port, takže sa testy navzájom nebijú. Výsledok? Keď test prejde dnes, prejde aj zajtra. A ak nie, vieš, že problém naozaj spôsobil zmenený kód, nie náhoda.
Ďalšia vec, ktorú oceníš najmä pri hľadaní chýb. Po každom teste vznikne tzv. snapshot – jeden súbor, ktorý obsahuje všetky dôležité informácie: aké závislosti boli použité, aké odpovede vrátili falošné (fake) služby, aký bol seed generátora náhodných hodnôt. Ak kolega z druhého tímu napíše, že mu nejaký test padá, stačí ti poslať ten snapshot. Ty si ho pustíš lokálne, dáš breakpoint priamo do miesta chyby a môžeš začať debugging.
Každý test má svoje vlastné prostredie, takže viacej testerov môžu spúšťať veľké množstvo scenárov naraz bez toho, aby si navzájom niečo prepisovali.
Saftest má, prirodzene, aj zopár limitov. Prvým z nich je, že je naviazaný na JVM (Java Virtual Machine). Ak napríklad vyvíjaš Node.js službu, SafeTest ti priamo nepomôže. Musel by si si vytvoriť vlastnú prepojovaciu vrstvu, čo už môže byť zložité a celkom náročné na údržbu.
Ďalším obmedzením je to, že SafeTest predpokladá, že tvoja appka používa Dagger a „dependency injection“. Ak máš staršiu aplikáciu postavenú na frameworku Spring bez čistého DI, tak budeš musieť refaktorovať kód, aby sa dal použiť v izolovanom testovacom prostredí.
Netflix už naznačil, že plánuje uvoľniť SafeTest ako open-source projekt, ale zatiaľ nie je jasné, kedy presne sa tak stane ani pod akou licenciou. Ak by framework vyšiel pod licenciou Apache 2.0, získal by si prístup k rovnakému nástroju, ktorý denne používajú interne v Netflixe. Hovorí sa aj o ďalších rozšíreniach – napríklad o podpore pre Kotlin, či dokonca o špeciálnom balíku pre Android, ktorý by mohol nahradiť Robolectric, ale s lepšie kontrolovaným a izolovaným prostredím.
Otázne zostáva, čo presne Netflix zverejní – či len jadro SafeTestu alebo aj balík predpripravených fake serverov a pomocných modulov, ktoré používajú interne.
Ďalšou zaujímavou témou je napojenie na OpenTelemetry. Do budúcna sa uvažuje, že každý SafeTest by mohol automaticky generovať trace – teda záznam o priebehu testu, latencii a ďalších metrikách, ktoré by pomohli lepšie sledovať výkon a odhaľovať slabšie miesta.
Diskutuje sa aj o tom, ako by mohla vyzerať komunita okolo projektu. Nie je ešte rozhodnuté, či bude vývoj riadený len Netflixom, alebo vznikne nezávislá technická rada, do ktorej sa budú môcť zapojiť aj vývojári z iných firiem. Táto otázka bude dôležitá najmä z pohľadu dlhodobej udržateľnosti a otvorenosti projektu.
V Netflixe tento framework už dnes dohliada na kvalitu pri stovkách nasadení denne. Ak máš o SafeTest záujem, prinesie ti výhody, ako extrémne rýchlu spätnú väzbu, jednoduchosť a ľahké hľadanie chýb. Určite sa oplatí sledovať Netflix Tech Blog, pretože akonáhle sa SafeTest stane open-source frameworkom, budeš pripravený naplno využiť jeho potenciál a vyskúšať ho aj na svojich projetoch.
Zdroje:
• https://medium.com/androiddevelopers/netflix-app-testing-at-scale-eb4ef6b40124
• https://testguild.com/netflix-safetest/
• https://netflixtechblog.com/
Súvisiace články