Ondřej Machulda

Webový vývojář v LMC. Má rád, když věci fungují, a momentálně se proto věnuje testování a QA obecně. @OndraM

Funkční systémové testování: když unit-testy nestačí

Quality assurance v praxi

Při dlouhodobém vývoji větší a komplexnější aplikace se zvyšuje šance, že změna na jednom místě rozbije nebo jinak ovlivní fungování aplikace na místě nějakém úplně jiném. Tato rizika můžeme snižovat řadou způsobů (důsledná kontrola a code-review, dobře dekomponovaná aplikace apod.), ale ani tak není nikdy možné je snížit na nulu. Proč to tak je a co se s tím dá tedy dělat?

Rozbíjíme si aplikace

Pro uvedení do kontextu stručně k tomu, jak u nás v LMC vývoj probíhá: vyvíjíme naše vlastní webové produkty prostřednictvím několika agilních týmů. Průběžně tak dochází v aplikacích k množství změn, na úpravách jedné aplikace pracuje souběžně několik vývojářů z daného týmu, a to v rámci různých a často nesouvisejících issues (ticketů). Produkty jsou to rozsáhlé, mají za sebou několikaletý vývoj, a je tak téměř nemožné, aby některý vývojář znal nazpaměť všechny části aplikace.

Aplikace jsou samozřejmě do rozumné míry pokryté unit testy a každou issue před dokončením testuje kromě samotného vývojáře obvykle v rámci code-review i alespoň jeden další vývojář, stejně tak funkčnost kontroluje ještě produktový manažer. Jenomže – ani jedna z těchto vrstev testování nemusí odhalit některé problémy. Zvláště ty, které se zdánlivě vůbec netýkají upravované části aplikace. Jakto? Může napovědět pár příkladů, ať už z naší praxe či teoretických:

  • Oblíbené „odstranění nepoužívaného kódu“ odstranilo kód používaný na místě, kde o něm vývojář nevěděl, a způsobil tím nefunkčnost jiné části aplikace.
  • Úpravou v globální šabloně se na jedné stránce přestal načítat externí javascript, takže nefungovaly žádné javascriptové interakce (modální okna, taby, AJAX…).
  • S aktualizací jQuery přestal v jednom prohlížeči fungovat jQuery plugin používaný na jedné „zapadlé“ podstránce.
  • Odkaz zasílaný v potvrzovacím e-mailu přestal fungovat.
  • Změna v databázi (nový sloupec) kvůli potřebě rozšíření API způsobila nemožnost ukládat položky přes jedno zapomenuté GUI.

Podobné „rozbíjení“ aplikací je víceméně přirozenou a nevyhnutelnou součástí vývojového procesu. Důležité ale je umět tyto problémy účinně a co nejdříve nacházet.

Funkční systémové testování

Cestou k tomu je systémové testování, konkrétně jeho funkční část (proto „funkční systémové testování“). Tedy testování, které probíhá nad celým již integrovaným systémem a které testuje nějaký funkční požadavek či očekávání. Jedná se tedy o vrstvu testů, která je nad unit testy a těsně nad integračními testy. Svým způsobem se tak jedná o výstupní kontrolu produktu před jeho předáním zákazníkovi. (U nás děláme vlastní produkty, takže máme tzv. „interního zákazníka“ – stakeholdery, nicméně na podstatě to nic nemění.)

Na straně zákazníka by pak mohlo následovat ještě akceptační testování, tedy testování, zda aplikace splňuje např. smluvně zadané funkční scénáře. Na rozdíl od akceptačního jsou ale při systémovém testování ověřovány i různé podscénáře, chování, které zákazník očekává (a nemusí být ani ve specifikaci) a celková bezporuchovost dané webové aplikace. A jak bylo uvedeno výše, má navíc automatizace tohoto testování neocenitelnou hodnotu v průběhu vývoje. Funkční testy jsou tedy nezbytnou součástí fungujícího continuous integration – vrchol testovací pyramidy.

