Testování kódu

Školení, která pořádám

S vytvářením testů, které kontrolují správnost programu nebo některé jeho části, nemám velké zkušenosti. Samozřejmě si každou funkci nebo celý program vždy zkouším, nedělám to ale nijak systematicky. Výhoda systematického přístupu spočívá v tom, že se testy mohou spouštět opakovaně a pokud jsou dostatečně obsáhlé, tak máme slušnou šanci, že pokud všechny projdou, tak jsme úpravou programu nenarušili jeho funkčnost.

Vytvoření formálního testu není nic složitého – stačí si uložit testovací program (což může být třeba jen jeden řádek) a jeho očekávaný výstup. Spuštění testu porovná skutečný výstup s očekávaným a pokud se liší, tak nás na to upozorní.

Pro program kontrolující správnou inicializaci proměnných se mi testy velmi osvědčily – vstup i výstup je jasně definovaný. Naopak zvýrazňovač syntaxe by to měl těžší – výstup je složitý, takže porovnávat ho celý by znamenalo úpravu požadovaného výstupu při doplnění téměř jakékoliv funkčnosti a porovnávání jen části výstupu by nemuselo být spolehlivé.

Pro testy v php-initialized jsem použil formát odvozený z testů používaných pro testování PHP:

--TEST--
Usage of uninitialized variable
--FILE--
<?php
echo $x;
?>
--EXPECTF--
Uninitialized variable $x in %s on line 5

Pro spuštění testu se nepoužívá přímo skript z PHP, protože já nepotřebuji kód spustit, ale jenom zkontrolovat. Proto jsem si napsal vlastní jednoduchý testovač:

<?php
preg_match("~^--TEST--\n(.*)\n--FILE--\n(.*)\n--EXPECTF--\n(.*)~s", file_get_contents($filename), $match);
ob_start();
check_variables($filename);
if (!preg_match('(^' . str_replace("%s", ".*", preg_quote($match[3])) . '$)', ob_get_clean())) {
    echo "failed $filename ($match[1])\n";
}
?>

Díky testům mám jistotu, že při zásahu do citlivé části programu nepřestanou fungovat podporované konstrukce.

Jakub Vrána, Seznámení s oblastí, 30.7.2008, diskuse: 15 (nové: 0)

Diskuse

ikona David Majda:

Doplnil bych, že psaní testů má kromě svých plusů i jedno podstatné mínus - cenu psaní testů samotných. Pokud člověk píše testy poctivě, je množství jejich kódu srovnatelné s množstvím kódu testovaného programu a pokud budeme uvažovat stejnou cenu za řádek, znamená to, že program s testy může být cca 2x dražší než program bez nich. Záleží pak na konkrétní situaci, jestli těch pár odchycených chyb a větší volnost při vývoji za tuto cenu stojí.

Jinak testy pomůžou nejen při zásazích do programu a refaktorizacích, ale třeba i při změnách v použitých knihovnách/frameworcích. Hodí se to, když například nová verze deklaruje nekompatibilitu s předchozí a zároveň není k dispozici podrobný seznam všech rozdílů nebo je tento seznam příliš dlouhý. (Modelový příklad je upgrade aplikace v Rails 1.x na 2.x, ale třeba i upgrade z PHP 4 na PHP 5. Pokud existují testy, stačí je spustit s novou verzí, opravit nalezené problémy a výsledkem je rozumná míra jistoty, že aplikace poběží v nové verzi správně.)

Borek:

Pokud se testuje něco složitějšího než porovnání dvou textových výstupů, dokonce bych řekl, že cena psaní testů je větší než cena psaní produkčního kódu, tj. kód bude víc než 2x tak "drahý".

Přesto se pomalu dostávám do situace, kdy jsem OSS projekt ochoten použít jen pokud jsou k němu dostupné testy, protože ty jsou často jedinou zárukou aspoň nějakého QA.

Projekt bez testů nebrat.

ikona Marty:

Já bych řekl, že cena celého projektu se mnohdy o moc nezvýší, pokud to nejsou vyloženě jednoduché stránky (o tomto mluvil David Majda). Pokud se vytváří nějaký projekt většího rozsahu, pak se vždy nějakým způsobem testuje, jestli vše funguje. Buď to dělají testeři nebo se napíší automatické testy. Jak testeři (třeba i samotný programátor), tak automatické testy se musí zaplatit. Rozdíl je však v tom, že práce testerů je ryze jednorázová záležitost a platí se za každé testování, kdežto automatické testy se zaplatí jednou.

LLook:

Psaní testů nepředstavuje až tolik práce navíc, jak by se mohlo zdát: Prakticky všichni testujeme tak jako tak. Práce navíc spočívá už pouze v tom, tuto činnost nějakým způsobem zaznamenat pro budoucí reprodukci.

Větší počet řádků je diskutabilní - neuvažuješ ty testovací řádky, které jsi smazal, ale jejichž napsání bylo nutné.

tom:

tak - to testování se často pak smaže a nebo se s ním dál nepracuje. nárůst objemu nebude nijak zásadní...

Bruinee:

Kdyby kod pro testovani byl stejne rozsahly jako vlastni testovany program, tak by to melo zajimavou rekurzi. Je prece potreba jeste napsat test, ktery overi, ze testovaci program je v poradku.

Borek:

