tifyty

pure Java, what else ?

Nincs felesleges teszt

Most nem lesz mintakód, és nem lesznek nepáli kecskepásztorok. Most csak arról fogok elménckedni, hogy mi is igazából a unit teszt.

Amikor Java-ról beszélünk, akkor unit teszt alatt leginkább a JUNIT teszteket értünk, amelyekkel metódusokat, osztályokat tesztelünk. Általában egy osztály egy junit teszt osztály a megfeleltetés, és minden egyes ellenőrizendő (public) metódusra legalább egy teszt metódus készül. Az osztály működése során a használt más osztályokat pedig általában mókoljuk, amire ott az EasyMock, a Mockito, vagy a PowerMock … Van sok. Az én kedvencem a Mockito, mert szimpatikus az apija. Az EasyMock esetében éreztem néhány helyen, hogy nem thread safe. Nem másztam bele, csak olyan hívási konstrukciók voltak, amelyek szaglottak valahogy. Ott sem mindenki mos lábat.

Elég sok unit tesztet írok. De csak olyankor ha csapatban dolgozom. Ha valami hobbi projekt ragad magával, akkor a legritkább esetben írok unit tesztet. Minek is írnám? A hobbi projektek jellemzője, hogy nem csapatmunka, egy ember készíti, magának, így aztán minden egyes modul pontosan illeszkedik egymáshoz, és nem kell állandóan refaktorálni a változó igények miatt, hiszen minden egy ember kezében van. (Persze ennek megfelelően ne legyen illúziónk a hobbi projektek valós és közvetlen értékével kapcsolatban sem.) Ha pedig minden egy kézben van, akkor a ‘unit’ lehet magasabb szinten, írhatok rögtön integrációs tesztet, ahol az egyes osztályok egymást és nem egymás mókjait hívják. Ettől persze ez még ugyanúgy JUNIT, és néhol zümmög benne egy-egy Mockitó is nyáresti éjszakákon.

Az ilyen integrációs tesztnek az a hátránya, hogy ha megváltozik és eltörik egy ‘A’ osztály, amelyiket használ a ‘B’, akkor szinte biztosan el fog törni a ‘B’ tesztje is, hiszen a ‘B’ a tesztek közben is használja az ‘A’-t. Ha meg mégsem törik el, akkor nem eléggé teszteli az integrációt a teszt. És ha eltört ‘A’ is, meg ‘B’ is, akkor lehet keresni, hogy ki a hunyó. Csapatmunkában több a lehetőség az egymásra mutogatásra.

A unit tesztnek pedig pont az ellenkezője a hátránya: lefut ‘A’ is, meg ‘B’ is, csak éppen már nem működnek együtt. Szóval az integrációs teszt elkerülhetetlen. Szerencsére a mókolás nem olyan egyszerű, és ezért a fejlesztők hajlamosak unit teszt helyett integrációs teszteket írni, így sok esetben a dolog automatikusan megvalósul. És ha van elég integrációs teszt, akkor a unit teszt nem is hiányzik. Feltéve, hogy elegendően jó az együttműködés a csapatban megkeresni a törött kódot legyen az ‘A’-ban, avagy ‘B’-ben. Elvileg az kellene, hogy legyen, hiszen egy agilis csapatban a kód mindenkié, nincs olyan, hogy ezt a részt te javítod, mert te írtad, azt meg én, mert azt én írtam, és letöröm a kezed ha belenyúlsz.

És ha már teszteket írunk, akkor jön a kérdés, hogy írtunk-e elegendő tesztet. A válasz persze egyszerű: nem. Ha ezt a választ adom, akkor igen jó eséllyel sikerül eltalálnom a helyes választ, hiszen hibátlan szoftver nincs, és ha előjön akár üzemelés közben egy hiba, akkor az a funkció, amelyik törött bizony nem volt eléggé tesztelve. Vagyis nem írtunk elég tesztet.