Laicky se dá v případě webu funkční systémové testování označit jako „klikací testy“, protože o tohle převážně jde: v prohlížeči – ať již skutečném (např. Firefox) nebo v headless (PhantomJS) – se automaticky projde nějaký scénář a podobně jako v unit-testech se zkontroluje, že očekávané chování odpovídá tomu, které skutečně nastalo. Může se jednat o podobně jednoduché scénáře s jednoduchými očekáváními:

  • Po kliknutí na odkaz „Přihlášení“ se dostanu na stránku s přihlašovacím formulářem, kde mě po zadání přihlašovacího jména a hesla aplikace přihlásí.
  • Když naopak zadám špatné heslo, zobrazí se chybová hláška.
  • Když zadám něco do vyhledávacího políčka a formulář odešlu, zobrazí se stránka s výsledky.
  • Přiložený soubor (CV) v odpovědi na nabídku pracovní pozice nemůže být spustitelný a větší než 5 MB (a v obou případech se uživateli zobrazí chybová hláška).

Může jít ale i o komplexní scénáře podobného typu:

  • Jako firma vytvořím inzerát s nabídkou pracovní pozice. Po zaplacení kartou se mi zobrazí potvrzení a e-mailem mi přijde faktura. Notifikační e-mail přijde také fakturačnímu oddělení. Do několika minut (po zaindexování fulltextem) pak bude bude pozice vystavená a fulltextem vyhledatelná.
  • Zaměstnavatel založí v personálním systému nového zaměstnance. E-mailem mu pošle výzvu, aby nahrál do systému svou fotku prostřednictvím jednorázového odkazu. Zaměstnanci přijde na jeho e-mail během 5 minut (tj. až projde message queue) zpráva s tímto odkazem. Na uvedeném odkazu nahraje svoji fotku, nastaví její ořez a fotku uloží. Jeho nová fotka se od této chvíle začne personalistovi správně zobrazovat. Jednorázový odkaz navíc nejde znovu použít.

Pokud bychom kontrolu podobných scénářů dělali ručně, zabralo by nám to spoustu času. To by kromě nákladů a zdržení nasazování (když se najde a pak opraví chyba, je třeba celou aplikaci opět přetestovat) znamenalo, že bychom ji ani nemohli testovat průběžně, a na chyby bychom přicházeli až v okamžiku „těsně před předáním“. A jak známo, čím dříve chybu najdeme, tím nás stojí méně.

Co používáme v LMC

Pro automatizaci tohoto testování dnes existuje řada nástrojů. Některé jsou postavené pouze nad jedním headless prohlížečem (Casper JS a další), čímž mimo jiné testerům ztěžují psaní i kontrolu testů, jiné možná zbytečně přidávají vrstvu abstrakce (Behat + Mink) a pro funkční testování se vlastně ani nehodí.

Po špatných zkušenostech s proprietárním nástrojem Squish jsme se letos rozhodli přejít na Selenium WebDriver (mimochodem – WebDriver se stává W3 specifikací a tedy de-facto průmyslovým standardem pro automatizaci ovládání prohlížečů).

Testy píšeme v PHP (kde používáme implementaci php-webdriver), a spouštíme skrze vlastní nástroj Steward postavený na PHPUnitu. O jejich automatické spouštění oproti různým vývojovým prostředím se stará Jenkins CI server. Samotné Selenium pak testy vykonává ve Firefoxu, Chromiu, PhantomJS a zkoušeli jsme i MSIE. I přes určité porodní bolesti se nám to zatím vcelku osvědčuje, což dokládá mimo jiné i snížení množství WTF za minutu při psaní a kontrole testů (v porovnání s předchozí platformou).

 

Ve chvíli, kdy neděláte jenom aplikace zakázkovým low-end stylem „nasadit, vyfakturovat a zapomenout hesla k ftp“ ale naopak se k aplikaci vracíte, nebo se třeba jako v našem případě jedná o vlastní vyvíjené produkty, byl by život bez systémového testování hodně na hraně. Testy se dají rychle napsat i spustit a z praxe musím říci, že nám zachránily řadu i velmi vážných produkčních incidentů.

(Autorem úvodního obrázku je NATO a podléhá licenci CC-BY-2.0)