11.2. Server oldal tesztelése

11.2.1. Egységteszt

Az egységtesztek írásánál két módszert is követhetünk a teszt osztályaink létrehozására:

  1. A tesztelendő sztály mellett létrehozunk egy mappát (csomagot) és abban helyezzük el a teszt osztályokat

  2. Egy külön mappában, ahol követjük a tesztelendő osztályok strukturáját (uyganaz a csomag egymásba ágyazása). A Netbeans csak ezt támogatja.

Mind az Eclipse, mind a Netbeans támogatja az egység tesztek generálását, és grafikus felületen nyomon követhetjük a tesztek eredményét. Mi a fejelsztés alatt a Netbeans használtuk, és a fejlesztés ideje alatt a 6.9.1-es verziót használtuk. A teszt osztályokat elkészíthetjük kézzel vagy más alkalmazással is ilyenkor másoljuk a be egy mappába az src mappa mellé. Ha még nem létezik, akkor a Netbeans létrehozza a teszt osztályokat automatikusan. Hozzunk létre egy új fájlt! A kategóriák közül keressük ki a JUnit nevűt, majd válaszzunk a lehetőségek közül: Egy üres teszt, amit nekünk kell majd testreszabni, vagy teszt egy létező osztály alapján (ilyen az osztály nyilvános függvényei alapján elkészít egy teszt osztályt), vagy egy teszt gyűjteményt egy csomag osztályai alapján.

11.3. ábra - Tesztkészlet létrehozása Netbeans alatt

Tesztkészlet létrehozása Netbeans alatt


Miután a teszt osztályok elkészültek töltsük ki azok metódusait. Ne felejtsük el, hogy az IDE minden metódushoz csak 1 teszt függvényt generál, azonban nekünk ez nem elég. Általában többre van szükség, tesztelnük kell pozitív eredményekre és hibák helyes kezelésére is. Az egy tervezési döntés, hogy egy hiba keletkezésénél kivételt dobunk vagy visszatérési értékben adjuk vissza sikertelen művelet tényét.

[Tipp]Tipp

Használjuk kivételeket a hibás paraméterek, nem megfelelő környezet jelzésére, mert ilyenkor lehetőség van visszafejteni a hiba pontos helyét, okát!

A JUnit segítségvel figyelhetjük egy kifejezés értékét, kivétel keletkezését.

Érdemes a tesztünk paramétereit nem szövegkonstansokkal megadni, használjuk helyette a JVM paramétereket, mert ilyen kor lehetőség van újrafordítás nélkül futtatni különböző paraméterekkel. Netbeans alatt ezt könnyen megtehetjük, csak fel kell venni egy új futási konfigurációt (run configuration), melynek neve "test":

11.4. ábra - Egységtesztek generálása Netbeans segítségével

Egységtesztek generálása Netbeans segítségével


Az aktuális paraméter elérése a következő kóddal lehetséges, amelyet érdemes a tesztek előtt egyszer kiolvasni és eltárolni:

11.1. példa - A JUNIT teszt setUp() függvénye, ami kiolvassa a JVM paramétereket

@Before public void setUp() throws Exception { 
testComp = System.getProperty("testCompName"); 
}


Ha a tesztek elkészültek, akkor futtatsuk azokat! Futtahatunk egy teszt osztály (Test File, Debug Test File), vagy a projekthez tartozó összeset is (Test Project).

11.5. ábra - Egységteszt eredményének ablaka Netbeans alatt

Egységteszt eredményének ablaka Netbeans alatt


11.2.2. Integrációs teszt (Cactus)

