Ph: 10242008

Mock testing - Potěmkinovy vesnice

Řada z vás možná už na výraz Mock testing narazila, nÄ›kteří ne. Pro ty z vás, kteří Mock přístup v testování nepoužili je tento Älánek. Pro ostatní může být zajímavá ukázka této techniky na knihovnÄ› EasyMock.

Co jsou to mock objekty?

Jedná se vlastnÄ› o techniku psaní urÄitého druhu automatických testů. V podstatÄ› se jedná o nahrazení reálného objektu testovací fasádou, která neprovádí žádnou funkcionalitu nahrazovaného objektu - jen se jako tento objekt tváří. Místo původní logiky objektu je vloženo chování, které ve svém testu potÅ™ebujete.

Nejlepší bude teorii provázat s praxí. Pokud píšete automatické testy, jistÄ› jste narazili na situaci, kdy k napsání jednoduchého testu musíte velmi složitÄ› a pracnÄ› pÅ™ipravovat okolní podmínky. NapÅ™. testujete metodu v business objektu, který se dotazuje internÄ› DAO objektů na data v databázi. To ale znamená, že pÅ™ed tím, než zaÄnete testovat logiku tohoto business objektu, musíte pÅ™ipravit správná data v databázi. Musíte také zajistit, že se tam omylem nedostanou další data, která by test zhatila (napÅ™. v SELECT dotazu vrátila více řádků) a tudíž obvykle zase musíte po sobÄ› uklízet. OÅ¡etÅ™ení okolních podmínek spuÅ¡tÄ›ní testu vás nakonec může stát nÄ›kolkrát více Äasu, než potÅ™ebujete k napsaní vlastní testovací logiky.
Existují různé techniky, jak toto zajistit - setkal jsem se napÅ™. s použitím HSQL databáze, která byla celá v pamÄ›ti a po každém testu se kompletnÄ› dropla a pÅ™ed novým opÄ›t vytvoÅ™ila a dosadila se přísluÅ¡ná testovací data (tím, že se vÅ¡e odehrávalo pouze v pamÄ›ti byly testy velmi rychlé). OvÅ¡em lehÄí je dle mého názoru právÄ› použít techniku mock objektů.

PÅ™i použití této techniky se soustÅ™edíte na testování pouze logiky business objektu a pÅ™edpokládáte že okolní objekty fungují správnÄ› tak jak mají. Potom v naÅ¡em příkladÄ› za DAO objekty dosadíte pouze “prázdné” ulity, které mají stejné rozhraní, ale místo vlastní logiky obsahují logiku takovou, kterou potÅ™ebujete pro vlastní test. Tzn. ve chvíli, kdy se business objekt zeptá DAO na konkrétní data, neprobÄ›hne dotaz do databáze, ale vy mu rovnou vrátíte data, která v daném testu potÅ™ebujete.

To je celý princip mock objektů. Nic víc, nic míň. Pokud se chcete dozvÄ›dÄ›t nÄ›co víc, pokraÄujte ve Ätení.

Stub objekty a Mock objekty

Pokud chcete použít tuto techniku při testování, potřebujete vyřešit dva problémy:

možnost nastavit testovanému objektu mock objekt zvenÄí - princip IoC
V podstatÄ› to znamená, abyste mohli nahradit původní reálný objekt náhražkou napÅ™. pÅ™es setter metodu, konstruktor nebo jako parametr testované metody. Velmi prosté, ale v nÄ›kterých případech, kdy nemáte pod kontrolou API testovaného objektu, to vyluÄuje samotné použití této techniky.
možnost nahradit originální logiku nahrazovaného objektů testovací logikou - princip OOP
Tzn. testovaný objekt nesmí poznat, že pracuje s náhražkou. Musí být zachováno rozhraní původního objektu a objekty musí být typově stejné.
To se dá v JavÄ› udÄ›lat jen dvÄ›ma způsoby: dÄ›dÄ›ním nebo implementací interface. Vytvářet mocky dÄ›dÄ›ním bych nedoporuÄoval, jelikož potom máte v mock objektu fragmenty původní logiky, což vám to může mnohokrát akorát celé zamotat. Ideální je tedy nadefinovat rozhraní, se kterým testovaný objekt pracuje a dané rozhraní je implementováno dvÄ›mi třídami - tou reálnou a faleÅ¡nou “mock” implementací. Je to trochu pracnÄ›jší způsob, ale Äistší.

Ve vlastním testu potom jednoduše vytvoříte testovaný objekt, nastavíte mu místo původní reálné implementace testovací náhražku a zajistíte, aby ta vracela data, která ve svém testu požadujete.