Az élet azonban nem ilyen egyszerű, mert ha ilyen egyszerű lenne, akkor most is még az első assembly programot tesztelnénk, hiszen még mindig lehet benne hiba. A kérdés az, hogy elegendő sok tesztet írtunk-e ahhoz, hogy ha még egy tesztet írok, akkor a szoftver kevesebbel több hasznot hoz, mint amennyibe a teszt elkészítése kerül. Megtérül-e a az újabb unit teszt határköltsége? Ha nem térül meg, akkor minden további teszt írás veszteséget okoz.

Az a szuper ezekben a megközelítésekben, hogy olyan egyszerűek, és átláthatóak, és annyira triviálisak. Csak éppen ki mondja meg, hogy mennyivel több hasznot hoz a szoftver. Az első évben? Az első két évben? Tíz év alatt? És mennyi üzleti eredményt jelent egy elégedett látogató a web oldalon? Vagy mennyivel kevesebbet egy frusztrált? Milyen eloszlásban? És milyen kamatlábbal kell rediszkontálni arra az időpillanatra, amikor a plusz egy teszt megírásának a költsége felmerül? Ilyenkor persze jön a sacimetria, amivel ugyanúgy bekötött szemmel lövünk célba a sötétben, de legalább nem előbb lövünk, és utána rajzoljuk a koncentrikus köröket a luk köré. Egy kicsit nagyobb az esélye, hogy nem lőjük le a nagymamát, de azért a gyerek fején az almát nem próbálnám meg eltalálni így sem. És még szóba sem jött, hogy mibe kerül a plusz egy teszt megírása…

Szóval akkor jön a jó öreg “így szoktuk”, meg a “nálunk így szokás”, és mérjük a kódlefedettséget és elérjük a 60%, 80% meg 98%-ot. Jó esetben. Sajnos az a tapasztalatom, hogy amikor azt kérdezem a mai magyar IT iparban dolgozó szakemberektől, akik jönnek hozzám interjúra, hogy írnak-e unit tesztet, akkor többnyire az a válasz, hogy “hallottam már róla, hogy van olyan”. Lefedettség? Hol élsz?

Mennyi is legyen? 60% már jó? Vagy legyen 80%? Esetleg célozzuk meg a 100%-os lefedettséget? Mit mond a cég policy? Nálatok mit mond? Van?

Amikor elkezdtem az EMMAecl eszközzel nézni a lefedettséget azt találtam, hogy a lefedettség mérése nem is azért olyan jó, mert mutatja, hogy mennyire sok tesztet írtam, sokkal inkább azért, mert a piros, sárga és zöld sorok az Eclipse-ben pillanat alatt megmutatják, hogy mely eseteket nem teszteltem le. Direkben persze a sorokat mutatják, amerre a teszt futkorászott, vagy éppen nem, de a fejlesztő abból pillanat alatt látja, hogy melyek azok a funkciók, amiket még ki kell próbálni, és melyek azok, amelyek már le vannak fedve. Végül is: a kód struktúrája és a funkcionalitás összefügg. És persze minek letesztelni a generált settereket, gettereket? Nem kell. Azaz: nem kellene.

Aztán ahogy elkezdett nőni a projekt, és néztem, hogy melyik csomag mennyire van tesztelve, és amelyikben sok getter, meg setter, meg egyéb generált kód volt és ezért alacsony volt a lefedettsége az hátrébb került, és egyszer csak azt vettem észre, hogy ezek között megbújt egy olyan csomag is, amelyiknek bizony kellett volna több teszt. És akkor lefedtem a settereket, és a gettereket is általánosan, reflection-nel, csak azért, hogy ne tudjon elbújni egy kevéssé tesztelt csomag. Ez az amikor a fapapuccsal verjük be a szöget. Csak éppen most nem volt kalapács. Persze értem, hogy nem ez lenne a jó megoldás, hanem az, ha meg tudnám jelölni, hogy mely sorokat vegyen be a lefedettség mérésbe, és a lefedettség mérés azt modaná meg, hogy hány százaléka van lefedve a lefedendő kódnak. De ilyen tool nincs. És nem csak nincs, ismereteim szerint, hanem kérdéses az is, hogy mennyire lenne használható. Megjelölni a le nem fedendő programrészeket majdnem akkora munka, mint megírni a generált kódra a generált tesztet. És a tesztnek van egy előnye, ami ha ritkán is (nekem eddig egy alkalommal) de kifizetődik.

