11. května 2009

GUTs = good unit tests

K problematice testování jsem četl výborný článek (1, 2) na JavaWorld, který všem vřele doporučuji - nejen kvůli obsahu, ale i kvůli množství odkazů na další články a zajímavé knihovny.

K obvyklým a častým "best-practices" (např. JUnit best practices) bych ještě přidal z mých zkušeností následující:

  • testovací kód by měl splňovat stejné kvalitativní nároky jako produkční kód. Místo jedné dlouhé testovací metody, která navíc ještě obsahuje duplicitní kód střídající se v jedn. testovacích metodách, je vhodné společný kód vyčlenit do separatních metod nebo i tříd, aplikovat pravidla refactoringu, když už kód "začne smrdět" (1, 2).
  • testy jsou ukázkou použití produkčního kódu, testy jsou součástí popisu API aplikace.
  • jednotlivé testy musí být na sobě nezávislé, pořadí spouštění testů může být libovolné
  • otestovat lze (skoro) všechno, jen je potřeba najít rovnováhu mezi náročností napsání testů a jejich přidanou hodnotou. Nesnažit se tedy za každou cenu dosáhnout 100% pokrytí produkčního kódu testy, ale spíše se držet pravidla 20/80, kdy otestováním 20% kódu otestujeme 80% funkcionality.
  • testy nesmí mít žádné vedlejší efekty, např. přidaný záznam v DB.
  • při psaní testů se nespoléhat na konkrétní prostředí (Locale, adresář na lokálním disku apod.), protože testy se mohou spouštět kdekoliv.

Co mě ale v uvedeném článku nejvíce zaujalo byla knihovna hamcrest. Při psaní testů jsem měl pořád problémy se psaním smysluplných komentářů do assertů a navíc mi ty všechny kontroly nepřisly úplně přehledné, když toho bylo více. Toto vše řeší hamcrest díky jednoduchému a výstižnému API - na ukázky a možnosti se podívejte do tutorialu nebo do uvedeného článku.

11 komentářů:

Jira řekl(a)...

Byl jsi rychlejší než já ... hamcrest mě zaujal také a moc, je to hrozně šikovná věcička.

Ale co mě z tvých pravidel nepřijde zcela správně:

- ne vždy musí být testy nezávislé, nené to nutné (TestNG to dost hezky i podporuje)

- test samozřejmě může mít side effect v podobě obsahu v DB a DAO se tak testuje docela hezky a dobře

Jinak souhlas ...

Petr Jůza řekl(a)...

Nevím, zda myslíme to samé, ale dle mého názoru stav po spuštění testů musí být stejný jako před spuštěním testů, tedy jak DB, tak cokoliv jiného.

To že v DB budou určitá specifická data jen pro testy, to je věc jiná.

Anonymní řekl(a)...

Pekna pravidla, ale ponekud naprd.

Jednoduche metody jimi projdou snadno, ale slozitejsi testy ne. Jako vysledek pak bude 99% junk testu (getter/settery). A na slozitejsi testy (GUI, user simulation, integration) se clovek vykasle protoze to neprojde 'best practisses'

Moje testy mozna nejsou best, ale problemy najdou brzy a spolehlive.

PS: Unit testy pisu v Groovy :-)

Jira řekl(a)...

2Petr: Nemusí. Nečo jiného je po běhu všech testů, ale po běhu každého individuálního testu to není nutné. Např. DAO je docela výhodné testovat stavově.

2Anonymní: Všechny best-practices se musí brát s rezervou, ne?

Petr Jůza řekl(a)...

Já jsem to myslel tak, že po skončení všech testů. Napsal jsem to asi nepřesně.

Michal NkD Nikodím řekl(a)...

mrkni/te na http://unitils.org . Pekna vecicka.

Honza Novotný řekl(a)...

Docela mne překvapuje kolik lidí polemizuje s těmi to pravidly, které i já považuji za správné a jejich porušení podle mého názoru vede k negativním dopadům na kvalitu / udržovatelnost testů.

Dřív jsem psal testy, které se mohly ovlivňovat a po svém skončení nechaly třeba záznam v DB. Brzy jsem ale přišel na to, že to je cesta do pekel a byl jsem velmi rád, když jsem objevil transakční podporu pro testování datové vrstvy od Springu. Nyní jsem tvrdým zastáncem toho, že test musí po sobě uklidit - už jsem se prostě dost natrápil s hledáním chyb ovlivňujících se testů.

Ani nesouhlasím s tím, že pravidla jsou naprd, protože se dají splnit jen jednoduchými testy? Pokud vím stále se bavíme o junitovém testování - pokud někdo dělá Seleniové integrační testy, tak je přeci jasné, že to je o něčem jiném a tam se prostě persistentních dopadů mezi testy nemůžeme jednoduše zbavit a je nutné zaujmout jiný přístup. V rámci Junitových testů propaguju ale tyhle pravidla taky.

Navíc testy sice píšeme pro odhalení chyb, ale musíme je taky udržovat - a tohle diskutované pravidlo je především o udržovatelnosti testů než o čemkoliv jiném.

