Lekce 1 - Úvod do testování softwaru v Pythonu Nové
Vítejte, všichni pokročilí programátoři, u prvního tutoriálu online kurzu testování aplikací v Pythonu. Kurz je tvořen zejména na základě praktických zkušeností s projekty většího rozsahu. Naučíme se v něm postupně pokrývat kód různými typy testů a tím vytvářet spolehlivé a kvalitní aplikace. Kurz je určen všem, kteří usilují o slušné zaměstnání s dobrým platovým ohodnocením. Nabyté znalosti využijí i ti, kteří mají vlastní aplikaci a rádi by ji dále pohodlně rozšiřovali beze strachu, že změny kódu rozbijí původní funkčnost:
-
import unittest class TagDAOTest(unittest.TestCase): known_tags = None @classmethod def setUpClass(cls): TestEnvironment.init() cls.known_tags = TestEnvironment.get_data(Tag) cls.assertGreater(len(cls.known_tags), 0) def setUp(self): self.dao = TagDAO()
Minimální požadavky
Pro úspěšné absolvování kurzu je nutná znalost jazyka Python alespoň v rozsahu našich kurzů:
Výhodou jsou dále znalosti z kurzu:
Testování je vlastně takovým čtvrtým bodem osnovy výše, který by měl každý dobrý programátor znát, aby jeho práce měla skutečnou hodnotu.
Kdy testovat
Testování se jistě řadí mezi dobré praktiky vývoje softwaru (best practices), kterými se také zabýváme v samostatném kurzu Best practices pro návrh softwaru. Další takovou praktikou je např. používat vícevrstvou architekturu a podobně. Rozdělení logiky aplikace do objektů a vrstev znamená pro zkušeného vývojáře pouze nepatrné zdržení a minimalizuje náklady na rozšiřování a udržování nepřehledné a nerozvrstvené aplikace. Na druhou stranu, některé praktiky vývoje softwaru bychom naopak neměli dodržovat příliš extrémně. A mezi ně patří právě testování.
Hned na úvod je třeba zmínit, že testování je velmi důležité, a v určité části projektu dokonce nenahraditelné. Na druhou stranu, v prvních fázích projektu (a to zejména u start-upů), kdy se hraje o čas, funkčnosti aplikace se často mění a je třeba ji co nejdříve spustit, není vůbec dobrý nápad testy psát. Musely by se často měnit, zbytečně by zdržovaly a mohly by poškodit rozjezd.
Některým z nás nyní možná bleskla hlavou teorie ohledně TDD (Test-Driven Development), která se naopak opírá o neustálé testování naprosto všeho. Jako teoretický koncept zní sice pěkně, ale v praxi by měl dobrý programátor dokázat myslet i trochu jako manažer a nakládat rozumně s vývojovým budgetem (rozpočtem). Koneckonců i jeho plat vychází z toho, jak je aplikace konkurenceschopná. Pokud máme naopak aplikaci, která má již několik stabilních funkcí a kterou chceme dále rozšiřovat, bez testů se neobejdeme.
Testy tedy pokrýváme takové aplikace, které již mají několik stabilních funkčností.
Rozšiřování softwaru
Jakékoli rozšiřování softwaru, provádíme-li jej kvalitně, vždy znamená změnu stávajících funkčností. Kvalitní analýza a návrh mohou sice do jisté míry připravit půdu pro budoucí úpravy, ale i když se budeme hodně snažit, trh se mění nekontrolovaně a úspěšnou aplikaci bude třeba upravovat ze všech stran. Pokud stávající funkce upravovat nebudeme, začne nám vznikat redundantní kód. Například napíšeme podobnou metodu znovu, místo abychom jen parametrizovali nějakou, kterou již aplikace má. Nebo zbytečně přidáme další databázové tabulky namísto toho, abychom jen upravili datový model a podobně.
Asi víme, že vyhýbat se úpravě stávajícího kódu aplikace se vůbec nevyplatí. Trpěl by tím její návrh a do budoucna by bylo velmi obtížné takový slepenec nějak upravovat nebo rozšiřovat. Změny bychom pak museli provádět na několika místech, bloky kódu by se opakovaly a podobně. Došli jsme tedy k tomu, že svou aplikaci budeme stále měnit a přepisovat, takto vývoj softwaru zkrátka vypadá. A kdo pak otestuje, že všechno funguje? Člověk? K našemu závěru, že musíme testovat, zda se předchozí funkčnost novou úpravou nerozbila, si také vysvětleme, proč nemůže testování provádět vždy jen člověk.
Proč testování nemůže dělat člověk?
Zamysleme se nad naivní představou.
Očekávání
Programátor nebo tester si sednou k počítači a kliknutím projdou jednu službu za druhou, zda fungují. Vezměme si například ITnetwork. Tester by se zkusil zaregistrovat, přihlásit se, změnit si zapomenuté heslo, upravit profil, dobít body platební kartou a tak dále. Funkčností (use casů) jsou ve zdejším systému stovky. Možná nás napadá, že člověk by je testoval hodiny, a proto testování ponecháme stroji, který to zvládne pravděpodobně za pár minut. Ano, ale stále ještě nemáme úplně pravdu. Sednout si a den klikat není v zásadě až takový problém, testy se píšou dlouho, možná by se neautomatizované testování dokonce vyplatilo. Ale…
Realita
Představme si však, že takto testujeme aplikaci, jsme někde v polovině testovacího scénáře a najdeme chybu. Například nelze napsat komentář k článku kvůli změně nějakého validátoru. Chybu opravíme a úspěšně pokračujeme až do konce scénáře. Otestovanou aplikaci nasadíme na produkční server a další den nám přijde e-mail, že není možné kupovat články. Po chvíli zkoumání zjistíme, že oprava, kterou jsme provedli, rozbila jinou funkčnost, kterou jsme již otestovali předtím. Takto jsme mohli přijít i o několik desítek tisíc Kč, než nám chybu někdo nahlásil. A to jsme ani nezmínili katastrofické scénáře, kdy by došlo k nějakému úniku dat, zaspamování uživatelů a podobně.
Pokud se během testování provede oprava, musíme celý scénář testu provést od začátku!
Chyb se navíc obvykle najde hned několik, takže se celé testování protáhne až na několik dní klikání. Programátor toto pravděpodobně nevydrží a neprovede testy pečlivě. Osobně jsem frustraci z provádění stejných úkonů stále dokola nikdy nesnesl Potom hrozí zanesení chyb na produkci.
A právě proto musí testy provádět stroj, který celou aplikaci prokliká obvykle maximálně za desítky minut a může testování provádět znovu a znovu a znovu. Proto píšeme automatické testy. Toto vám bohužel většina návodů na internetu neřekne.
Typy testů
Zaměřme se nyní na to, co na aplikaci testujeme. Typů testů je hned několik a obvykle nepokrývají úplně všechny možné scénáře (veškerý kód). Mluvíme proto o procentuálním pokrytí testy (code coverage), a to většinou kritických částí aplikace. Čím větší aplikace je, tím více typů testů potřebuje a tím více funkčnosti obvykle pokrýváme. První verze menších aplikací většinou ještě nepotřebují žádné testy nebo jen ty úplně základní, aby se do nich dalo registrovat.
Popišme si ty nejzákladnější typy testů.
Jednotkové testy (unit testy)
Obvykle testují univerzální knihovny, nepíšeme je pro kód specifický pro danou aplikaci. Jednotkové testy testují třídy, přesněji jejich metody, a to jednu po druhé. Předávají jim různé vstupy a zkoušejí, zda jsou jejich výstupy korektní.
Pomocí unit testů nedává moc smysl testovat, zda metoda obsahující databázový dotaz, která je použita v jednom kontroleru (nebo nějaké jiné řídicí vrstvě), vrací, co má. Naopak dává velmi dobrý smysl jimi testovat např. validátor telefonních čísel, který používáme na dvaceti místech, nebo dokonce v několika aplikacích. Takový jednotkový test vyzkouší např. dvacet různých správných a nesprávných telefonních čísel a ověří, zda validátor opravdu pozná, která čísla jsou validní a která ne. Unit testy jsou tzv. white-box testy, to znamená, že je píšeme s tím, že víme, jak testovaný kód uvnitř funguje (vidíme dovnitř).
Akceptační testy
Tento typ testů je naopak zcela odstíněn od toho, jak je aplikace uvnitř naprogramována, jsou to tedy black-box testy (nevidíme dovnitř). Každý test obvykle testuje určitou funkčnost. Test na psaní článků by testoval jednotlivé use casy s tím spojené (předat článek ke schválení, schválit článek, zamítnou článek, publikovat článek jako administrátor…). Tyto testy obvykle využívají framework Selenium, který umožňuje automaticky klikat ve webovém prohlížeči a simulovat internetového uživatele, který naši aplikaci používá. Těmito testy se tedy v podstatě zkouší specifická logika aplikace (databázové dotazy a podobně), testuje se výsledek, který aplikace vygeneruje, nikoli přímo její vnitřní kód.
Integrační testy
V dnešní době dosahují aplikace již poměrně vysoké komplexnosti a velmi často bývají rozděleny do několika služeb, které spolu komunikují a jsou vyvíjeny zvlášť. Právě integrační testy dohlížejí na to, aby do sebe vše správně zapadalo. Někdy to budou dva moduly (balíčky) jednoho systému, které si mezi sebou vyměňují zprávy, jindy dva dílčí softwary. V dalším případě můžeme testovat integrování se softwarem cizí strany, tedy vyvíjeným jinou firmou.
Systémové testy
I když aplikace funguje dobře, v produkčním prostředí podléhá dalším vlivům, s nimiž musíme také počítat. Například by měla zvládat obsluhovat tisíc uživatelů v jeden okamžik. To bychom ověřili zátěžovým testem, který patří mezi ty systémové.
V-model
Úvod do problematiky softwarového testování zakončíme představením tzv. softwarového testování – V-modelu. Ten rozšiřuje známý vodopádový model vývoje softwaru, který má následující fáze:
- analýzu požadavků,
- high-level návrh,
- detailní specifikaci,
- implementaci.
Jak jistě víme, celý software se již dávno nevyvíjí postupným provedením těchto čtyř fází, ale iteračně, tedy prováděním těchto fází v krátkých časových intervalech pro další a další části aplikace. V-model každé z těchto fází přiřazuje testovací fáze (typ testů, o kterých jsme hovořili výše):
Vidíme, že název V-model je odvozen od podoby s písmenem V. Po implementaci pomocí unit testů ověříme detailní specifikaci, integračními testy vyzkoušíme high-level návrh a akceptačními testy funkčnost všech use casů. V-model se někdy dělá ještě o několik pater vyšší, pokud je aplikace opravdu rozsáhlá a vyžaduje více typů testů, například systémových.
V následující lekci, Testování v Pythonu - První unit test s knihovnou unittest, se naučíme programovat unit testy, což jsou nejjednodušší testy ověřující funkčnost vnitřních knihoven (jednotek), se kterými obvykle začíná při pokrývání aplikace testy.