Produkční kód je testován testy a naopak - testovací kód je testován produkčním kódem. K žádné "rekurzi" nedochází.

jos:

Beck by tě za to nechal ukřižovat na neotestovaným kříži

Botanicus:

Davide, Borku, co se tyce te ceny, ta je samozrejme nezanedbatelna, jenze na druhou stranu testy prave vyvoj zrychluji – jednak uz pri vyvoji samotnem, kdyz odhali spoustu chyb, ale hlavne pak pri pozdejsich upravach – podle meho nazoru se netestovany kod slusne upravovat ani neda.

Borek:

Ne každý kus kódu nutně prochází náročnou fází údržby, ale u větších projektů v zásadě souhlasím.

Jakub Nesveda:

Napsání dobrých testů se musí člověk naučit stejně tak jako programování. Cílem není napsat test "na míru" určité jednotky aby testem prošla(nepopírám, že psaní testů k tomu svádí), ale napsat test, který proklepne jednotku ze všech směrů. Testům "na míru" lze do jisté míry předejít dle TDD(Nejdřív testy, pak implementace).

Cena testů je sporná, jak praví klasik: "Kvalita je zadarmo". Zvlášt u rozsáhlých a komplexních projektů se testy nejspíš i vyplatí. Je ošemetné srovnávat testování jednotek a testera(osobu). Testování jednotek zajistí kvalitu na nízké úrovni, kdežto tester dokáže mnohem víc. Mně osobně psaní testů nutí psát kód, který nebude těžké otestovat, což dle mého názoru vede ke kvalitnějšímu kódu od počátku - kód je smysluplnější(rozuměj lépe čitelný) a méně náchylný k chybám.

Pro unit testy ohledně php se mi libí phpUnit . Testy za vás sice nenapíše ani nezajistí jejich kvalitu, ale usnadní jejich správu a psaní.

finc:

Žádný kód není funkční, pokud k němu neexistují testy (ať už se jedná o jakýkoli běžný prog. jazyk).

Pokrytí aplikace testy je pro mě automatická součást programátorského života. Každý, kdo tvrdí, že testy jsou k ničemu, či zbytečně zvyšují náklady, tak asi nikdy nepracoval na rozsáhlejším projektu.

Docela by mě zajímalo, kolik zdejších čtenářů testy skutečně používá. Či kdo dokonce používá extrémní programování, kdy se nejprve píší testy, které v první fázi nikdy nesmí projít.

Keff:

V PHP je problém, že neexistuje kompilace, která by odhalila největší chyby (neinicializované proměnné, nekompatibilitu typů), kód je tedy nutno "sputit". Na druhou stranu, dobré testy opravdu stojí čas, a to nemálo, takže to zkouším řešit ne unit testy, ale integračními testy v Seleniu http://selenium-core.openqa.org/ . Pokud si tedy třeba pro typickou webovou aplikaci přidám kód který inicializuje databázi, mohu pak automaticky projít všechny use casy (s firefox pluginem který zaznamenává uživatelovy akce), kontrolovat že výstupem jsou očekávaná data/hlášky, a jako bonus na každé stránce ověřit nepřítomnost řetězců kterými PHP oznamuje chyby/notice/wariningy.

Výsledek je dobře použitelný především při úpravách aplikace, kde je možné ovřit, že přidaná funkčnost nezmění výsledek žádného use casu a nezanese do něj chyby.

Co si myslíte o tomto přístupu?

optik:

kompilace problém není, v php se taky kompiluje do byte kódu, ale není to vidět. Nutnost psát testy plyne z ohledem na jazyk spíše z toho, že PHP je dynamický slabě typovaný jazyk. Java je staticky typovaná a stejně se tam píšou testy jak o život, je to spíš o metodice vývoje, vedení projektu a tak.

Každopádně unit testy bych psal určitě také, minimálně pro kód knihoven je to velmi žádoucí.

Keff:

Máš pravdu, problém je v dynamičnosti a slabém typování - je legrační jak se typované jazyky můžou přetrhnout aby mohly být slaběji typované (různé konstrukce nad Javou), a slabě typované zase přidávají kontrolu typů (třeba signatury funkcí s typem parametrů v PHP).

Pro knihovny to má určitě větší smysl než pro celou aplikaci, ale přijde mi, že v praxi nastávají tyto možnosti:

a) Knihovna dělá něco triviálního, díky čemuž je dobře testovaná (výstup fce deterministicky závisí na vstupních parametrech - třeba práce s časem atd), ale dělá něco víceméně triviálního a psaní testů je neužitešná otravná práce

b) Knihovna dělá něco užitečného, obvykle při tom ale mění stav okolního prostředí, často nevratně (práce s filesystémem, curl, databáze), výstup není deterministický vzhledem k vstupu - funkce je závislá na kontextu, kontext je komplexní a jeho mockováním strávíme spoustu času (psát "emulaci databáze", či filesystému kvůli jedné knihovně?), a často vzniknou problémy kdy s mockem je test ok, v reálném použití funkce selže. Tady jsou unit testy taky setsakra drahé a ještě k tomu občas neužitečné.

Co s tím? Tohle mi příde jako černý klín do růžového utopického světa 100% testovaných programů, a nemůžeme se schovat do krásné krajiny funkcionálního programování, neboť uživatelé chtějí aplikace interagující se světem, ne funkci závislou jen na parametrech...

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.