A Java alapú webalkalmazások tesztelése könnyen elvégezhető a Cactus (Apache) termékkel, amely hatékony keretrendszer az integrációs egységtesztek futtatására. Az egységteszt ellnörzi az egyes objektumok helyes működését, de nem ellenörzi az objektumok közötti helyes kapcsolatokat. A redndszer a megfelelően együttműködő objektumok alkotják, így tesztelésük fontos. A Cactus rendszer fontos tulajdonsága, hogy a tesztelendő elemek (szervlet, EJB, ...) a valóságos konténerben futnak. Léteznek mock objektumok is az integrációs tesztekre. Bár a Cactus bizonyosan lassab, de valóságos, tényleges (vagy ahhoz nagyon közeli) környezetben ellenőrizhetjük segítségével rész rendszereineket. A mock teszttel ellentétben tudjuk ellenőrizni az életciklust, biztonságot, tranzakciót, perzisztenciát, stb. .

A keretrendszer Ecosystem-et definiál, amely három féle komponenst használ: Cactus keretrendszer,Cactus integrációs modulok, Cactus minták, melyet a következő ábra illusztrál:

11.6. ábra - Cactus Ecosystem felépítés

Cactus Ecosystem felépítés


Telepítése egyszerű, csak a jar file-okat kell a megfelelő helyre másolni. A grafikus felületről az IDE gondoskodik, és a CI rendszerükben karakteresen futva szövegesen gyűlnek a teszt esetek. Mivel itt a teszt esetek a szerver oldalon futnak, ezért egy kicsit speciális a használatuk. Nézzük át a használati esetek készítését! Alapvetően két lehetőség van teszt futtatni írni:

  1. A Cactus teszt esetek osztályait használva, melyek ServletTestCase a szervlet API (HttpServletRequest, HttpServletResponse, HttpSession, ServletConfig, ServletContext, ...), JspTestCase a JSP API (PageContext, JspWriter, ...) és a FilterTestCase a Filter API objektumainak ellenőrzésére (FilterChain, FilterConfig, HttpServletRequest, HttpServletResponse, ...), amiket ténylegesen servletként, vagy filterként, futtatunk a webalkalmazás motoron.

  2. Hagyományos JUnit teszt eseteket használunk, ami egy Cactus teszt készletet használ ServletTestSuite. Parancssoról futtatjuk.

A keretrendszer kliens-szerver alapú. A következőkeben az xxx a teszt neve minden esetben, azaz ha van egy LoginOk nevű teszt eset, akkor ehhez definiálhatunk beginLoginOk(), endLoginOk() és testLoginOk() metódusokat. Vannak metódusok amelyek a kliens oldalon futnak (beginxxx(), endxxx()) majd létrehozva egy proxy objektumot az előzőleg telepített szerver szervlet-hez kapcsolódva lefuttatják a szerver oldali metódusokat a szerver oldalon (setUp(), testxxx(), tearDown()). Egy webalkalmazás funkciójának lehetnek utóhatásai a szerver és a kliens oldalon is, melyeket a rendszer segítségével ellenőrizhetünk. A szerver oldali utóhatások ellenörzésére a kódot a testxxx() metódusba kell írnunk, a kliens oldali válasz ellenőrzésére pedig ott van az endxxx() metódus.

Bármelyik futtatási módot választjuk a szerver oldalon mindenképpen telepítenünk kell nehány komponenst. Mi Apache Tomcat rednszert használtuk, a beállításokat nézzük meg erre az esetre. Jelen pillanatban a Cactus 1.8.1-es verziója érhető el, és az interneten elérhető dokumentációa függőségekről egy régebbi verziót ismertet. Bármilyen java rendszer is telepítünk használat közben az osztály betöltő jelzi, hogy ha valamely osztály nem található. Ennek két oka lehet: vagy nincs egy jar fájl a classpath helyen, vagy valamelyik jar fájlból egy régebbi verziót használunk.

Első lépésként másoljuk a megfelelő helyre a Cactus jar fájljait! Két lehetőség van: vagy a tomcat lib mappájába másoljuk, vagy célzottan a tesztelendő webalkalmazás WEB-INF/lib mappába. Az első megoldás hatékonyabb, hiszen egy újabb tesztelendő webalkalmazás esetén újból telepíteni kell, ami plusz mnunka és újabb helyfoglalás a háttértárolón. Másolandó jar fájlok: cactus.core.xxx, commons.codecxxx, commons.httpclientxxx, commons.loggingxxx, aspectjrt.jar, junitxxx (xxx attól függmelyik verziót használjuk). Ezután módosítsuk a /conf/web.xml konfigurációs állományt!

[Tipp]Tipp

Ha módosítani akarjuk valamely rendszer konfigurációs állományát, akkor minden módosítás előtt mentsük el azt valamilyen kiterjesztéssel, pl. egy dátum utótaggal egészítsük ki. A fájl tartalmában is írjuk be megjegyzésként, hogy ki miért módosított.

Írjuk be a következő bejegyzést a web-app bejegyzés elejére, azaz a <web-app ....> kedetű sor után:

11.2. példa - Cactus szervletek beállítása Tomcat rendszerbe

<servlet>
  <servlet-name>ServletRedirector</servlet-name>
  <servlet-class>org.apache.cactus.server.ServletTestRedirector</servlet-class>
  1<init-param>
    <param-name>param1</param-name>
    <param-value>value1 used for testing</param-value>
  </init-param>
</servlet>

<servlet>
  <servlet-name>ServletTestRunner</servlet-name>
  <servlet-class>org.apache.cactus.server.runner.ServletTestRunner</servlet-class>
</servlet>

1

Nem kötelező, ezzel a módszerrel lehet paramétereknek értéket megadni.


A konfigurációs állományban számos ilyen szervlet bejegyzés található, ahol megadjuk a szervlet nevét, és az azt implementáló osztály teljes nevét. Ezután meg kell adni ennek a szervletnek az url mintáját, holy milyen kérés hatására töltse be a konténer, melyet a következő kód definiál:

11.3. példa - Cactus szervletek beállítása Tomcat rendszerbe

<servlet-mapping>
    <servlet-name>ServletRedirector</servlet-name>
    <url-pattern>/ServletRedirector</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>ServletTestRunner</servlet-name>
    <url-pattern>/ServletTestRunner</url-pattern>
</servlet-mapping>
  <servlet-class>org.apache.cactus.server.runner.ServletTestRunner</servlet-class>
</servlet>


Az állományban már vannak ilyen bejegyzések, helyezzük ezt a többi hasonló elé!

Ugyanúgy definiálhatunk setUp és tearDown metódusokat, de ezek immáron a szerver oldalon futnak, így elérik a szerver és a Cactus implicit objektumokat is. A teszt függvényekben testXXX() példányosítjuk a szerveletet (vagy más szerver oldali objektumot), használjuk azt, majd ellenőrizzük a szokott módon az asserts(), assertEquals() metódusokkal vagy a fail() metódussal hibát jelzünk. A Cactus rendszerben lehetőség van minden testXXX() metódushoz beginXXX() és endXXX() metódusokat definiálni, amelyek csak az adott teszt függvény előtt (után) futnak le. (Emlékezzünk vissza. A setUp és tearDown minden teszt metódus előtt lefut ráadásul a szerver oldalon.) A beginXXX(WebRequest theRequest) metódus paramétere egy Cactus objektum, amit beállíthatunk a tényleges kérésnek megfelelően, amely függvény "kliens" oldalon fut, mielőtt a testXXX lefutna. A teszt utáni függvény szintén a "kliens" oldalon fut le (ha a aszerver oldalon nem volt hiba, és minden ellenörzés sikeresen lefutott), feladata a válasz ellenörzése endXXX(). A Cactus egy proxy objektumon keresztül továbbitja a kérést a szerver oldali logikájának, majd ha lefutott vissza tér a kliens oldalra. A pontos foylamatot a következő ábrán láthatjuk:

11.7. ábra - Cactus tesz metódus futtatásának folyamata

Cactus tesz metódus futtatásának folyamata


A Cactus-ban használhatjuk a már megszokott implicit objektumokat (publikus adattag formájában). Ezek egy része csak a szerver oldalon létezik, mások kliens oldalon is elérhetőek. Csak szerver oldali implicit objektumok a session,ServletContextWrapper,config. Ezek használhatóak a setUp(), a testXXX() és a tearDown() metódusokban (nem használhatjuk a kliens oldali fv.-ekben, mert ott értékük null). Kliens oldalon is használhatóak a request (beginXXX), response (endXXX). Összegezve 5 objektum létezik, melyek a következőek:

request

Saját wrapper osztály, ami a megszokott HttpServletRequest osztályból származik. Állatlában a beginXXX() metódusban használjuk, hogy beállítsuk a teszt kérés paramétereit, és a süti(ke)t. Néhány metódussal rendelkezik, ami nincs a hagyományos kérés objektumban, ezek a tesztelés paramétereinek a beállítását szolgálják: setRemoteIPAddress(), setRemoteHostName(), setRemoteUser(), setMethod().

response

Ez a megszokott HttpServletResponse osztály egy példánya.

config

Saját wrapper osztály, ami a megszokott ServletConfig osztályból származik. Lehetőséget biztosít, hogy inicializáló paramétereket adjuk meg a szervletnek web.xml nélkül. Használható definiált plusz metódusok: setInitParameter(), setServletName()

ServletContextWrapper

Nem érhető el közvetlenül, de ugyanúgy létrejön és használata javasolt. A config.getServletContext() metódus visszatérési értéke

session

A rendszer változatlan formában elérhetővé teszi (inicializálja) a megszokott objektumot. A teszt függvényben a szokásos módon használhatjuk, majd ellenőrizhetjük a tartalmát.

Nézzünk meg egy kódrészletet a ManagerServlet-ünk tesztelését végző TesztManagerServlet osztályból:

