Hlavní navigace

Co to znamená „pořádně“ otestovat webovou aplikaci?

Pavel Herout

První z minisérie článků popisujících zkušenosti získané při testování webové aplikace pomocí automatických funkcionálních testů. Seznámíte se s termíny z oblasti testování nutnými pro porozumění dalším dílům.

Doba čtení: 11 minut

Poděkování

Děkuji kolegům z výzkumné skupiny ReliSA Ing. Štěpánu Caisovi a Ing. Richardu Lipkovi za pečlivou revizi textu a cenné praktické poznámky a náměty k němu.

Koho mohou tyto články zajímat

  1. Testery vyvíjející automatizované testy – budou uvedeny zkušenosti a dobré rady.
  2. Vedoucí projektů – testů je potřeba skutečně hodně, mnohem více, než by se zdálo, tj. testování trvá déle a stojí více peněz. Ale získáme za to uspokojivou míru jistoty, která je navíc opakovatelná již jen za cenu strojového času.
  3. Programátory webových aplikací – jak je možné testerům pomoci (minimálně soucitem).

Motivace

„Testy jsou drahé a pracné“, „testování zabírá až 50 % času“, „GUI testy jsou křehké“ atp. Určitě jste se s těmito výroky a mnoha jim podobnými již setkali. Souhlasím s nimi, ale bylo by vhodné je nějak kvantifikovat. Dal jsem si tedy za úkol otestovat webovou aplikaci tolika automatizovanými funkcionálními testy, kolik jich jen budu schopen smysluplně napsat. Abych na závěr mohl říci: „Otestoval jsem opravdu všechno, a pokud tam nějaká chyba zůstala, tak musí být tedy sakra dobře ukrytá.“

Vědomě jsem se nedržel dobrých zásad shrnutých v pojmu testovací pyramida), protože jsem prostě chtěl zkusit pokrýt celou aplikaci automatickými funkcionálními testy. Testy byly psány v Javě a využívají Selenium WebDriver a framework JUnit 4.

Poznámka:
Předem se omlouvám všem čtenářům, kteří dále popisované a vysvětlované věci již dávno znají. Pro ty ostatní ale může být pro pochopení dalších dílů tohoto miniseriálu užitečné, když vysvětlím, co je míněno dále používanými pojmy. Budu se snažit držet se definic zavedených v:

  • ISTQB (International Software Testing Qualifications Board), což je organizace certifikující SW testery
  • CaSTB (Czech and Slovak Testing Board) – národní organizace, která je součástí ISTQB

Teď už k problematice testování

Důležitost testování už, doufejme, nikdo nezpochybňuje. Pokud by to přesto někdo dělal, může k tomu zneužít známý výrok E.W.Dijkstry: „Testování programu je velmi efektivní způsob, jak ukázat přítomnost defektů, ale je bohužel nedostatečné k prokázání jejich nepřítomnosti.“

Když se s takovýmto názorem setkáte, můžete úspěšně polemizovat tím, že když už tedy v programu nechcete hledat chyby (protože „Naši programátoři chyby nedělají.“) pak je tedy nutné program (systém, aplikaci) alespoň verifikovat, tj. zjistit, zda vyhovuje specifikaci. No a verifikaci lze provést verifikačními postupy či akcemi (tj. prakticky testy), jejichž hlavním cílem je prokázání shody se specifikací. A vedlejším účinkem je pak nalezení defektů. U testování je to tak, že hlavním cílem je nalezení defektů a vedlejším účinkem je stav, kdy nenalezení defektů zvyšuje důvěru ve správnost programu.

Po tomto malém rétorickém cvičení, ze kterého by mělo vyplynout, že ať se na to díváme z jakéhokoliv pohledu, je nutné provádět testy, je možné pustit se do dalšího popisu.

Testy lze kategorizovat mnohými způsoby. Jedním ze způsobů je dělení na manuální, poloautomatické a automatické. U manuálních testů nejčastěji někdo (senior tester) napíše testovací scénář a někdo další, často méně kvalifikovaný (junior tester) podle něj opakovaně testy provádí a vyhodnocuje. Pokud je tato činnost prováděna opakovaně, lze použít nějaký softwarový nástroj, který práci částečně zautomatizuje.

V případě testování webové aplikace může být tímto nástrojem např. Selenium IDE, které dokáže zaznamenat (nahrát) sled prováděných činností, případně k ještě tomuto nahranému skriptu dodatečně přidat i nějaká (většinou jednoduchá) vyhodnocení správnosti výsledků. Automatické testy jsou často plnohodnotné programy, které jsou schopny i sofistikovaných činností včetně netriviálních vyhodnocení správnosti. Navíc s případným (samozřejmým) bonusem agregování výsledků běhu všech testů.

Nelze zavrhovat ani jeden typ testů, každý má své opodstatnění. Zde se ale budeme dále zabývat jen automatickými testy.

Pro testování jsem si vybral webovou aplikaci, pro což jsem měl několik dobrých důvodů. Těchto aplikací v současné době vzniká asi nejvíce. Poměrně dobře se pro ně píší funkcionální testy. Posledním důvodem je, že jsem měl již zkušenosti s testováním webových aplikací z dřívějška. A zde je třeba vysvětlit další pojmy.

Je dobré podotknout, že definice pojmů není v oblasti testování vždy jednotná a terminologie se může různě překrývat a lišit v závislosti na pohledu. Například funkcionální testy produktu A mohou být v kontextu produktu B integrační a přitom se může jednat o testy napsané s pomocí testovací knihovny pro jednotkové testy.

Z časového hlediska vývoje aplikace se nejdříve píší tzv. jednotkové (unit) testy. Ty píší samotní programátoři a testují jimi svůj vlastní kód. Pak přichází na řadu integrační testy a po nich systémové testy. V systémových testech se zkoumá funkcionalita testovaného systému, proto se pro ně občas používá i název funkcionální. Píše je obvykle někdo jiný, než vývojáři testované aplikace. A samozřejmě, že k tomu má své pomůcky. V oblasti webových aplikací je jedním z nejznámějších frameworků Selenium WebDriver. Lze jej získat z docs.seleniumhq.org a z tohoto „ekosystému“ pochází i již dříve zmíněný Selenium IDE.