Můžete se vydat cestou tzv. stub objektů - což znamená že vytvoříte fyzicky třídy, které obsahují logiku, kterou od nich požadujete v testech. Je to asi jednodušší cesta pro někoho, kdo s tímto způsobem testů ještě nepřišel do styku, ale cesta dosti pracná.

Dalším vývojovým stupněm jsou tzv. mock objekty. Jejich použití je možná obtížnější, zato přinášejí jednu zásadní výhodu. Dynamické mock objekty jsou vytvářeny přímo testem a existují pouze virtuálně. Tzn. není třeba fyzicky implementovat vlastní mock třídu a navíc vám zdarma poskytnou řadu funkcionality, kterou byste do svých stub objektů pracně implementovali. Příklad použití mock objektů si ukážeme na použití knihovny EasyMock.

EasyMock knihovna a vytvoření jednoduchého testu s použitím mock objektu

Dejme tomu, že máme tento business objekt, který chceme testovat.

BusinessObject zdrojový kód

Ten používá ke své Äinnosti dvÄ› rozhraní - IMessageSender a ILogger. Vlastní logika business objektu je velmi prostá, pokud dojde Äíslo menší jak 10, poÅ¡le email příjemci jedna. Pokud je Äíslo vÄ›tší, poÅ¡le mail příjemci dva. Pokud se nepodaří odeslat email, tuto chybu zaloguje. Uvedená dvÄ› rozhraní jsou zobrazena níže.

IMessageSender - zdrojový kód

ILogger zdrojový kód

Výše uvedenou logiku bychom normálnÄ› obtížnÄ› testovali, ale s použitím mock objektů to bude triviálnÄ› jednoduché. Kompletní zdrojové kódy jako Maven 2 projekt naleznete na konci Älánku. V nÄ›m je implementace testů jak pomocí Mock, tak i Stub objektů. Nuže, jak vypadá testování s knihovnou EasyMock?

Test první:

V prvním testu chceme otestovat, že pokud je vstupní hodnota 1 odešle se email prvnímu příjemci a zároveň se nezaloguje žádná chyba (odeslání se podaří).

Test1 zdrojový kód

Ve výše uvedeném kódu je několik zajímavých míst.

#1 - zde se vytváří virtuální mock objekt implementující specifikované rozhraní; fakticky zde v tuto chvíli knihovna EasyMock vytvoří novou třídu implementující vaše rozhraní a instanciuje objekt této virtuální třídy;

#2 - zde zavoláme metodu naÅ¡eho rozhraní stejnÄ›, jako bychom to udÄ›lali s reálným objektem; v tomto případÄ›, vÅ¡ak pouze instruujeme mock objekt, že má oÄekávat (po odstartování skuteÄného testu - viz. bod #3) volání metody send s uvedenými parametry

#3 - zde řekneme knihovně EasyMock: až do tohoto bodu jsme programovali tvé chování pro daný test; od této chvíle se chovej dle našich instrukcí (viz. bod #2)

#4 - nastavíme mock objekty do testovaného objektu - tzn. místo původních reálných implementací mu podstrÄíme kukaÄÄí vejce v podobÄ› naÅ¡ich mock objektů

#5 - zavoláme testovanou metodu business objektu - ta proběhne standardním způsobem a business objekt při svém chodu zavolá metodu rozhraní IMessageSender na našem mock objektu - volání proběhne bez problémů, tudíž si business objekt myslí, že vše proběhlo v pořádku (náš mock objekt se tvářil jako reálný objekt)

#6 - verifikace mock objektů - toto je hlavní pasáž mock testování; zde EasyMock prověří, zda celá akce probÄ›hla tak, jak jste ho pÅ™ed replay fází nauÄili; tzn. ověří, že mezi replay a verify doÅ¡lo k jednomu volání metody send na messageMock objektu a že vstupní parametry odpovídaly (equals) vámi definovaným hodnotám z bodu #2; pokud by k volání nedoÅ¡lo, nebo doÅ¡lo vícekrát, Äi nÄ›která z hodnot se liÅ¡ila od vámi specifikovaných, dojde zde k vyjímce a celý test selže; taktéž si povÅ¡imnÄ›te že souÄástí verify je i loggerMock objekt, u kterého jsme neprovádÄ›li žádnou “instruktáž” - v takovém případÄ› ve fázi verify ověří EasyMock knihovna, že nebyla zavolána žádná metoda tohoto rozhraní (což je dobÅ™e, protože v takovém případÄ› by se business objekt zachoval Å¡patnÄ›)

