14. května 2008

Máte čas na unit testy?

Pokud bych si měl hned sám odpovědět, tak dle mého názoru moc času není. Není z jednoho hlavního důvodu a to jsou peníze resp. náklady plynoucí z času, který bude věnován psaní unit testů. Hned na začátku také musím dodat, že celý článek je o "normálních" jednorázových projektech, které nejsou nějak mission-critical. U vývoje produktu (tj. aplikace, kterou zabalím a pošlu zákazníkům nebo aplikace, kterou opakovaně dodávám) předpokládám, že bude prostor pro psaní testů mnohem větší.

Ještě jsem nedělal ve firmě, kde by se počítalo s tím, že N dní budu vyvíjet produkční kód a dalších N dní budu mít vyhrazeno na testování (pozn. píši to sice v pořadí produkční kód, testy, ale klidně je možné si představit přístup opačný - TDD). Takto to často prezentují agilní přístupy k vývoji softwaru, ale v praxi je dle mého názoru toto neuskutečnitelné, aspoň tedy v rámci projektového vývoje, protože poměr nákladů na psaní unit testů vs. přínosy z toho plynoucí nejsou zrovna moc pozitivní.

V žádném případě nejsem odpůrce psaní testů, ale realita mě nějak naučila se na tu problematiku dívat i z jiného pohledu než jen vývojářského. Vidím spoustu výhod ze psaní testů, zejména následující dva:

  • zajištění požadované kvality produkčního kódu
  • kvalitou se nemyslí jen kód bez chyb, ale i takové věci, jako kvalitní architektura aplikace, dobrá dekompozice problému apod. Pokud chci psát efektivně testy, tak pak potřebuji mít i dostatečně kvalitní produkční kód.

Samozřejmě bez testů to nejde, nedovedu si představit, že bych takto odevzdal aplikaci zákazníkovi. Ale na jaké testy se zaměřit, aby přínosy a náklady byly v rozumné rovnováze? Dle mých zkušeností jsem asi nejlepší poměr přínosy vs. náklady zaznamenal v těchto případech:
  • znovupoužitelný kus kódu (knihovna, modul)
  • stěžejní aplikační logika, např. jádro aplikace
  • doménové objekty - equals. Na to jsou již testy napsané, takže je jen použít. Přínos možná malý, ale ušetří spoustu času při hledání chyb s kolekcemi.
  • při použití ORM nástroje testovat základní CRUD operace nad objekty. Pro tyto účely jsem si napsal sérii testů, protože je to stejné pořád dokola a je dobré vědět, že práce s DB na té základní úrovni funguje jak má. Spíše je to testování ORM mapování jako celku, než jen samotného procesu ukládání dat do DB.

Naopak mám negativní zkušenost z Mock (jMock, EasyMock) testů servisních a jiných interních rozhraní v aplikaci. Přínosem určitě bylo to, že mi testy drží strukturu aplikace pohromadě, ale velkou nevýhodou byla ztráta flexibility - kdykoliv jsem provedl nějaký refaktoring, tak jsem musel měnit i tyto testy. Testy se musí měnit vždy s ohledem na změny produkčního kódu, ale v oblasti rozhraní se mění a přidává celkem hodně a dle mého názoru výhody nepřevažují nevýhody.

Speciální kapitolou je testování GUI. Již jsem se na toto téma jednou rozepsal, kde jsem porovnával nástroje pro unit testy GUI. Bohužel je dost často rychlejší tedy levnější nechat GUI testy provést manuálně, než testy vytvořit a testovat automaticky. Většina si hned asi v duchu řekne, že je to možná levnější během vývoje, ale z pohledu nasazení, údržby a podpory aplikace to už neplatí. Toto je asi pravda, ale těžko se to prosazuje v průběhu vývoje. Úplně nejlepší je zapojit do testování zákazníka. Snažíme se vyvíjet iteračním způsobem a vždy na konci každé iterace dáme hotovou část aplikace zákazníkovi, který to následně připomínkuje.

Mě by hodně zajímalo, zda můj pohled na věc odráží realitu současného vývoje aplikací nebo zda jsem mimo. Znám spoustu firem resp. kolegů, kteří ještě nenapsali ani čárku testovacího kódu, ale na druhé straně znám i firmy, kde se na testování klade velký důraz. Já bych sám rád testoval určitě více než doposud, ale ekonomické tlaky od obchodníků, projekt managerů a vedoucích mi to moc nedovolují.

24 komentářů:

Borek Bernard řekl(a)...

