
E2E testování bez bolesti: Časté problémy a naše řešení
·
20 min čtení
·
Martin Macura
Časté problémy a technické limitace
End-to-End (E2E) testování je relativně populární způsob testování aplikací. Automatizují chování uživatelů (navigaci, klikání myší, vyplňování dat, atd.) a ověřují správnost chování software. Často se ale trápí několika problémy, ne vždy se podaří vyřešit všechny a jejich řešení mohou také zvýraznit problémy jiné.
Rychlost - ****Ať už je to průběh samotného testu nebo jeho spuštění, E2E testy patří k těm nejpomalejším. Pro spuštění je potřeba kompilace a start celého serveru a jeho závislostí či služeb, průběh může často obsahovat delší interakci s aplikací (např. od registrace až po hlavní funkcionality), a délku průběhu může stejně jako běžného uživatele ovlivnit například rychlost načítání obsahu. Rychlost je důležitá zejména kvůli tomu, kde a kdy se spouští - chceme je spouštět v rámci CI/CD pipeline, a tedy při integraci každé změny do celkové aplikace. S rychlostí nám může pomoct například spouštění testů paralelně - tedy více testů najednou.
Izolace dat - Izolace se zaobírá právě ovlivňováním testů mezi sebou. V aplikaci se mi mohou objevovat data z předchozích testů (u testu emailového klienta by to mohly být například předchozí přijaté/odeslané emaily). Naše testy s těmito daty musí počítat (lze pokud je pořadí předem dané a navíc je pak velmi jednoduché změnou jednoho testu rozbít jiný), nebo mohou být více obecné (například při hledání odeslaného emailu projdou všechny stránky v seznamu emailů), nebo v ideálním případě se stejnými daty vůbec nepracují a ideálně začínají vždy s prázdným nebo připraveným obsahem. Pokud jsou testy však paralelní, první dvě řešení nemusí fungovat vůbec a to třetí vyžaduje trochu práce s infrastrukturou aplikace - například tvorbu nového serveru, databáze a jiných služeb při startu testu. A tím jsme zpět u rychlosti.
Každý projekt má také svoje specifické požadavky a limitace, které ovlivňují přípravu testovacího řešení - aplikace může být psaná v různých jazycích, testovací tým může být totožný s týmem vývojářů, aplikace může také obsahovat více (pod)služeb. V tomto článku se budeme věnovat požadavkům a našemu řešení v následujícím projektu:
Aplikace na React Routeru, psaná v TypeScriptu, s Postgres databází, prozatím žádné další služby
Testovací tým bude víceméně totožný s týmem vývojářů, lze tedy využít jejich znalosti aplikace. Hodí se také možnost použít stejný jazyk, přepoužít různé konstanty aplikace či známé knihovny.
Testy bychom rádi spouštěli paralelně a ideálně s čistými nebo připravenými daty
Naše řešení
Snažili jsme se řešení nijak nepřekomplikovat a případné nedostatky vyřešit, až nastanou. Pro psaní E2E testů jsme zvolili populární knihovnu Playwright, kde se kód píše také v TypeScriptu, testy píšeme přímo v repozitáři, takže nemusíme řešit ani složité nastavování pipeline, kde bychom při změně hlavního repozitáře museli volat kód z jiného.
Pro paralelní spouštění testů jsme zvolili tu nejjednodušší možnost vzhledem k možným řešením a knihovnám - spouštíme pro každý test nový server s databází PgLite v dočasné složce. Přestože PgLite není čistý Postgres, používá se velmi podobně (my ho máme schovaný za Drizzlem) a můžeme ho mít v paměti nebo v souborech. PgLite už v projektu také používáme, protože je super náhrada spouštění separátní Postgres databáze při vývoji aplikace. Přestože testy tím pádem nejsou nutně 1:1 k reálnému prostředí, je to pro nás prozatím přijatelný kompromis mezi jednoduchostí/rychlostí a “správností” testů.
Při spuštění serveru je potřeba mu předat testovací environment variables (proměnné prostředí), port mu pak nastavujeme podle WORKER_INDEX přístupného v rámci Playwright, který samotné paralelní spouštění řeší za nás.
Testy jsme si rozdělili na 2 části - příprava počátečních dat (funkce withData) a samotné spuštění serveru s testováním (withServer). Některé testy přípravu nepotřebují (například některé testy přihlášení), a fázi lze tedy přeskočit. Při využití obou fází je potřeba předávat informace o kontextu infrastruktury - prozatím jen složka databáze. Do callbacků taky posíláme různé utility, které souvisí s daty a infrastrukturou (například betterAuth instanci nebo funkci pro tvorbu adres pro navigaci dle nastaveného portu)
Nezapomínejme na vývojáře, aneb jak tvořit dobré API
API je zkratka pro Application Programming Interface, reprezentuje to možnosti, jak můžeme nějakou aplikaci ovládat. Naše zmíněné řešení je sice funkční, ale šlo by ho určitě navrhnout lépe. Při návrhu API je vždy důležité se zamyslet např. nad snadností použití zejména pro většinu případů použití, jak moc bude potřeba měnit kód při změně implementace či například nad tím, jak obtížné je s kódem vůbec začít - kde hledat pomoc/dokumentaci a v našem případě i třeba odkud brát různé utility. Naše zmíněné řešení není ideální ani z jednoho aspektu.
Hlavní problémy
Z příkladu je vidět, že v případě testů s přípravou dat je potřeba ke spuštění serveru předat i kontext infrastruktury - u nás prozatím složka databáze. V případě rozvinutí aplikace a přidání dalších služeb by bylo potřeba upravit testy, kde se kontext musí předávat. Lze tomu předejít tím, že kontext zabalíme do objektu. Také bychom ho mohli vždy explicitně získávat a předávat. Musíme se pak ale vypořádat i s lokací různých utilit, některé jsou obecné pro celý kontext (např. tvorba adres pro navigaci) a některé pouze pro práci s daty (přístup k databázi, auth knihovně).
Vidíme, že si kód spíše prodlužujeme, navíc je potřeba, aby vývojář dodržoval tuto strukturu, což se může zdát jako jednoduchý požadavek, ale je to vcelku zbytečné. Navíc syntakticky dovolujeme zavolat withServer uvnitř withData, což kvůli limitacím PgLite není možné.
Finální řešení
Namísto několika asynchronních funkcí, na které musíme vždy čekat, jsme zvolili zápis podobný návrhovému vzoru Builder - testy skládáme z jedné utility funkce, připravíme celý proces a čekáme jen jednou. Vývojář vždy píše jednu callback funkci a má k dispozici vše, co potřebuje. O sdílení kontextu se stará samotná utilita. Jelikož celý životní cyklus infrastruktury máme na jednom místě, mohli jsme tam přesunout správné odpojení od databáze, které jsme předtím řešili pomocí nové syntaxe using.
Shrnutí
Ukázali jsme si hlavní problémy End-to-End testování a možnosti jejich řešení. Ukázali jsme si, jak jsme to v Softeru vyřešili my a na co si u implementace dát pozor. S naším řešením jsme zatím spokojení a doufáme, že jste se E2E testů nezalekli a použijete je pro testování aplikací i Vy.