Test druhý:

V druhém testu chceme simulovat chybu odeslání zprávy. Objekt implementující IMessageSender rozhraní by v metodě send měl vyvolat chybu SendException. Business objekt by na to měl reagovat zalogováním chyby rozhraním ILogger.

Test2 zdrojový kód

#1 - vytvoříme business objekt

#2 - opět vytvoříme virtuální mock objekty

#3 - nyní instruujeme EasyMock, že má oÄekávat volání metody logError na objektu loggerMock - pÅ™iÄemž první parametr musí být ne null objekt (String) a druhým parametrem má být objekt pÅ™etypovatelný na třídu SendException (v této Äásti vidíte, že argumenty nemusíte uvádÄ›t exaktnÄ› pÅ™esnÄ› - EasyMock poskytuje abstraktnÄ›jší výrazové prostÅ™edky)

#4 - zde instruujeme EasyMock, že má oÄekávat volání metody send na objektu messageMock s konkrétními vstupními hodnotami (obdobnÄ› jako v testu 1)

#5 - zde je další novinka, zde říkáme EasyMocku aby v posledním volání (viz. bod #4) vyhodil jako výsledek volání exception SendException (obdobně bychom mohli EasyMocku říct, aby poslední volání vrátilo konkrétní návratovou hodnotu záměnou andThrow voláním metody andReturn)

#6 - nyní řekneme knihovně EasyMock: až do tohoto bodu jsme programovali tvé chování pro daný test; od této chvíle se chovej dle našich instrukcí
#7 - nastavíme mock objekty do testovaného objektu - tzn. místo původních reálných implementací mu podstrÄíme kukaÄÄí vejce v podobÄ› naÅ¡ich mock objektů

#8 - zavoláme testovanou metodu business objektu - ta probÄ›hne standardním způsobem a business objekt pÅ™i svém chodu zavolá metodu rozhraní IMessageSender na naÅ¡em mock objektu - pÅ™i volání metody send náš mock vyhodí SendException a náš business objekt by se mÄ›l patÅ™iÄnÄ› zareagovat zalogováním chyby

#9 - verifikace mock objektů - EasyMock ověří, zda na messageMock objektu byla zavolána právě jednou metoda send a parametry volání odpovídaly (equals) předpisu z kroku #4; dále ověří, že na objektu loggerMock byla právě jednou zavolána metoda logError s prvním ne nullovým parametrem a druhým parametrem přetypovatelným na SendException

Další možnosti knihovny EasyMock

Výše uvedené příklady prezentují pouze základní a jednoduché možnosti knihovny EasyMock. V druhé verzi má již pomÄ›rnÄ› rozsáhlé možnosti “imitace” a ověřování požadovaného chování. Například umožňuje parametry porovnávat ne jen na ekvalitu, ale má Å™adu výrazových prostÅ™edků z nichž jsme uvedli pouze dva (napÅ™. je / není null, test na regulární výraz, aritmetické operátory >,<,= nebo spojování podmínek pÅ™es logické operátory and, or). PoÄet volání metod lze definovat pÅ™esnÄ› (napÅ™. právÄ› jedno jako v naÅ¡ich testech) nebo v urÄitém limitu - alespoň jednou, od - do, libovolnÄ›. Umožňuje ověřovat poÅ™adí volání konkrétních metod a naopak. Dokáže vytvoÅ™it mock objekty v různých režimech - strict mock (oÄekává volání jen vámi pÅ™edepsaných metod - jinak generuje UnexpectedCallException), nice mock (umožňuje libovolné volání metod na tomto objektu - u neinstruovaných metod vrací "výchozí" hodnoty - null, 0 nebo false).

V druhé verzi je také novinkou Class Extension knihovna, která vám umožňuje generovat mock objekty nikoliv pouze na základě rozhraní, ale i na základě normálních tříd (tzn. není potřeba mít definovaný interface). Tento způsob vám také umožní nechat některé implementace metod mock objektů nepřekryté knihovnou EasyMock - tzn. budou obsahovat původní logiku reálné třídy.

Pozitivní dopady mock testování

Testy jsou pomÄ›rnÄ› velmi pÅ™ehledné, jednoduÅ¡e se píšou a uÅ¡etří vám plno práce s nastavováním prostÅ™edí pro test. Navíc vám umožní testovat funkcionalitu, která je jen velmi obtížnÄ› testovatelná (napÅ™. že váš skript odeÅ¡le v urÄitou chvíli email).

Negativní dopady mock testování