Petře já se ti pod ten seznam bodů podepisuju bez výhrad. Kdyby nebylo šest ráno a já necucal své první kafe, možná bych ještě pár bodů doplnil :)

Anonymní řekl(a)...

Asi bych mel dodat ze nepisu unit testy, ale spis component a integration testy. Nevidim duvod testovat dvouradkove metody kdyz stejnou chybu zachyti vyssi test.

Asi bych mel byt konkretnejsi ktera pravidla mi vadi.

> jednotlivé testy musí být na sobě nezávislé, pořadí spouštění testů může být libovolné

Ne, zavislosti tam muzou byt, ale musi byt dobre zdokumentovane. Tim lze napriklad dosahnout daleko rychlejsiho behu testu.

> testy jsou ukázkou použití produkčního kódu, testy jsou součástí popisu API aplikace.

Tohle je zhruba stejna hloupost jako o ze zdrojovy kod dokumentuje sam sebe. Pouzivam hromadu utility trid aby testy byly kratsi a rozhodne to neni citelne pro nekoho kdo nezna vnitrnosti aplikace.

> testy nesmí mít žádné vedlejší efekty, např. přidaný záznam v DB.

Jsem zjedavy jak tohle chcete kontrolovat. Testovaci kod muze zavolat i 'rm -rf /', od toho je testovaci. A psat uklizeci kod neni casto dost dobre mozne. (ale DBUnit apod smysl urcite ma.

> při psaní testů se nespoléhat na konkrétní prostředí (Locale, adresář na lokálním disku apod.), protože testy se mohou spouštět kdekoliv.

Tohle obcas dost dobre neni mozne, napriklad pokud testujete online sluzbu :-) S adresari se mi osvedcil 'subst v: c:\neco' a symlinky na Linuxu.

Petr Jůza řekl(a)...

ad "testy jsou součástí popisu API aplikace" - zdůraznil bych zde slovíčko API, tedy nějaké rozhraní k užití. Pokud chce tvůrce API ukázat jeho používání, pak jsou testy naprosto ideální věc.

ad "nezávislost testů" - nemůžu si pomoci, ale to mi přijde jako rozumný požadavek. Abych si otestoval chování s externími systémy, tak buď v transakci a nebo přes Mock a Stub objekty. Konkrétně u souborového systému lze použít virtuální souborový systém a operace provádět nad ním.

Anonymní řekl(a)...

Ja sem vzdycky preferoval unit testy nad integracnimy. Maji nekolik vyhod
1 - mene se rozbijeji
2 - pomahaje ve vyvoji uz od zacatku
3 - jsou schopny pokryt 100% testovane funkcnosti
4 - presne dokumentuji pozadavky na testovanou funkcnost

Je pravda, ze samotny unittest nic nerika o funkcnosti celeho systemu. Muze nastat situace kdy vsechny unittesty projdou, ale cela komponenta ne. Coz je ale zpusobeno tim, ze nektery z unittestu nepokryl 100% pripadu. Maji i nektere dalsi nevyhody napr. nektere vstupni hodnoty do metody v realu nikdy nevstoupi, tudiz se testuji i pripady v realu nenastavajici.

Tyto dve nevyhody vedly k naposto spatnemu rozhodunuti v mem byvalem zamestatni, psat vyhradne integracni testy. Coz vedlo k tomu, ze kromne mne temer nikdo testy nepsal. Protoze integracni testy:
1) sou vyrazne slozitejsi. Mnozstvi nastaveni, ktere je nutne udelat drive nez se test spusti, je obrovske a uplne zastini co se vlastne testje.
2) casteji se rozbiji, protoze pokryvaji vetsi kusy kodu.
3) kvuli bodu 1 jsou spatne udrzovatelne, kdyz se to smycha s bodem 2, jsou testy spatne udrzovatelne. (V praxi se tak casto stane, ze do testu postupne prybyvaji ruzne dalsi nastaveni a hacky, proto aby prosel a casto se z nej uplne vytrati testovaci schopnost)
4) vetsinou nepokryji 100% funkcnosti, protoze to neni v lidskych silach ta vsechna nastaveni napsat
5) z bodu 4 pak plyne ze nemaji dokumentacni schopnost (nebo jen omezenou)
6) pisi se az po napsani kodu ktery ma byt testovan. Tudiz pri vyvoji vubec nepomahaji (a to je nejvesi vyhoda testu)

Proto si myslim ze vyvojar by mnel hlavne psat unit testy a integracni testy, by meli "to navic", cim by se melo navic zabyvat oddeleni QA

Anonymní řekl(a)...

Mám pocit že pojem Unit test je používaný ve dvou významech:
1/ automatické testy psané nad API ve stejném programovacím jazyku jako kód
2/ testování jednotlivých komponent (unitů)

Z tohodle pohledu může být ve významu 1/ použité i jako integrační. Záleží ale samozřejmě na dobře navrženém API a dobrém umístění kódu pod/nad API. Vlastně by mělo být integrační i ve významu 2/ protože v dobré architekuře máte vždy jednu komponentu která integruje ostatní.

Honza