11.4. példa - Cactus servlet teszt eset osztály (részlet)

    public void beginLogOut(1WebRequest webRequest)
    {

        2webRequest.addParameter("function", "logout");
    }

    public void testLogOut()
    {

        3ManagerServlet servlet = new ManagerServlet();
        try{
            4servlet.processRequest(request, response);
        }catch(Exception ex)
        {
            5fail("Exception occured: " + ex.getMessage());
        }


       6assertFalse("Session is exist yet", request.isRequestedSessionIdValid());

    }

    public void endLogOut(7WebResponse theResponse)
    {
        try {

   // XML feldolgozas helye

    NodeList nodes = (NodeList) result;

    assertEquals("XML is not correct ", 1, nodes.getLength());

   String response =  nodes.item(0).getNodeValue();

    8assertEquals("wrong resultCode", "0", response);


        } catch (Exception ex) {
            Logger.getLogger(TestManagerServlet.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

1

Kliens oldalon a kapott paraméter használhatjuk, a kérés beállítására.

2

Beállítjuk a kérés paraméterét, jelen esetben function paramétert logout értékre.

3

A teszt függvényben létrehozzuk a kívánt szervletet.

4

Meghívjuk a tesztelendő metódust.

5

Ha bármi kivétel keletkezik, akkor a teszt sikertelen (muszály kezelnünk).

6

Ellenőrizzük a szerver oldali kívánt utóhatást. Jelen esetben a kijelentkezés funkciótól elvárjuk, hogy érvénytelenítse a session-t.

7

Az endxxx() kliens oldali függvény egyetlen paramétere a kapott válasz (tartalom, válaszkód, süti,...)

8

A kliens oldalon ellenőrizhetjük a kapott tartalmat. Jelen esetben egy XML, aminek tartalmazni kell egy response tagot, aminek az értéke 0 kell legyen. (Az XML feldolgozás több utasításból áll, több lehetőség is van.)


A tesztek futtatására két lehetőség is kínálkozik. Futtathatjuk egy másik servlet formájában, vagy JUnit tesztbe ágyazva (jelen pillanatban a JUnit 4-es nem támogatott). Szervletként futtatva használnunk kell a telepített redirector szervletet megadva neki a teszt osztályunk teljes nevét. A válasz XML formátumú, de a Cactus oldaláról letölthetünk egy xls fájlt hozzá, így kimenet html szöveg lesz. Miután elhelyeztük a webalakalmazás gyökerébe a letöltött xsl-t a kérés a mi esetünkben a következőképpen néz ki:

11.5. példa - Cactus szervlet teszt futtatása

http://localhost:8080/ServletTestRunner?suite=hu.sztaki.openrtmext.server.servlets.TestManagerServlet&xsl=cactus-report.xsl


Mely hatására hasonló az eredmény tartalmazza egy összesítést, és hiba vagy rossz eredmény esetén a helyét, leírását. A kérés kimenete a mi esetünkben egy hiba esetén a következő képpen néz ki:

11.8. ábra - Cactus szervlet tesztünk eredménye a böngészőben

Cactus szervlet tesztünk eredménye a böngészőben


Paransorból vagy Netbeans alól futtatva (Shift + F6) az előző kódot ki kell egészítenünk egy main függvénnyel:

11.6. példa - Cactus teszt futtatása futtatható alkalmazás formájában

    public static void main(String[] args) {
        junit.textui.TestRunner.run(suite());
    }


A fenti kódon kívül beállíthatjuk a Cactus paramétereit, amiből egy kötelező, és nincs alapértelemezett éertéke. A Beállísra több lehetőség és kínálkozik:

  1. JVM paraméterrel -Dparamnév=érték

  2. Konfigurációs fájlban, melynek neve cactus.properties, aminek a classpath-ban kell lennie. Átdefiniálható a név a -Dcactus.config=c:/cactus.txt JVM paraméter segítségével.

  3. A teszt kódban a megfelelő rendszer változó állításával System.setProperty()

Ilyenkor a kimenetre írja a teszt eredményeket. Alapértelmezett a beszédes mód (ALL), így minden akciót kiír a kimenetre (kapcsolódva a szerver oldalhoz, milyen kérést kül el, milyen paramétert állít be, ...).

11.2.3. Jmeter teljesítmény teszt

A webalkalmazásunk teljesítmény tesztelését a rendszer struktúrájának, és működésének tudatában kezdjük el. Az alkalmazással szemben támasztott fő követelmény a helyes működés, utóhatás. A másodlagos követelmény a megfelelő (használható) válaszidő. Normál kéréseket 1-2 másodperc, egyes kéréseket maximum 5-8 másodperc alatt ki kell szolgálni. Természetesen a felhasználói kézikönyvben jelezni kell az esetleges nagy válaszidőket, és azt, hogy a válaszidő minek a függvénye.

Ebben az alkalmazásban a rekurzív végrehajtást igénylő funkciók fálasuideje lehet nagy. Erre a tervezés során már gondoltunk, ezért ezek szálban futnak, és a használt külső elemek válaszidejét is kicsire állítottuk. A rendszerünk CORBA objektumokat használ, és minden rekurzív futást igénylő művelet az éppen aktuális fa méretétől erősen függ. A CORBA objektumok válaszideje alacsony kell legyen. Inkább Zombie-nak nyilvánítjuk, mintsem megnövelje drasztikusan a válaszidőt. Normál esetben a rendszert 10-20 felhasználó fogja használni egyszerre, mi azonban 100 felhasználót fogunk szimulálni. Általában az egyidejű felhasználók száma határozza meg a válaszidőt, ami a mi esetünkben is igaz, hiszen minden kérés kiszolgálás egy új szálat índít el a szerver oldalon. A szervletek ebben az új szálban futnak. Figyeljük oda ezért a globális jellegű adatok használatakor, hogy szálbiztos legyen. A Jmeter segítségével több - helyben elosztott - kérést is tudunk küldeni, hogy minnél reálisabb teszt eredményeket kapjunk. Első lépésként csak egy bizonyos erőforrást (funkciót) kérünk ciklikusan (ilyenkor érdemes a szerver oldalon a memória szivárgást is figyelni). Majd emeljük a párhuzamos szálak számát, és az ismétléseket. Érdemes extrém helyzeteket is előállítani, hogy megfigyeljük a rendszer hibatűrési képességét és határait.

11.2.4. Hudson rendszer

A Hudson rendszer nem teszt alkalmazás, hanem folyamatos integrációs alkalmazás, ami ebben az esetben egy webalkalmazás. Segítségével automatizáltan lehet projektjeink kész termékét (artifact) előállítani és közös helyen tárolni. A végtermék lehet egy programozó könyvtár (lib vagy jar), lehet egy alkalmazás, dokumentáció vagy bármi ami a projekt kimenete lehet. Alapvetően java projektek menedzselésére hozták létre, de bármilyen nyelvű projkre beállítható testte szabható. A rendszer a következő szolgáltatásokat nyújtja:

  • Automatikus fordítás, létrehozás. A rendszer lehetővé teszi, hogy szükség esetén megprőbálja létrehozni a terméket akár osztottan is. (Ant és maven támogatott, de bármilyen külső alkalmazást meg tud hívni.)

  • Teszt esetek futtatása fordítás után. Fordítás után lefuttatja a kívánt teszteket, amelyekről kimutatás is készít kérésre, jelenleg JUnit/TestNG riportokat támogat.

  • Biztosítja a felhasználókat, hogy mindenki ugyanazt a végterméet használja. Definiál URL-t a termék letöltésére, ha mindenki azt használja, akkot garntált, hogy nem keringenek különböző verziók a rendszerben.

  • Automatikus telepítés. Ha a készítés menete sikeres volt, akkor a kész alkalmazást telepíthetjük végleges helyére.

  • Felhasználó menedzsment, jogok, jogkörök. A rendszerben felhasználókat hozhatunk létre, majd definiálhatjuk, hogy melyik projekten milyen műveleteket hajthat végre. Ezzel hangolhatjuk rendszerünk működését.

  • Lehetőséget nyújt, hogy a régebbi verziókat is letölthessük. Beállítható, hogy hány (utolsó valamennyi) vagy meddig (adott dátumig) verziót tartson meg a rendszer. Ezek a verziók bármikor letölthetőek, készítésük menete (log fájlok), teszt eredmények megtekinthetőek.

  • Automatikus verziókövető rendszer figyelés. A rendszernek frissnek kell lennie. Erre két megoldás adódik:

    1. A verzió követő kezdeményezi a termék készítésének folyamatát. Svn esetén a commit hook szriptbe beírhatjuk, hogy kérje meg a webalakalmazást a friss verzió leszedésére, majd a termék előállítására (wget vagy curl alkalmazások segítségével).

    2. A Hudson figyeli időről időre az verzió követőt, hogy történt e változás. Crontab szerűen meg lehet adni egy kifejezést, amelytől függően a rendszer fogyeli a verzió követőt, és ha új verziót észlel, akkor lehozza, majd elkészíti a terméket.

  • Levél/IM üzenet küldés sikeres/sikertelen fordítás esetén, RSS tármogatás

  • Kompakt, nincs szükség háttér adatbázisra.

  • Számos plugin, amely segíti munkánkat.

A redszer több fajta projektet ismer, számunkra a legkényelmesebb a szabad stílusú ( "Build a free-style software project") volt. A projekt létrehozásának menete a következő:

  1. Projekt fő paramétereinek a megadása. Itt elsősorban a prokjekt nevét kell megadni.

  2. SCM (Source Code Management) paraméterek beállítása. Mi svn-t használtunk, amihez beépített pluginja van a rendszernek. Mivel az svn tároló is ugyanazon a gépen volt, így fájl szinten értük el a tárolót.

  3. Termék előállításának megadása. Itt adjuk meg a termék előállításának pontos menetét. Mivel projektünk ant alapú (Netbeans ant-ot használ), amit a Hudson-ba épített plugin támogat.

  4. Egyéb kisérű műveletek paramétereinek a megadása.

  5. Értesítések definiálása, azok paramétereinek a beállítása.