Je potÅ™eba si uvÄ›domit, že objekty, které nahrazujete se vlastnÄ› netestují. Použití mock objektů vám umožňuje vytváření izolovaných unit testů - ty jsou sice jednodušší a pÅ™ehlednÄ›jší, ale nemají takovou cenu pro vlastní testování jako testy integraÄní. IntegraÄní testy vám pomohou objevit chyby na úrovni rozhraní různých komponent - tzn. ne pouze, komponenta A se chová tak jak má, ale že komponenta B používá komponentu A jak má. Ve výsledku totiž uživatel aplikace nepracuje pouze s komponentami jednotlivÄ›, ale s celým systémem komponent a chyba komunikace mezi komponentami je pro nÄ›j úplnÄ› stejná jako chyba jedné konkrétní komponenty. ProstÄ› to nefunguje.

Zdroje

Zdrojové kódy příkladu ke stažení jako projekt Maven 2

Mock Objects - úvod do techniky mockování
EasyMock - knihovna pro podporu dynamických mock objektů (viz. příklady v Älánku)
jMock - alternativa k EasyMock, abyste si mohli vybrat ;)

Podělte se s ostatními:
Digg del.icio.us De.lirio.us Technorati
OhodnoÅ¥te Älánek:
Takovéhle Älánky už radÅ¡i ne!Nic nového pod sluncem.PrůmÄ›r - obsahuje zajímavé střípky informací.Hodnotný Älánek - lecos nového jsem se dozvÄ›dÄ›l.SkvÄ›lý Älánek - informace se mi dost hodí. (3 hlasů, průmÄ›rnÄ›: 5 z 5)
Loading ... Loading ...

10 reakcí to “Mock testing - PotÄ›mkinovy vesnice”

komofei:

Velmi pekny clanok.
Ale mam problem s pochopenim testu c. 2. Ako to dopadne pri verify() ? Zliha to,a lebo je to OK ? Nepochopil som presne ako sa zachova konstrukcia: expectLastCall().andThrow(new SenderException) . Vysvetli mi to niekto? Vdaka.

Novoj:

VÅ¡echny testy ve zdrojových kódech projdou bez chyby. V metodÄ› verify se skuteÄnÄ› ověřuje pouze to, co jsem uvedl v bodÄ› #6. Konstrukce “expectLastCall().andThrow(new SenderException())” říká pÅ™esnÄ› toto. Výsledek posledního instruovaného volání (což je v naÅ¡em případÄ› #4) bude exception, která je uvedená v parametru.

Tzn. když je zavolána objektem tested metoda performBusinessLogic (#8) a ten uvnitÅ™ svého vykonávání zavolá na messageMocku metodu send, EasyMock místo “simulace” vykonání vyhodí exception SenderException.

Obdobně by se dal mock instruovat k vrácení návratové hodnoty (pokud by metoda send měla nějakou návratovou hodnotu, jako že v našem ukázkovém příkladě nemá) deklarací (uvažujme boolean):

expectLastCall().andReturn(Boolean.TRUE)

Pokud by stále byly nÄ›jaké pochybnosti, doporuÄuji stáhnout si pÅ™iložený projekt a celé si to oddebugovat. Tam nejlépe poznáte, co se kdy jak volá a s jakými výsledky.

A propo. DÄ›kuji za kladnou reakci - taková vÄ›c ÄlovÄ›ka vždycky potěší.

Satai:

Ajaj, také jsem se chystal napsat něco o EasyMock. Objevil jsem je před nedávnem a dost mi zjednodušily život.
VÄ›tÅ¡inou testuji stav, ale nÄ›kdy je opravdu lepší testovat procesy a na to je EasyMock jako dÄ›laný. NaÅ¡el jsem i další knihovny jako je JMock, ale pÅ™iÅ¡ly mi příliÅ¡ komplexní pro 95% použití. A urÄitÄ› musím pochválit EasyMock za žikovné použití Javy5 v v nových versích knihovny.

Jsem Å edý » Blog Archive » Mock testing - PotÄ›mkinovy vesnice:

[...] PÅ™ed pár týdny jsem objevil skvÄ›lou knihovnu EasyMock. Chystal jsem se o ní nÄ›co málo blognout, ale Otec Fura mne pÅ™edbÄ›hnul. RozhodnÄ› si jeho post pÅ™eÄtÄ›te. Nazajde moc do hloubky, ale na první dojem to staÄí. [...]

Andrej:

zdrojaky ako png - to je pomerne velka vzacnost!

Novoj:

:) výsledek je lepší než jako zdroják vložený textově. Zkoušel jsem to.

Nicméně kompletní zdrojáky je možné si stáhnout jako ZIPko, takže pokud máte zájem o detaily, originály jsou k dispozici.

Old ShatterKuna:

Moc ty mock objekty nechápu. TeÄ mám nÄ›jaké automatické testy, které dynamicky vytvoří SQL dotaz a vyberou nÄ›co z databáze. Já pak potÅ™ebuju zjistit, jestli ten SELECT nÄ›co vrací nebo ne. TÄ›chto testů probíhá za sebou nÄ›kolik a vždy se hrabe do databáze a tahá to data, což dÄ›lá testy ponÄ›kud zdlouhavými, protože je tam hodnÄ› záznamů. Dá se pro tento problém použít nÄ›jaký mock objekt, který dobu provádÄ›ní zkrátí? Já si teda myslím, že ne, protože zde se jedná o kontrolu dat a ne o kontrolu přístupu k databázi. Mám pravdu?

Novoj:

Mock objekty se typicky nehodí na testování “datové vrstvy” - tzn. pokud potÅ™ebujete testovat, zda jste složil správnÄ› SQL dotaz, tak v takovém případÄ› vám nezbyde než se skuteÄnÄ› dotázat databáze a poÄkat si na reálné výsledky. S tím souvisí i nutná příprava testovacích dat.

Mock objekty se hodí na testování aplikaÄní vrstvy (viz. příklad s business objektem). Vrstva obsahující aplikaÄní logiku obvykle pracuje s datovou vrstvou, ze které získává data pro své operace. Za pÅ™edpokladu, že máme již datovou vrstvu otestovanou a funkÄní - můžeme si uÅ¡etÅ™it práci pÅ™i testování aplikaÄní vrstvy použitím mocků.

A) původní přístup:
chci testovat aplikaÄní objekt, ten se dotazuje datové vrstvy -> pÅ™ed vlastním testem musím naplnit data v databázi, tak aby, když se aplikaÄní objekt zeptá datového, vrátily správná data. Je to dost práce, ale testuju tím vlastnÄ› najednou nÄ›kolik vÄ›cí (integraÄní test): vlastní aplikaÄní objekt, objekty datové vrstvy, spolupráci aplikaÄního objektu s datovou vrstvou

B) přístup s mocky:
chci testovat aplikaÄní objekt, ten se dotazuje datové vrstvy -> v testu si vytvořím virtuální mock objekty datové vrstvy a nainstruuji jim jejich chování - tzn. Å™eknu jim, že až se jich aplikaÄní objekt zeptá, mají vrátit konkrétní testová data. Žádné dotazy do databáze neprobÄ›hnou. Výsledkem je daleko ménÄ› práce pÅ™i psaní testů a izolovaný (unit) test, pÅ™i kterém testuji jen a jen aplikaÄní objekt. SamozÅ™ejmÄ› musím nÄ›kde jinde otestovat zvlášť datovou vrstvu, abych zamezil chybám na tomto místÄ›. MyÅ¡lenkou je, že i když budu izolovanÄ› psát testy na více objektů (zvlášt aplikaÄní a zvlášt datová vrstvaj), zabere mi to ménÄ› práce a bude to lépe otestované, než kdybych se pokouÅ¡el psát pouze integraÄní (složité) testy. V unit testech mám totiž vÄ›tší možnosti jak otestovat i různé niance daného volání, což bych pÅ™i volání ob jeden objekt obtížnÄ› testoval.

Tomas Jurman:

Díky moc za super Älánek.

bjbj:

Test Ä. 1 chápu - je jasný a srozumitelný. V podstatÄ› testujeme if příkaz ve výkonné metodÄ› business objektu. Ale test Ä. 2 nechápu - jak autor Älánku správnÄ› píše, je to simulace, ne test. Kdy ho tedy spouÅ¡tÄ›t ? Když nepojede internet a spustÄ›ní business classy naostro by tedy způsobilo logování, pustíme místo toho simulaci (test Ä. 2) s tím, že pÅ™edpokládáme, že se spůstí logování a v tomto případÄ› prohlásíme test za úspěšný ? V takovém případÄ› ale test sám o sobÄ› závisí jeÅ¡tÄ› na okolním prostÅ™edí (stavu internetu) a sám se nemůže vyhodnotit jako úspěšný nebo neúspěšný - nebo je to úplnÄ› jinak ???

Nechte zde svůj komentář

[image] Opište prosím text z obrázku:


You are viewing a mobilized version of this site...
View original page here

How do you rate mobile version of this page?

Mobilized by Mowser Mowser