Történt egy bús délután, hogy valami funkcionalitás belekerült egy getterbe. És el lett rontva. És persze nem az volt, hogy meg volt jelölve, hogy ezt nem kell tesztelni, mert akkor nem lett volna tesztelve, no meg mert, mint arról épp az imént volt szó nem ismerek olyan túlt ami ezt tudná. Volt rá teszt. És elhasalt. Én meg kapkodtam a fejemhez, és levontam a tanulságot, hogy nincs felesleges teszt.

Az is egy jó kérdés, hogy miért a kód lefedettséget nézzük. A unit teszt az white-box vagy black-box teszt? Ha white-box, akkor rendben van a dolog, de akkor miért nem teszteljük a privát metódusokat? Ha viszont black-box, azaz nem érdekel minket, hogy hogyan működik, akkor miért a kód lefedettséget mérjük? Miért nem a funkcionális lefedettséget nézzük? Na, ez a kérdés is meg szokta akasztani a fiatal kollégákat. A válasz pedig egyszerű: mert erre van egyszerűen használható és könnyen kezelhető eszköz. Lehet ugyan mérni a funkcionális lefedettséget, de ahhoz formálisan le kellene írni a funkciókat, és valamilyen módon azt is, hogy melyik teszt melyik funkciót teszteli. Persze vannak ilyen eszközök, de ez inkább teszt eset adminisztrációs túl, mintsem mérés.

Ezért aztán a kód lefedettséget mérjük. Nem olyan nagy baj ez. Annak idején, amikor a BME-n voltam óraadó, és az egyik hallgató panaszkodott, hogy rossz jegyet kapott, pedig tudja a tárgyat csak nem jól oldotta meg a feladatot: nem jól méri a ZH a tudást és ez nem igazságos — azt feleltem neki, hogy tudok róla. Az élet nem igazságos, és nem is szándékozom a tudását mérni, mert ugyan jó lenne, csak éppen nem vagyok rá képes. Persze arra vagyok kíváncsi, de mérni nem tudom. Helyette mérek valami mást. És reménykedem, hogy amit mérek, és amire kíváncsi vagyok, azok összefüggnek egymással. Vagy legalább korrelálnak. Sok esetben még azt sem. Ha korrelálnak, akkor már szerencsés vagyok. És ez az élet más területein is így van, kezdve a házasságtól a parlamenti választásokig. Valamit látunk, mérünk, és az alapján döntünk, aztán reménykedünk, hogy jó legyen.

A tesztelés sem más. Mérjük a lefedettséget, és azt gondoljuk, hogy ha jó a kód lefedettség, akkor jó lesz a funkcionális lefedettség is. Garancia nincs. Sok esetben jól járunk, sok esetben nem. De a kódlefedettség és a funkcionális lefedettség legalább korrelál.

Lehet olyan, hogy nem teljes a funkcionális lefedettség, de a kódlefedettség teljes. Ilyenkor kell a hiba kiderülése után új unit teszteket írni, és persze javítani a kódot (ebben a sorrendben). De az is lehet, hogy a funkcionális lefedettség teljes és a kód lefedettség mégsem éri el a 100%-ot. Ilyenkor van az, hogy felesleges kód van a programban. Vagy olyat fejlesztettünk, ami YAGNI (you are not going to need it). Ez is megért egy misét.

Szóval: nincs felesleges teszt. Írd meg a tesztjeidet, javítsd ki a bugokat, ragaszd meg a törött bildet, és aludj jól!