Myslím, že mezi následovným psaním testů a TDD je ohromný rozdíl. TDD je o návrhu aplikace a především zajišťuje, že v kódu bude přesně jen to, co vyžaduje specifikace (testy jsou onou specifikací). Kromě toho, že při TDD jsou v kódu zachyceny funkční požadavky na aplikaci, je taky skvělé, že když do kódu zasáhne kolega a není si úplně jistý, jestli jeho změna nebude mít nějaké negativní následky, dobře napsané testy mu hned dají najevo, jestli vše proběhlo v pořádku.

Zrovna teď pracuji na aplikaci, kterou jsem dělal sám (takže jsem s kódem dobře obeznámen), jen byla pár měsíců u ledu. Bohužel je situace taková, že bez testů je jakákoliv sebemenší změna dost problematická a vývoj probíhá bolestně pomalu. Tady by testy jednoznačně pomohly...

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

Já osobně z pohledu času a tedy nákladů moc velký rozdíl mezi TDD a následovným psaním testů nevidím. TDD vnímám jako jiný přístup, jinou filosofii k tvorbě aplikace, ale z pohledu architektury, návrhu aplikace neočekávám žádné změny oproti "standardnímu" vývoji.

Borek Bernard řekl(a)...

"ale z pohledu architektury, návrhu aplikace neočekávám žádné změny oproti 'standardnímu' vývoji"

Tento předpoklad je podle mého názoru nesprávný. Když aplikaci píšete tak, aby vyhovovala unit testům, automaticky to ovlivňuje její návrh.

Když budete číst názory jiných vývojářů, často se setkáte s názorem, že zatímco je "test after" ztrátou času, TDD je něco zcela jiného (protože to je metodika vývoje softwaru, nikoliv testovací metodika). Pokud tedy máte zkušenost primárně s testy psanými posléze, neznamená to nutně, že se vaše názory musí vztahovat na unit testy obecně.

Celkově souhlasím, že napsat aplikaci s unit testy je časově náročnější než napsat ji bez testů, na druhou stranu fáze vývoje je jen jedna z mnoha v životním cyklu SW aplikace. TDD myslí i na budoucnost a nemá tak smysl asi jenom v případě, kdy se skutečně jedná o jednorázovou věc, o které jste si jistý, že už na ni nikdy nebudete muset sáhnout (opravdu takové aplikace máte?).

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

Když budu psát testy, tak vždy mě to bude nutit mít co "nejlepší" produkční kód, abych mohl psát testy efektivně. A je jedno, zda ty testy píšu jako první nebo až pak.

Jinak já osobně si moc nedovedu představit použití TDD v reálné komerční firmě v našich podmínkách. Ale o tom možná v jiném článku...

Jan Novotný řekl(a)...

Tak tyhle otázky mě trápily dlouho. Proto byla tohle jedna z věcí kterou jsem zahrnul do přednášky na UHK (http://blog.novoj.net/2008/05/09/podcast-zaznam-z-prednasky-automaticke-testovani-v-praxi/).

Osobně jsem přesvědčený, že testy se vyplatí pro jakoukoliv aplikaci. Budeš se divit, ale osobně mám pocit, že mi testy dokonce urychlují vývoj a to proto, že:

a) okamžitě si můžu spustit to co jsem napsal - mám to v hlavě a chyby se mi řeší daleko lépe
b) radikálně se sníží hluchý čas strávený na kolečkách objevím chybu, opravím chybu, spustím, ověřím
c) slepé vývojové větve naleznu a odříznu dříve, než jsem nakódoval další balast okolo nich
d) když nevím jak se kód zachová v nějaké situaci, jen dopíšu další variantu testu

TDD je pro mě také poměrně problematicky stravitelné, ale to co používám je TEST PARALLEL - současně s psaním kódu píšu i test (nebo okamžitě potom). Rozhodně je nesmysl TEST AFTER ve smyslu testy se dopisují až když je aplikace odladěná. To jsem si odzkoušel a tudy cesta nevede. Tady totiž přijdeš o většinu ušetřeného času.

Nebudu se extra rozepisovat, pokud budeš mít zájem, zkus si poslechnout ten podcast a rád si poslechnu já zase tvoje názory na něj.

Anonymní řekl(a)...

Při čtení komentářů mam pocit, že došlo k menšímu nepochopení článku :-) Petr Jůza nekritizuje automatické testy ani TDD, ale jenom tu podmnožinu, která se nazývá jednotkové testy.

Po dvou letech psaní testů jsem totiž došel k podobnému názoru, jako je v článku. V tom co se dá nazvat aplikační kód je účinek jednotkových testů minimální. Užitečné pro mě jsou integrační testy aplikační vrstvy nad databází a funkční testy systému (černá krabička). Sem tam napíšu i jednotkový test, ale to je zlomek všech automatických testů.

I při psaní znovupoužitelného kódu (něco ve stylu frameworku nebo knihovny) u mě nikdy netvořily striktně jednotkové testy víc než polovinu ze všech testů. Většinou spíš míň. Jednoduše řečeno: čas na psaní jednotkových testů se tak často nevrací.

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

ad Novoj: souhlasím s tebou, že TEST AFTER nemá vůbec smysl, když už, tak jedině TEST PARALLEL.

Tvé důvody pro psaní testů mi přijdou rozumné, ale asi každý vytváříme trochu jiný typ aplikací. Já jsem dělal hodně webově orientované aplikace, žádná velká business logika.

PS: Tvůj blog pravidelně sleduji, jen jsem ještě neměl čas si ten podcast poslechnout. :)

Jan Novotný řekl(a)...

Nemyslím si, že došlo k nepochopení článku. Kdyby Petr mluvil striktně o Unit testech a nikoliv o integračních, tak nezmiňuje vůbec GUI testování - protože to je svou podstatou jednoznačně integrační.

Podle mého názoru se článek vztahuje k automatickým testům jako k celku, ale k tomu se Petr jistě sám vyjádří.

Jinak s Bady s tebou souhlasím. Osobně jsem na tom s poměrem integračních / unit testů podobně. Hodnota integračních testů je jistě nepopiratelná, ale na druhou stranu mají několik nevýhod. Podle mě dvě hlavní jsou:

a) rychlost - integrační testy jsou jednoznačně pomalejší jak unit testy
b) křehkost - zdá se mi, že integrační testy je nutné udržovat výrazně více než jednotkové; tím že mají daleko větší záběr je také velmi často i drobná změna v produkčním kódu může porušit

Jan Novotný řekl(a)...

Ad Petr) taky dělám výhradně web aplikace a právě tady pozoruju velký časový přínos právě vynecháním deploymentu do appl. serveru

Typicky si kompletní logiku aplikace (apl. + dat. vrstva) odladím pomocí testů aniž bych jedenkrát musel deployovat do Tomcatu. První deployment dělám až s první dávkou GUI, která na apl. vrstvu navazuje.

Tenhle postup se mi prozatím docela osvědčil.

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

Já se jen vyjádřím k tomu, jak jsem to vlastně myslel :) - záměr článku byl psát o unit testech. S tím GUI jsem trochu "ujel", to přiznávám. Ale i tak GUI (přesněji řečeno prezentační vrstvu) lze testovat pomocí unit testů, např. unit testy MVC vrstvy, unit testy Spring Web Flow apod.

Anonymní řekl(a)...

Ahoj. Psát testy po naprogramování je jen pro formu, že se to má (?) - asi jako dopsat osnovu ke slohu.

Asi je take zbytecne psat testy k metode getName(), i kdyz i tam jsem zazil hledani chyby.

Ja osobně nesnáším, když mám dělat něco tupého dvakrát po sobě. Takže napsat test, který něco otestuje za mě, je ideální. Nakonec vždycky zjistím, že se mi to vyplatilo. Ono několikrát volat parsování stejným způsobem je otrava.

Vždycky tvrdím, že programátor má rozum od toho, aby ho používal. Když tuším, že implementace fičury se nepovede hned napoprvé, napíšu testy a implementuju to jednodušší. Když to dodělám, začnu složitější a vím, že nerozbourám to hotové. Taky při refaktoringu se mi to vyplatilo.

Po testování GUI teskním už dlouho, protože vyvolat aplikaci, zadat heslo, zavolat správnou úlohu, naťukat číslo smlouvy, jméno a pár drobností, to je nad moji trpělivost. Proto se mi líbil FEST testing.

Takze zaver: s rozumem...
Poznamenavam, ze nemam takove zkusenosti jako vy. Sil.

Jan Novotný řekl(a)...

Ona je vůbec velmi zajímavá definice, která rozlišuje unit a integrační test. Jedna je např. tady:

http://xunitpatterns.com/Unit%20Test%20Rulz.html

Podle Roda Johnsona např. už ale není unit test ten, který spoléhá na konfiguraci Springu - tzn. nahazuje aplikační kontext.

Potom by ani testy MVC typicky nebyly unitové, protože většina z nich ještě spoléhá konfiguraci, která je někde mimo danou testovanou třídu.

Ale to je fakt už tahání se o slovíčka a to nechci a je to i zbytečné.

Je zajímavé, jak ani po 10 letech od vzniku JUnitu není v plno věcech jasno a každý si na to postupně přichází sám.

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

Co týká informací o testování, tak nenechám dopustit na knížku JUnit recipes. Sice už trochu starší a hlavně pro JUnit3, ale i tak se i teď hodí.

Unknown řekl(a)...

Vyborny clanek a reakce, dik za prehled.

Tomáš Záluský řekl(a)...

První odstavec článku mi dost mluví z duše. Taky pracujeme na "normálním jednorázovém" projektu. V našem případě jsou překážky pro psaní testu ještě umocněny nedostatečným odděleným domain, dao a business vrstvy a používáním proprietárních knihoven. Odhady v projektu sice počítají s testováním, které provádějí testeři, ale už neuvažují kombinatorickou složitost, která zahrnuje interakci nových vlastností s potenciálně velkým množstvím současných vlastností, a kterou už lidská síla zpravidla tak dobře neotestuje.

Nicméně musím přiznat, že tato situace mě naučila lépe rozlišovat, co za napsání testu stojí a co ne, a především nedívat se na testy rigidně, ale spíš pragmaticky. Občas mi přijde, že se lidé motají ve filozofování o definicích, co je a co není unit resp. integrační test (např. zda je to v junitu, zda je to závislé na db, xml atd.). Např. mám implementovat složitou logiku, u které je popsáno, co má být ve výsledku v databázi, a mám k dispozici jen čisté JDBC. Pravděpodobnost jejího znovupoužití se blíží nule. Pak mi přijde menší zlo porušit pravidlo "test nesahá do db". Oddělovat to na vrstvy a testovat je zvlášť by bylo možná čistší, ale jednak dražší a především test má sloužit kódu a ne kód testu.

Jira řekl(a)...

Nějak se má reakce rozrostla .. takže na unit testy si čas vždy udělám.

Anonymní řekl(a)...

Myslim, ze otazka z nazvu by mela byt obracena: mate cas na vyvoj bez UT? na ladeni bez UT?
Ja delam ve firme, kde se v podobe projektu rozsiruje jedna velka aplikace a za ty roky na ni jsou tisice integracnich a GUI testu. Pred par lety jsme zacali i s TDD pomoci UT tam kde to davalo smysl a myslim, ze to melo jednoznacny prinos. Vzhledem k tomu, ze preklad potrebnych knihoven a spusteni aplikace trva asi 90s, tak se z ladeni stava peklo. Na druhou stranu, test je spusten za par sekund a odladim si to v nem stejne a aplikaci uz spoustim jen pro kontrolu nakonec.
Vetsinou pouzivam spis pristup ten, ze zkusim napsat funkcionalitu, pak test a pak to teprv poustim, takze zhruba paralelni jak uz bylo zmineno. Test zabere tak 10-20min a pak se lehce stane, ze jeste hodiny trva, nez to zacne fungovat (po mnoha desitkach spusteni). Takze uz behem vyvoje se ten cas lehce vrati, nehlede na lepsi navrh, udrzovani a vicemene i priklad pouziti.

Anonymní řekl(a)...

Priznam se UNIT testy moc nepisu, ale na takove pekne tridy jako Utils, Tools apod si je rad udelam, a zatim vzdy kdyz jsem si nasel cas na napsani UNIT testu jsem byl za nej rad a v dusledku mi ten cas naopak usetril.
Uz v psani Unit testu jsem si uvedomil veci, ktere mi pred tim nedosli.
Pri zmene kodu se pak jednoduse overi ze funkcnost se pozadovana nezmenila...

Anonymní řekl(a)...

to IA. 90 sekund je opravdu nesnesitelna doba. Pamatuju doby, kdy jsem preklad mel za dve hodiny vcetne skemrani, aby me provoz nekam vsunul - EC 1030.

Anonymní řekl(a)...

Unit testy typu setName/getName nedelam. Velmi se mi ale osvedcili dukladne integracni testy bezici pres noc.

Anonymní řekl(a)...

Plat vyvojare: 70 000
Plat testera: 20 000

Proto je mnohdy automatizovany test ekonomicky neprinosny. Naprogamovani testu je mnohdy mnohem delsi nez manualni otestovani celeho systemu. Problem je v tom, ze onene test vyviji velmi drahy zamestnanec.

Tim nemyslim, ze testy nepstat. Ano psat, ale zvazit kdy se to jeste skutecne vyplati.

Jira řekl(a)...

Opakovat automatizovaný test: 0 Kč
Opakovat manuální test: > 0 Kč

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

Přidávám odkaz na anketu "Do We Need Unit Testing?".

Podle různých průzkumů adopce agilních metodik vzrůstá, ale asi se vybírá jen to, co se hodí. Pěkný článek na toto téma je zde.

Anonymní řekl(a)...

ad plat ~ pozor stagnace
http://ktp.istp.cz/charlie/expert2/act/h3-karta14.act?id=5823&is=1&lh=0

http://ktp.istp.cz/charlie/expert2/act/h3-karta26.act?id=5823&is=1&lh=0