Selenium WebDriver dává programátorovi vyvíjejícímu testy (v několika jazycích – Java, C#, Ruby, Python, Javascript) k dispozici knihovny, jimiž lze programově ovládat reálný webový prohlížeč. Jsou podporovány nejznámější prohlížeče (já jsem používal Chrome, Opera a Firefox). Aby si knihovny Selenia „rozuměly“ s příslušným webovým prohlížečem, je nutné mít aktuální driver pro každý konkrétní prohlížeč. Drivery jsou připravovány třetími stranami (tedy ne vývojáři Selenia) a u známých prohlížečů velmi rychle reagují na jejich vývoj.

Selenium WebDriver umožňuje ovládat i „nereálný“ prohlížeč, např. HtmlUnit nebo PhantomJS. Tento typ prohlížečů se nazývá headless browser a na rozdíl od reálného prohlížeče nic nezobrazuje. Běžným uživatelům tedy není k ničemu, ovšem při automatizovaném testování má tu výhodu, že je rychlejší než reálný prohlížeč, který se „zdržuje“ minimálně renderováním. Nevýhoda těchto typů prohlížečů je v tom, že neumí vše, co reálné prohlížeče, takže při testování složitějších webových stránek selhávají. Ovšem např. Chrome lze spustit v headless módu, bohužel ale jeho rychlost v tomto módu není významně vyšší.

Jaké aktivity lze tedy díky Selenium WebDriver programově v prohlížeči vykonávat? V podstatě všechny, které běžný uživatel provádí, tj. přechody na různá URL, klikání na prvky, vyplňování vstupních polí, výběry z výběrových seznamů atd. Navíc samozřejmě umí získávat ze zobrazené webové stránky informace, které se pak dají ověřovat. Jednotlivé elementy na webové stránce umí adresovat několika různými způsoby, z nichž nejdůležitější je pomocí ID těchto elementů. Ale zvládá např. i XPath dotazy/adresaci.

Pokud se pomocí Selenia píší rozsáhlé sady testů, lze využít návrhového vzoru Page Object Model, pomocí něhož se vytváří Object Repository pro jednotlivé elementy. Já jsem tento postup nepoužil, protože jsem měl v době psaní testů s POM jen velmi malé zkušenosti. Nahradil jsem jej vlastním balíkem knihovních tříd, což mi připadalo jako jednodušší řešení. Využití POM na stejné aplikaci je ale zajímavá výzva, do které se pravděpodobně pustím později.

Selenium WebDriver ale umí jen webovou stránku přes prohlížeč ovládat a získávat z ní informace. Na vyhodnocování správnosti není příliš dobře připraven. Z tohoto důvodu se (alespoň v mnou použité Javě) často jako doplňkový framework využívá JUnit. Ten sice primárně slouží pro jednotkové testování, ale poskytuje řadu velice efektivních služeb, díky nimž lze snadno psát, parametrizovat, organizovat, spouštět a vyhodnocovat i funkcionální testy. Tak to bylo i v mém případě.

Používal jsem JUnit 4 a dobrá znalost jeho pokročilejších funkcí mi výrazně usnadnila práci a zpřehlednila celý systém stovek testů. Chtěl bych výslovně zdůraznit, že jsem nepsal jednotkové testy. A proč jsem nepoužil nejnovější verzi Junit 5? Protože obecně nemám rád verze X.0, a raději si počkám na verzi X.0.1. Konkrétně v případě JUnit 4 to bylo dobré rozhodnutí, protože JUnit 5 ve verzi 5.0 neumožňovalo jednu drobnou, ale pro rychlost běhu testů dosti podstatnou funkcionalitu (viz podrobně v dalších dílech této série).

V souvislosti (nejenom) s JUnit se používají další termíny, které budou vysvětleny následovně.

Časté termíny v testování

Assert – způsob vlastního vyhodnocení testu. Dopředu víme, jak má test ideálně dopadnout, a tento stav porovnáváme se skutečným stavem. Například výsledná suma má být dle předpokladu 1000. Pokud je i skutečná suma 1000, pak test „projde“ a assert způsobí nejvýše zalogování úspěšného testu, ale nejčastěji neudělá nic. Ovšem pokud předpokládaný výsledek nesouhlasí se skutečným, vyšle assert zprávu o tom, že tento test selhal. Tato zpráva je zachycena a dále zpracována příslušným frameworkem. Podstatné je, že se o tuto komunikaci programátor testů nemusí vůbec starat.

Parametrizovaný test – je vhodná technika, pokud potřebujeme ověřit správnou funkčnost na různých typově podobných datech. Typický příklad je přihlašování do aplikace, kdy pro dostatečnou míru jistoty nestačí otestovat jen jediného uživatele. Protože je test principiálně stále stejný, nemění se jeho tělo, ale nějakým způsobem se spustí vícekrát s měnícími se vstupními daty.

Organizace testů – při větším množství testů je vhodné je nějakým způsobem seskupovat do testovacích sad (test suite) a tyto sady pak jednotně spouštět. Případně mít možnost některé testy dočasně vynechat apod. Pro všechny tyto požadavky je v dobrém testovacím frameworku podpora.

Když už byly výše zmíněny jednotkové versus funkcionální testy, je třeba v této souvislosti zmínit i další důležité pojmy, a těmi jsou bílá a černá skříňka.

Testování bílé skříňky je testování, kdy máme k dispozici (vidíme) zdrojový kód aplikace. Tento způsob testování je typický pro jednotkové testy, kdy vývojář samozřejmě vidí svůj právě testovaný zdrojový kód. Vidí tak v něm všechny detaily a testování tomu může přizpůsobit.

Opakem je testování černé skříňky, kdy do testované aplikace „nevidíme“ a máme k dispozici pouze její ovládání přes vstupy a její výstupy na vyhodnocování. Toto je typické právě pro funkcionální testování.

V případě testování webových aplikací se ale často používá jakýsi hybrid nazývaný šedá skříňka. V tomto případě (stejně jako u černé skříňky) nevidíme zdrojový kód, ale máme k dispozici nějaké interní informace. U webových aplikací je to struktura a obsah HTML souboru, která se dá kdykoliv zjistit. Takže můžeme v testovacím programu používat pro adresaci jednotlivých web elementů např. jejich ID.

Dalšími pojmy a oblasti testování jsou pozitivní a negativní testy. Pozitivní testy (též verifikační testy nebo testy splněním [test-to-pass]) jsou testy, které verifikují korektní (požadované) chování za očekávaných podmínek použití. Vstupem jsou očekávané (správné, korektní) hodnoty, výstupem je očekávaná užitečná činnost a cílem je ověřit očekávanou správnou funkčnost při běžném používání.

Naopak negativní testy (testy selháním nebo vynucením chyb [test-to-fail]) ověřují chování systému při provádění testu za neakceptovatelných, abnormálních nebo neočekávaných podmínek. Vstupem jsou chybné (nekorektní, nesmyslné) hodnoty, které jsou ve specifikaci zapovězené, výstupem je reakce popsaná ve specifikaci, která nevede k pádu programu. Touto reakcí může být např. přehledné hlášení o nekorektních datech, automatická oprava vstupní hodnoty, logovací záznam apod. Důležité je to, že je s touto alternativou v programu počítáno.

Pozor na to, že jak pozitivní, tak i negativní testy píšeme vždy tak, aby prošly. To znamená, že máme pod kontrolou, co se v testovaném programu děje a že program dává očekávané výstupy/reakce. Neprojde-li (selže-li) test, znamená to, že se v testovaném programu děje něco, s čím jsme dopředu nepočítali či o čem nevíme. To je nutné dodatečně opravit.

Z tohoto krátkého popisu je zřejmé, že negativní testy musejí být důsledně psány v místech, kde má uživatel možnost zadat nekontrolovaně jakoukoli hodnotu („blbost“). Této možnosti se při vytváření webových aplikací poměrně úspěšně bráníme, takže všude, kde je to jen trochu možné, uživatelův vstup kontrolujeme pomocí vhodného elementu. Proto používáme výběrové seznamy, posuvníky, komponenty pro zadání data a času atp. Čili negativních testů je pro současné aplikace zapotřebí jen velmi málo.

Pro zjištění skutečnosti, jak moc testů je nutné napsat, slouží u jednotkových testů metrika nazývaná „pokrytí kódu“. Její nejjednodušší forma je pokrytí řádek kódu a zkoumá se pomocí ní, zda vykonané testy způsobily průchod přes řádky testovaného zdrojového kódu. Čím více řádek zdrojového kódu je pokryto, tím samozřejmě lépe. Ovšem 100% pokrytí nelze v praxi smysluplně dosáhnout, a pokud by se s ním někdo chlubil, pak je to (pro běžné softwary) lehce podezřelé.

V dalších článcích bude zmíněn i pojem N-version programming. Toto je technika, která se používala a možná ještě používá zejména u bezpečnostně kritických systémů. Principem je, že podle jedné specifikace se systém naprogramuje vícekrát různými vývojovými týmy. Oba (všechny) systémy pak běží paralelně se stejnými vstupy a očekává se, že budou mít i stejné výstupy. V případě více než dvou systémů je pak možno většinově volit výslednou hodnotu. V případě dvou systémů ukazuje rozdílnost výstupů na chybu minimálně jednoho z nich. Teď se pravděpodobně vynoří otázka, proč tuto evidentně nákladnou techniku používat ve webové aplikaci, která většinou bezpečnostně kritická není. Nepoužívá se důsledně. Ovšem při psaní sofistikovanějších automatizovaných testů se dříve či později dostáváme do situace, kdy v testech znovu programujeme alespoň základní logiku celé aplikace. Tento postup lze považovat za jakousi modifikaci konceptu N-version programming.

Dalším pojmem budou dynamicky generované testy. Běžně jsou automatizované testy napsány stejně jako program, tj. jejich zdrojový kód vytvořil osobně vývojář testů. Ovšem pokud je aplikace dostatečně popsána a existuje dobrá podpora pro vývoj testů, lze testy generovat i dynamicky, tj. jejich zdrojový kód píše program. Tyto testy mají samozřejmě svoje omezení, ale lze je třeba použít pro ověření průchodu všemi cestami v aplikaci apod. Tento typ testů nebude dále popisován, byť se s nimi na testované aplikaci dělají úspěšné pokusy, což je ale mimo rozsah článku. (Poznámka – termín „statické testy“ představuje něco jiného.)

Vhodná literatura k dalšímu čtení o testování:

Našli jste v článku chybu?