11 responses to “Nincs felesleges teszt

  1. Rév Tamás október 16, 2012 9:15 du.

    Jó cikk ez is. Csak van egy olyan érzésem, hogy pont a könnyebbik felén fogjuk meg a dolgot: új kódhoz elég könnyű teszteket írni – csak meg kell tanulni tesztelhető kódot írni. Nade, mennyi unit tesztet írsz össze-vissza egymástól függő legacy kódban? Illetve: hogyan írsz hozzájuk unit (vagy bármilyen más) teszteket?

    • Takacs Otto április 2, 2013 4:39 du.

      Nem unit tesztben kell gondolkodni, hanem teszt automatizálásban. Mivel ha valahogy (!) automatizáltad a tesztelést, akkor már refactorálhatod is, hogy unit tesztelni is tudd.

  2. István Viczián (@vicziani) október 16, 2012 9:46 du.

    Nagyon jó írás! Különbözik az eddigiektől, abban, hogy ez több kérdést vet fel, mint megválaszol. 🙂
    Az, hogy ki rontott integrációs tesztnél, szerintem egyértelműen meghatározható. Akinek a commit-ja eltörte a build-et. Lehet, hogy rosszul működő osztályt használt, de mivel ő törte el, biztos ki sem próbálta a kódját, tehát az ő hibája.
    Unit/integrációs teszt nálam örök dilemma, és nem jutok egyről a kettőre. 🙂 Pl. egy dao metódust szerintem kizárólag integrációs tesztelni lehet, ahol a select-et ronthatod el, vagy az annotációkat. Ezek unit tesztelésnél nem jönnek ki. (De erről már értekeztem a java listán. 🙂
    Exclude van osztály szinten mindegyikben, a Cobertura-ban ignore regexp, ami metódusra is illeszt, valamint a Jacoco-ban tervben van, elég jó kis draft:
    https://github.com/jacoco/jacoco/wiki/FilteringOptions

  3. István Benedek október 16, 2012 10:07 du.

    Sajnos ONE felesleges teszt, ennek is mint sok masnak van egy egyensulyi pontja, ahol az ujonan hozzaadott tesztek mar nem szolgaljak a projekt erdekeit, ahogy emlitetted is az otthoni projektnel ez pontosan a 0 darab teszt. Igazabol az tetszene nekem ha a lefedettseg sulyozva lenne a programozasi hibak kodban valo elofordulasaval korrelalo tenyezokkel (pl. cyclomatic complexion), illetve a suly a standard getterek, setterek es egyeb eseten 0 volna, mert igy este 10 fele az a hulye gondolatom tamadt, hogy a generalt getterek setterekhez ha nem nyulsz akkor az hibas mukodesenek varhato erteke pont 0. Szoval en meg azt mondanam csinjan a tesztekkel…
    OFF: Nem akarnal mar egy jo cikket irni a java securityrol, amiben van minden okossag, amit mar holnap hasznalhatnek? Vagy hol kell cikket rendelni? Kuldenem a baratoknak, meg a munkatarsaknak:)

  4. Gábor Garami (@hron84) október 16, 2012 11:42 du.

    En meg a hobbiprojektben is csinalok teszteket, ha az bonyolultabb egy hello, worldnel, vagy egy huszsoros scriptnel.
    A pollra pedig 60%-ot nyomtam, mert jelenleg ennyi a kodjaim lefedettsege, de amikor van egy kis idom, akkor mindig irok teszteket a mar kesz kodokhoz is, mert nekem a szemem elott a 80% lebegne… csakhat amikor epp “ihletett” allapotban vagyok, akkor nem unalmas teszteket akarok irni.

    • tvik október 17, 2012 9:40 de.

      Jó cikk. Csatlakozom az előttem szólóhoz, én is írok hobbiprojekthez is unit tesztet. Unit tesztet nem kellene hogy nehéz legyen csinálni, ergó olcsó kellene hogy legyen, ergó elvileg mindig érdemes kellene hogy legyen írni. 80%-nál szokott járni a lefedettség nálam. A maradék 20%-ban általában olyan kódrészek vannak, amik esélyes hogy le lesznek cserélve a közeljövőben.

      A unit teszt nem csak annak a tesztelése, hogy a kód jól működik-e, hanem a koncepció tesztelése, hogy a kódot tényleg jól fel lehet-e használni. Szerintem egyébként inkább az utóbbi.

      Ha nehéz unit tesztet írni, ott baj van a kódrésszel amit tesztelni kell. Ha legacy kód akkor ez van, ezt kell szeretni. Ha saját kód, ott refaktorálni kell.

      Mondjuk én nem nagyon használok mock frameworköt. Szinte mindig van interfész a tesztelendő service jellegű osztályhoz és azt terjesztem ki teszteléshez. Van ezzel a megközelítéssel valami probléma?

  5. athoshun október 25, 2012 11:40 du.

    Jo iras!

    Annyit fuznek hozza, hogy a unit teszt vs. coverage kerdes szerintem olyan, mint a csukott szemmel celbaloves: eloszor losz black-box, aztan leveszed a kendot, hogy lasd, mi lett a vege. Maskepp nem tudod meg, hogy megmurdelt-e mar a macska, csak ha kinyitod a dobozt, ha ugy tetszik. 🙂 Mas szoval: nem latok elvi problemat azzal, hogy ha innen nezem, black, ha onnan nezem, white.

    Privat metodusok: az igeny a privat metodusok tesztelesere szvsz leginkabb akkor jelentkezik, ha tul sok logika van azokban a metodusokban. Ilyenkor altalaban egy-ket absztrakcios retegnyi osztalyt ki lehet emelni az eredetibol, a kiemelt osztalyoknak pedig maris publikus metodusai lesznek a kerdeses metodusok.

    Mellesleg a tesztet es a production kodot szerintem nem erdemes a minimalisnal jobban osszedrotozni egymassal, mert minel kevesebb ponton fugg a teszt kozvetlenul a kodtol, annal konnyebb refaktoralni. (A trukk itt a lo tuloldalara valo atesesnel van.)

    A funkcio lefedettseg is jo tema. Ugy tekintek ra, hogy a kodom tesztjei tartalmazzak az osszes kovetelmenyt, sot viselkedest, amit a kodom (szandekaim szerint) produkal, ebben kozrejatszik az is, hogy miota ismerem, gyakorlom a TDD-t is. Ha egy eset nem szerepel a tesztben, akkor az azt jelenti, hogy arra az esetre nem definialt, hogy mit fog reagalni a kodom. Ha ezek utan a kollega olyan esetekre ohajtja hasznalni, ami nem szerepel a unit tesztben, akkor javasolt, sot elvart batran kiegesziteni az altala ahitott esettel a teszteket, es ha netalan torik, akkor a production kodot is kiegesziteni, mikozben a tobbi teszt nem valtozik. Esetleg rajohet, hogy ez a kod nem is arra valo, amire hasznalni szeretne. Nyilvan bug ezek utan is maradhat a szoftverben, de a “ja, hogy ez eredetileg nem tamogatja a negativ szamokat” jellegu meglepetes talan kevesebb igy, a cel pedig alapvetoen ez.

    Egyebkent a BDD-nek van egy-ket olyan elgondolasa vagy filozofiaja, ami “hagyomanyos” unit tesztelesnel is igen hasznos tud lenni.

  6. Attila Magyar (@zeroflag) október 26, 2012 12:11 de.

    Hobbi projecthez mindig irok unitteszteket , es nem is annyira a teszteles, hanem a designnal kapcsolatos visszajelzes miatt. Sokat lehet tanulni abbol ha elgondolkodik az ember arrol hogy vajon miert tesztelheto szarul, vagy sehogy egy-egy objektum.

  7. Rafael Devill október 27, 2012 8:37 du.

    Sok erdekes gondolat van benne.

    Az Athos altal emlegetett TDD bizonyos ertelemben megvalaszolja azt a kerdest, hogy mennyi tesztet kell irni uj kodra, de legacy kapcsan validak a kerdesek.

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s

%d blogger ezt kedveli: