Informatika | Tanulmányok, esszék » Informatikai navigátor, gondolatok a szoftverek használatáról és fejlesztéséről VI.

Alapadatok

Év, oldalszám:2012, 92 oldal

Nyelv:magyar

Letöltések száma:68

Feltöltve:2012. szeptember 30.

Méret:1 MB

Intézmény:
-

Megjegyzés:

Csatolmány:-

Letöltés PDF-ben:Kérlek jelentkezz be!



Értékelések

Nincs még értékelés. Legyél Te az első!

Mit olvastak a többiek, ha ezzel végeztek?

Tartalmi kivonat

2012. Szeptember Informatikai Navigátor Gondolatok a szoftverek használatáról és fejlesztéséről 6. szám Informatikai Navigator Gondolatok a szoftverek használatáról és fejlesztéséről Tartalomjegyzék 1. Grinder - Az alkalmazások teljesítményének mérése 3 2. Egységtesztelés a JUnit használatával 22 3. Grinder - Java Enterprise programok tesztelése 36 4. A Java biztonsági rendszere - Kerberos alapú SSO 56 5. A SPNEGO SSO beállítása JBoss környezetben 81 6. A JBOSS – Bonita páros produktív használata 84 7. Az Abstract Factory minta 88 Főszerkesztő: Nyiri Imre (imre.nyiri@gmailcom) 2 Tesztelés 1. Grinder - Az alkalmazások teljesítményének mérése Grinder - Az alkalmazások teljesítményének mérése Írásunk a Grinderről, azaz a darálóról szól. Ez egy olyan eszköz, ami lehetővé teszi a terheléses (stressz) alkalmazás tesztek elvégzését. Elsősorban a web alkalmazások tesztelésére lett

kitalálva, de ugyanúgy alkalmasnak bizonyult az adatbázisok, java JMS queue-k és egyéb komponensek terhelhetőségének vizsgálatára, amit a beépített Python (Jython) motor tesz lehetővé. A project webhelye: http://grinder.sourceforgenet/ A Grinder eredetileg egy terheléstesztelő eszközként készült, azonban amióta Python (Jython) scriptben készíthetjük el a teszteseteket, jól alkalmazható az alkalmazások funkcionális helyességének teszteléséhez is. Az eszközzel megállapítható az alkalmazás kiszolgáló képességének maximuma (capacity=kapacitása), illetve a Stress Testing funkcióval ellenőrizni tudjuk, hogy a produktív üzemre tervezett áteresztő képesség biztosított-e. A Grinder legfontosabb lehetőségei: Az agentek 1 vagy több munkafolyamatot (Worker process) indíthatnak, ezeken belül pedig szálak (Worker Threads) futnak, amik végső soron a Python nyelven megírt teszt scripteket futtatják. • A böngészők működésének

szimulációja egy felvett interakció ismételt lejátszásával • Webservicek tesztelése • JMS, JDBC és egyéb JEE kommunikációk tesztelése • Különféle internetes protokollok (POP3, SMTP, FTP, LDAP, .) feletti kommunikációk tesztelése A Grinder felépítése A Grinder architekturális felépítését az 1.1 ábráról tanulmányozhatjuk Látható, hogy általában több process fut egyszerre A konzol process tartja a kapcsolatot az agent (ügynök) processekkel, amik különböző fizikai számítógépen futhatnak. Ez a kapcsolat kétirányú, mert a konzolról parancsok adhatóak az agentek felé, ugyanakkor az ügynökök folyamatosan továbbítják a mérési eredményeket a központi konzol számára. 1.1 ábra A Grinder felépítése Az agentek tehát a Worker-eket menedzselik, elindítják és leállítják azokat, illetve fenntartják a megfelelő futási környezetüket. Mindegyik Worker folyamat naplót vezet, aminek a file neve a következő konvenció

szerint képződik: host-n.log, ahol a host a gép neve, az n pedig a Worker process sorszáma. A tesztelés közben keletkezett adatok a host-n-data.log file-okban képződnek, ahol ugyanez a névkonvenció érvé3 Tesztelés Grinder - Az alkalmazások teljesítményének mérése nyesül. A file formátuma CSV, emiatt könnyen lehet beolvasni azt egy táblázatkezelőbe és ezzel különféle utóelemzések végezhetőek el. Egy tesztelés lehetséges kimenetele SUCCESS (sikerült) vagy ERROR (hiba történt) lehet. Amikor a konzol elindult (ez egy GUI program) startoltathatjuk az agent-eket is, minimum 1 darabot. Ez természetesen ugyanazon a gépen is lehet, mint a konzol, a startAgent.sh mutatja az ajánlott indítást. // s t a r t A g e n t . sh A Grinder egyes elemeinek indítása A szoftver háromféle indítható programmal rendelkezik: #! / b i n / b a s h . / s e t G r i n d e r E n v sh j a v a −c l a s s p a t h $CLASSPATH n e t . g r i n d e r G r i n d e r

å $GRINDERPROPERTIES A proxy process-t nem közvetlenül a teszte• A konzol process, ahol adminisztráljuk és lésnél használjuk, ezért annak indítását és haszmonitorozzuk a munkát. nálatát egy külön pontban ismertetjük. • Az Agent processek, amik a konzolhoz kapcsolódnak. A Grinder konfigurációja • Egy TCP/HTTP proxy, ami a tesztfutta- A konfigurációt a grinder.properties tartaltáshoz nem szükséges, szerepét a későbbi- mazza, aminek lehetséges részeit mutatjuk be a ekben ismertetjük. következőkben. A következőkben néhány ajánlott bash parancsállományt javaslunk, ezekkel érdemes elindítani grinder.processes az egyes processeket. A setGrinderEnvsh a környezeti változók beállítását végzi A GRINDER- Az Agent által elindítandó dolgozó (Worker ) foPATH az a hely, ahova a Grinder-t kicsomagol- lyamatok (process) száma Ezzel lehet utánozni tuk. A GRINDERPROPERTIES a konfigurá- a többfelhasználós működést a tesztelés során

Az alapértelmezett érték: 1. ciós file helye. // s e t G r i n d e r E n v . sh #! / b i n / b a s h grinder.threads GRINDERPATH=/home/ t a n u l a s / g r i n d e r / g r i n d e r å −3.71 GRINDERPROPERTIES=/home/ t a n u l a s / g r i n d e r /å grinder . properties CLASSPATH=$GRINDERPATH/ l i b / g r i n d e r . j a r : å $CLASSPATH JAVA HOME=/u s r / l i b /jvm/ jav a −6−sun PATH=$JAVA HOME/ b i n :$PATH export CLASSPATH PATH GRINDERPROPERTIES A Worker folyamathoz itt lehet meghatározni, hogy mennyi szálon fusson. A böngészők is több szálon szokták az erőforrásokat letölteni, így ez a működés a tökéletesebb böngésző szimulációban segít. Az alapértelmezett érték: 1 Sorrendben mindig a konzolt kell először elindítani, hiszen az agentek erre fognak csatlakozni. A startConsole.sh script a konzol javasolt indítását automatizálja grinder.runs // s t a r t C o n s o l e . sh #! / b i n / b a s h . / s e t G r i n d e r E n v sh j a v a

−c l a s s p a t h $CLASSPATH n e t . g r i n d e r C o n s o l e 4 Itt lehet megadni, hogy a Python teszt scriptet hány alkalommal futtasson le egy tesztelő szál. Az alapértelmezett érték: 1. A 0 azt jelenti, hogy a script örökké fut, azaz lefutása után ismételten elindításra kerül. Ezt akkor érdemes használni, amikor a Grinder konzolt folyamatosan figyelni szeretnénk. Tesztelés Grinder - Az alkalmazások teljesítményének mérése grinder.processIncrement Amikor ezt megadjuk, akkor a Grinder nem indítja el az összes lehetséges munkavégző folyamatot (emlékezzünk, ez a grinder.processes property-ben van megadva), hanem csak egy grinder.initialProcesses-ben megadott darabszámot, amit a grinderprocessIncrementInterval (alapértelmezés: 60000 ms) időközönként növel, amíg el nem érjük a definiált maximális munkafolyamat számot. Amennyiben nem adjuk meg, úgy alapértelmezetten az összes Worker process az elején elindul.

grinder.processIncrementInterval tomatikusan létrejön. grinder.hostID Amennyiben megadjuk, úgy nem az agent gép host neve kerül a naplóba, hanem az itt megadott string. grinder.consoleHost A Grinder központi konzolja ezen a host-on fut, azaz erre az IP címre kell a Worker processeknek (Agent-ek) kapcsolódniuk. Az alapértelmezés a helyi gép összes hálózati interface-e. Szerepét a grinder.processIncrement résznél isgrinderconsolePort mertettük. grinder.initialProcesses A grinder.consoleHost property mellé tartozó TCP/IP port, az alapértelmezés a 6372. Szerepét a grinder.processIncrement résznél ismertettük grinder.useConsole Az alapértelmezés a true, de amennyiben ezt false-ra állítjuk, úgy a Worker process nem kapEzredmásodpercben megadhatjuk, hogy a Wor- csolódik a konzolhoz, így a konzolon keresztül elker processek mennyi ideig futhatnak, az alapér- érhető szolgáltatások sem vehetők ebben az esettelmezés az örökké tartó futás. Ennek

hatása – ben igénybe amennyiben megadjuk – együtt érvényes a már ismertetett grinder.runs konfigurációs lehetőséggel, mely esetben a Worker process megszűnik, grinderreportToConsoleinterval ha eléri a megadott időtartamot (duration) vagy Az a periódusidő (alapértelmezés: 500 ms), lefutott annyiszor, amennyit a futások száma ahogy a Worker process-ek információt külde(runs) előír. nek a Grinder konzol felé. grinder.duration grinder.script grinder.initialSleepTime Az a python file név (alapértelmezés: grinAz maximális idő ezredmásodpercben, amennyit der.py), amit a Worker processek futtatnak minden futási szál (thread ) vár mielőtt elindulna (alvási idő). Az alapértelmezett érték a 0 ms Itt grinder.logDirectory egy olyan véletlen-szám eloszlás van, aminek ez Az a könyvtár, ahova a Grinder a logfile-okat a maximuma, de 0 és e között bármekkora lehet helyezi. Amennyiben még nem létezik, úgy au- ez az érték 5 Tesztelés

Grinder - Az alkalmazások teljesítményének mérése grinder.sleepTimeFactor A Grinder konzol megismerése Az alapértelmezett értéke 1. Ez egy olyan szorzótényező, amivel a tényleges alvásidő kalkulálódik, azaz például 01 érték esetén 10-szer gyorsabban indul el a Worker process A 1.2 ábra a Grinder konzol képét mutatja, ahol a Graphs (grafikus áttekintés), Results (eredmények), Processes (beállított folyamatok), Script (python program, ami éppen be van töltve) fülek jelentik a konzolon végezhető főbb feladatok helyeit. grinder.sleepTimeVariation A Grinder alvási ideje a normális eloszlást fogja követni. Egy példán lehet legkönnyebben megérteni Legyen az grinderinitialSleepTime=1000 ms és a grinder.sleepTimeVariation=01, azaz 10%=100 ms. Ekkor ez meghatározza az 1000100 és 1000+100, azaz a [900, 1100] időintervallumot Definíció szerint ekkor a futások 9975 %-a ebbe az intervallumba eső ideig fog aludni az elindulásuk előtt. Ezen

paraméter alapértelmezett értéke: 02 A folyamatok kezelése A folyamatkezelés a tesztek elindítását (Start processes), alaphelyzetbe hozását (Reset processes) és leállítását (Stop processes) jelenti, amely funkció az Action menüből vagy az 1.4 ábrán látható ikonok révén érhető el. Ez a háttérben az előzetesen elindított és a konzolra beregisztrált Grinder (agent) processek felé történő parancsküldésekkel valósul meg, amiatt ezek a funkciók nem érhetőek el („szürkék”), amikor a konzolra grinder.reportTimesToConsole nincs egyetlen agent se kapcsolódva. A bereAz alapértelmezett érték a true, de false ese- gisztrált Grinder Worker processek a Processes tén az időzítési (timing) információkat a Grinder fülön tekinthetőek meg és a következő állapotok nem küldi el a konzol felé. egyikében lehetnek: grinder.jvm • Initiated : várakozás egy konzol parancsra Egy eltérő JVM is megadható, ekkor a java parancs helyett a

másik JVM futási nevét kell megadni. Természetesen a PATH környezeti változó helyes beállítása lényeges. Az alapértelmezett érték: java. • Running: a process éppen tesztet futtat, eközben küldi a riport adatokat a konzol felé grinder.jvmclasspath • Finished : várakozás egy konzol parancsra, mert lefutott Az itt megadott PATH -ok a java CLASSPATH A Start processes parancs mindig Running állapotba teszi a Workert, illetve amennyiben az részei lesznek. már fut, úgy figyelmen kívül marad. A Reset processes parancs leállítja a Workert, újraolvassa grinder.jvmarguments a konfigurációt és ismét elindítja a tesztelést. A További parancssori argumentumok a teszteket Stop processes parancs úgy állítja le az összes futtató JVM számára. Például itt adhatunk agent-et, hogy azoknak az operációs rendszerbeli futásuk is véget ér. több memóriát a Java Virtuális gépnek. 6 Tesztelés Grinder - Az alkalmazások teljesítményének

mérése 1.2 ábra A Grinder konzol 1.3 ábra Grinder folyamatok kezelése A A 1.2 ábra bal oldali részén lehet szabályozni (ezt a grinderreportToConsoleinterval property-ben is megadhattuk), hogy a konzol mennyi időnként vegyen át mérési mintát a Worker -ektől. A Script fül mögött egy szövegszerkesztő van, ahova a Python nyelvű teszteket is betölthetjük, a Grinder ezt fogja futtatni. Az ábrán éppen a később ismertetendő Hello World tesztprogram egy részlete látszik. A Graphs és Results fülek a tesztekről mutatnak információt (1.4 és 15 ábrák) Fontos mértékegység a TPS (Test Per Secundum), ami azt jelenti, hogy 1 másodperc alatt mennyi tesztfutás történt. A piros oszlopdiagram azt mutatja, hogy az utolsó 25 tesztnél mennyi volt a mért érték. Az átlag (mean) esetünkben 38000 TPS, míg a csúcsér- ték (peak ) 92700 TPS volt. Az diagram Y tengelye úgy van skálázva, hogy a teteje mindig a TPS csúcsértéket jelenti. A Hello World

egyszerű program, így 1174876 darab tesztet végeztünk el a mérés kb 32 másodperce alatt A 0.0235 ms 1 darab teszt átlagos futási ideje A bal oldalon látható 32800 TPS az utolsó mért érték. A Results fül a következő mérési adatokat mutatja, ahogy azt egy másik mérésre a 1.5 ábra tartalmazza is: • Test: A teszt azonosító sorszáma, amit a teszt script írásakor a Test class első paramétereként adtunk meg. • Description: A Test class 2. paramétere, ami egy string A generált http proxy alapú script-ek itt adják meg a lekért erőforrás nevét és módját, például: Test(14000, ’GET index.jsp’) Ez 1400 értékű tesztazonosítót is mutat, aminek a jelentését az előző pontban magyaráztuk el 7 Tesztelés Grinder - Az alkalmazások teljesítményének mérése • Successful Tests: A sikeresen elvégzett állnak: tesztek száma (a példánkban 1.557942 • Mean Response Length: A http válaszok db). byte-ban mért átlagos mérete. •

Errors: A hibára futott tesztek száma (példánkban ez most 0 db) • Response Bytes per Second : A http vála• Mean Time: Átlagos tesztfutási idő (példánkban 0.0223 ms) Ez http kérés esetén természetesen magában foglalja a válasz teljes beérkezését is • Mean Time Standard Deviation: A tesztfutási idők standard szórása (példánkban: 2.43) • TPS : Itt átlagos tranzakciószámot jelent másodpercenként (most 41600 db/s) szok átlagos byte/s sebessége. • Response Errors: A http válaszok során ennyi teszt futott hibára, például 404 vagy 500-as http hibakód miatt. • Mean Time to Resolve Host: A DNS névfeloldás átlagos időszükséglete ezredmásodpercben. • Peak TPS : Az eddigi legnagyobb mért TPS (itt 92700 db/s) • Mean Time to Establish Connection: A TCP kapcsolat felépítésének átlagos időszükséglete ezredmásodpercben. Amennyi HTTP alapú mérést végzünk, úgy a következő mérési adatok is a rendelkezésünkre • Mean

Time to First Byte: A http válasz első byte-jának átlagos megérkezési ideje. 1.4 ábra A Grinder tesztfuttatás közben - Graph fül 8 Tesztelés Grinder - Az alkalmazások teljesítményének mérése 1.5 ábra A Grinder tesztfuttatás közben - Result fül 1.6 ábra A Grinder tesztfuttatás közben - Processes fül 9 Tesztelés Grinder - Az alkalmazások teljesítményének mérése A 1.6 ábráról, a Processes fülről a kezelt t érdemes megnézni (a nevük névkonvencióját tesztelő folyamatokról tudhatjuk meg a legfon- már ismertettük): tosabbakat. Korábban már elmagyaráztuk en• csdev1-0log: Ide naplóz a tesztfuttatás, nek a hierarchikus felépítését. Az Agent-ek több ilyen sorokból áll: példányban indíthatóak, különböző gépekről is. 2012−03−03 0 8 : 1 0 : 0 8 , 1 9 2 INFO csdev1 −0 å Esetünkben a csdev1 nevű gépről 2 példányban t h r e a d −1 [ run −0 , t e s t −1 ] : H e l l o å adtuk ki a startAgent.sh

parancsot A grinWorld ) der.properties file-ban a grinderprocesses = 1 és grinder.threads = 1 a beállítás, amiatt minden • csdev1-0-data.log: Ide kerülnek a tesztméAgenthez 1 db Worker, ahhoz pedig csak 1 db rések futási szál tartozik. A mért adatok pedig ilyenekből (itt a Workerek száma 2, a Thread-ek száma 3 volt méréskor): Tesztelés Ennyi ismeret után kezdjük el használni a Grindert! Ahogy a kiadványunk borítóján lévő mesterünktől (Dennis M. Ritchie, a C nyelv megalkotója) tanultuk, ez most is legyen a Hello World tesztprogram (1-1. programlista)! Az eddigi képernyőkön ennek a tesztnek a futási képeit használtuk fel a magyarázatokhoz. A 7. sor egy Python lehetőséget használ, történetesen log néven egy alias nevet rendel a grinder.loggerinfo() metódushoz, így az INFO szintű loggoláshoz a log(.) kifejezés is használható lesz A 13 sorban egy test1 nevű tesztelésre képes, Test osztálybeli objektumot hozunk létre, emlékezzünk, hogy

a konstruktor paramétereit láttuk a Grinder konzolon A 18 sor kissé trükkös, de ez biztosítja, hogy a tesztfuttató környezet és a helloworld.py programunk között egyértelmű legyen, hogy mit kell futtatni majd a 22-26 sorban megadott TestRunner osztályban. A létrehozott logWrapper nevű objektum egyrészt a teszt1wrap() alias neve, ugyanakkor a Testwrap() implementációja pedig a log, azaz valójában a grinder.loggerinfo() metódus Mindez úgy, hogy ebben a játékban a logWrapper paraméterezési felülete ugyanaz marad, mint az eredeti log(), azaz info() metódus felülete. A TestRunner call (self ) metódusa kötött, ezzel tud kommunikálni a tesztfuttató szál. A tesztelés leállítása után a log könyvtárban 2 file10 Thread , Run , Test , S t a r t time ( ms s i n c e Epoch ) å , Test time , E r r o r s 2 , 0 , 1 , 1330762208189 , 1 , 0 1 , 0 , 1 , 1330762208191 , 1 , 0 1 , 1 , 1 , 1330762208192 , 0 , 0 1 , 2 , 1 , 1330762208193 , 0 , 0 1 , 3 , 1 ,

1330762208193 , 1 , 0 1 , 4 , 1 , 1330762208194 , 0 , 0 1 , 5 , 1 , 1330762208194 , 0 , 0 1 , 6 , 1 , 1330762208195 , 0 , 0 1 , 7 , 1 , 1330762208195 , 1 , 0 1 , 8 , 1 , 1330762208196 , 0 , 0 1 , 9 , 1 , 1330762208197 , 0 , 0 1 , 10 , 1 , 1330762208198 , 0 , 0 1 , 11 , 1 , 1330762208199 , 1 , 0 2 , 1 , 1 , 1330762208200 , 0 , 0 2 , 2 , 1 , 1330762208201 , 0 , 0 2 , 3 , 1 , 1330762208202 , 1 , 0 1 , 12 , 1 , 1330762208201 , 3 , 0 2 , 4 , 1 , 1330762208205 , 0 , 0 0 , 0 , 1 , 1330762208205 , 0 , 0 2 , 5 , 1 , 1330762208206 , 0 , 0 0 , 1 , 1 , 1330762208206 , 0 , 0 1 , 13 , 1 , 1330762208205 , 0 , 0 Vegyük észre, hogy ez a fájlformátum elsősorban az ismertebb táblázatkezelőkben való feldolgozhatóságot támogatja, de ebben a cikkben ismertetjük a Grinder Analyzer eszközt is, ami kiemelkedő minőségben támogatja a mért adatok elemzését. Látható, hogy a teszt python scriptek lényegében bármilyen tesztesetet implementálhatnak, ami azért jelent nagyon hatékony

megoldást, mert a futtató Jython környezet lényegében bármilyen technológia elérését biztosítja. A következő példánkban már egy egyszerű HTTP GET lekérés tesztelését fogjuk bemutatni Tesztelés 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Grinder - Az alkalmazások teljesítményének mérése // 1 −1. p r o g r a m l i s t a : H e l l o World from n e t . g r i n d e r s c r i p t Grinder import g r i n d e r from n e t . g r i n d e r s c r i p t import Test # A s h o r t e r a l i a s f o r t h e g r i n d e r . l o g g e r i n f o ( ) method log = grinder . logger info # C r e at e a Test w i t h a t e s t number and a d e s c r i p t i o n . The t e s t w i l l be # a u t o m a t i c a l l y r e g i s t e r e d w i t h The Grinder c o n s o l e i f you a r e u s i n g # it . # t e s z t number é s l e í r á s t e s t 1 = Test ( 1 , "Log␣method" ) # Wrap t h e i n f o ( ) method w i t h our Test and c a l l

t h e r e s u l t logWrapper . # C a l l s t o logWrapper ( ) w i l l be r e c o r d e d and f o r w a r d e d on t o t h e r e a l # i n f o ( ) method . logWrapper = t e s t 1 . wrap ( l o g ) # A TestRunner i n s t a n c e i s c r e a t e d f o r each t h r e a d . I t can be used t o # s t o r e t h r e a d −s p e c i f i c d a t a . c l a s s TestRunner : # This method i s c a l l e d f o r e v e r y run . def call ( s e l f ) : logWrapper ( " H e l l o ␣World" ) = grinder-3.71/examples/httppy beállítás legyen! Ennyi előzmény után kb 25 másodperAz 1-2 programlista egy olyan tesztet valósít cig futtattuk a tesztet, aminek az eredményéről, meg, ahol a http://index.hu URL-t kérdezilletve annak legfontosabb részeiről a zük le 25 másodpercen keresztül (a pontos lekérdezési időt a „csdev1-0.log file vége futási kép” • A csdev1-0.log file eleje futási kép 9. sorában láthatjuk, ez 24969 ms) A tesztprogram felépítése a Hello

World-höz hasonlít, • A csdev1-0.log file vége futási kép a 7-8 sorban itt is az ismertetett „aliasos” becsomagolása történik a HTTPRequest() hívás• csdev1-0-data.log file eleje futási kép nak. Ezen metódus paramétere egy URL, amiatt a TestRunner osztályon belül alkalmazhatjuk a tájékoztatja a kedves olvasót. Szeretnénk ki12 sornál lévő ugyanilyen paraméterezésű hí- emelni, hogy a csdev1-0log file végén megtavást, ami a lekért HTML tartalmat a result vál- lálhatóak azok az extra adatok is, amiket úgy tozóba menti. A 16 sor már nem lenne feltét- emeltünk ki, hogy HTTP alapú tesztek esetén lenül szükséges ehhez a teszthez, csak lementi a kerülnek be a logba. A csdev1-0-datalog pedig HTML válaszokat egy-egy file-ba csdev1-0-page- a későbbi statisztikákhoz segít. A következő fennnhtml néven, ahol nnn a tesztlépés sorszáma jezetben egy izgalmas dolgot csinálunk, ugyanis A teszt futtatása előtt nem szabad elfelejteni, egy

Grinder elemzőnek adjuk át ezeket az adahogy a grinder.properties fileban a grinderscript tokat Egy HTTP GET teszt 11 Tesztelés Grinder - Az alkalmazások teljesítményének mérése 1 // 1 −2. p r o g r a m l i s t a : Egy h t t p GET t e s z t 2 3 from n e t . g r i n d e r s c r i p t Grinder import g r i n d e r 4 from n e t . g r i n d e r s c r i p t import Test 5 from n e t . g r i n d e r p l u g i n h t t p import HTTPRequest 6 7 t e s t 1 = Test ( 1 , " Request ␣ r e s o u r c e " ) 8 r e q u e s t 1 = t e s t 1 . wrap ( HTTPRequest ( ) ) 9 10 c l a s s TestRunner : 11 def call ( s e l f ) : 12 r e s u l t = r e q u e s t 1 .GET( " h t t p : / / i n d e x hu" ) 13 14 # r e s u l t i s a HTTPClient . HTTPResult We g e t t h e message body 15 # u s i n g t h e g e t T e x t ( ) method . 16 writeToFile ( r e s u l t . text ) 17 18 # U t i l i t y method t h a t w r i t e s t h e g i v e n s t r i n g t o a u n i q u e l y named f i l

e . 19 def w r i t e T o F i l e ( t e x t ) : 20 f i l e n a m e = "%s−page−%d . html " % ( g r i n d e r processName , g r i n d e r runNumber ) 21 22 f i l e = open ( f i l e n a m e , "w" ) 23 print >> f i l e , t e x t 24 f i l e . close () 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // A c s d e v 1 −0. l o g f i l e e l e j e f u t á s i kép : // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− 2012−03−03 1 9 : 1 9 : 0 1 , 1 7 1 INFO c s d e v 1 −0 : The G r i n d e r v e r s i o n 3 . 7 1 2012−03−03 1 9 : 1 9 : 0 1 , 1 7 7 INFO c s d e v 1 −0 : Java (TM) SE Runtime Environment 1 . 6 0 26−b03 : Java HotSpot (TM) S e r v e r å VM (20.1 − b02 , mixed mode ) on Linux i 3 8 6 300 −8 − g e n e r i c 2012−03−03 1 9 : 1 9 : 0 1 , 1 8 2 INFO c s d e v 1 −0 : t i m e z o n e i s GMT (+0000) 2012−03−03 1 9 : 1 9 : 0 1

, 3 4 3 INFO c s d e v 1 −0 : w o r k e r p r o c e s s 0 o f a g e n t number 0 2012−03−03 1 9 : 1 9 : 0 1 , 5 2 7 INFO c s d e v 1 −0 : i n s t r u m e n t a t i o n a g e n t s : t r a d i t i o n a l Jython i n s t r u m e n t e r ; b y t e c o d e å t r a n s f o r m i n g i n s t r u m e n t e r f o r Java 2012−03−03 1 9 : 1 9 : 0 4 , 0 6 9 INFO c s d e v 1 −0 : r e g i s t e r e d p l u g−i n n e t . g r i n d e r p l u g i n h t t p HTTPPlugin 2012−03−03 1 9 : 1 9 : 0 4 , 0 8 9 INFO c s d e v 1 −0 : r u n n i n g " g r i n d e r − 3 . 7 1 / e x a m p l e s / h t t p py " u s i n g Jython 2 2 1 2012−03−03 1 9 : 1 9 : 0 4 , 1 3 2 INFO c s d e v 1 −0 t h r e a d −0: s t a r t i n g , w i l l run f o r e v e r 2012−03−03 1 9 : 1 9 : 0 4 , 1 3 2 INFO c s d e v 1 −0 : s t a r t t i m e i s 1 3 3 0 8 0 2 3 4 4 1 3 3 ms s i n c e Epoch 2012−03−03 1 9 : 1 9 : 0 4 , 4 8 7 INFO c s d e v 1 −0 t h r e a d −0 [ run −0 , t e s

t −1 ] : h t t p : / / i n d e x . hu/ −> 200 OK, 1 5 6 1 4 5 b y t e s 2012−03−03 1 9 : 1 9 : 0 4 , 7 5 0 INFO c s d e v 1 −0 t h r e a d −0 [ run −1 , t e s t −1 ] : h t t p : / / i n d e x . hu/ −> 200 OK, 1 5 6 1 4 5 b y t e s 2012−03−03 1 9 : 1 9 : 0 4 , 9 8 7 INFO c s d e v 1 −0 t h r e a d −0 [ run −2 , t e s t −1 ] : h t t p : / / i n d e x . hu/ −> 200 OK, 1 5 6 1 4 5 b y t e s // A c s d e v 1 −0. l o g f i l e v é g e f u t á s i kép : // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− 2012−03−03 1 9 : 1 9 : 2 8 , 7 1 5 INFO c s d e v 1 −0 t h r e a d −0 [ run −114 , t e s t −1 ] : h t t p : / / i n d e x . hu/ −> 200 OK, 1 5 6 1 4 5 2012−03−03 1 9 : 1 9 : 2 8 , 9 2 7 INFO c s d e v 1 −0 t h r e a d −0 [ run −115 , t e s t −1 ] : h t t p : / / i n d e x . hu/ −> 200 OK, 1 5 6 1 4 5 2012−03−03 1 9 : 1 9 : 2 9 , 0 3 6 INFO c s d e v 1

−0 : r e c e i v e d a s t o p m e s s a g e 2012−03−03 1 9 : 1 9 : 2 9 , 0 8 7 INFO c s d e v 1 −0 t h r e a d −0 [ run −116 , t e s t −1 ] : h t t p : / / i n d e x . hu/ −> 200 OK, 1 5 6 1 4 5 2012−03−03 1 9 : 1 9 : 2 9 , 0 9 8 INFO c s d e v 1 −0 t h r e a d −0 [ run −117 ] : s h u t down 2012−03−03 1 9 : 1 9 : 2 9 , 0 9 8 INFO c s d e v 1 −0 t h r e a d −0: f i n i s h e d 117 r u n s 2012−03−03 1 9 : 1 9 : 2 9 , 1 0 1 INFO c s d e v 1 −0 : e l a p s e d t i m e i s 2 4 9 6 9 ms 2012−03−03 1 9 : 1 9 : 2 9 , 1 0 2 INFO c s d e v 1 −0 : F i n a l s t a t i s t i c s f o r t h i s p r o c e s s : 2012−03−03 1 9 : 1 9 : 2 9 , 1 2 1 INFO c s d e v 1 −0 : Tests Errors Mean T e s t T e s t Time TPS Mean Response Mean t i m e t o Mean t i m e t o Mean t i m e t o Time ( ms ) Standard response bytes per r e s o l v e host e s t a b l i s h f i r s t byte Deviation length second å connection ( ms ) Test 1 ce " Totals 12 117 0

,76 0 117 0 ,76 0 bytes bytes bytes Responseå errors å 21 ,32 194 ,91 46 ,85 46 ,97 " Request 4 ,69 resour 156145 ,00 731665 ,87 0 å 21 ,32 194 ,91 46 ,85 46 ,97 4 ,69 156145 ,00 731665 ,87 0 å Tesztelés 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Grinder - Az alkalmazások teljesítményének mérése // c s d e v 1 −0−d a t a . l o g f i l e e l e j e f u t á s i kép : // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− Thread , Run , Test , S t a r t t i m e ( ms s i n c e Epoch ) , T e s t time , E r r o r s , HTTP r e s p o n s e code , HTTP r e s p o n s e HTTP r e s p o n s e e r r o r s , Time t o r e s o l v e h o s t , Time t o e s t a b l i s h c o n n e c t i o n , Time t o f i r s t b y t e 0 , 0 , 1 , 1 3 3 0 8 0 2 3 4 4 1 4 7 , 3 4 1 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 8 9 , 1 4 0 , 178 0 , 1 , 1 , 1 3 3 0 8 0 2 3 4 4 5 2 3 , 2 2 8 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 ,

1 7 , 53 0 , 2 , 1 , 1 3 3 0 8 0 2 3 4 4 7 6 6 , 2 2 1 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 , 1 3 , 49 0 , 3 , 1 , 1 3 3 0 8 0 2 3 4 5 0 0 3 , 1 3 5 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 , 1 3 , 30 0 , 4 , 1 , 1 3 3 0 8 0 2 3 4 5 1 6 0 , 2 1 3 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 , 4 8 , 71 0 , 5 , 1 , 1 3 3 0 8 0 2 3 4 5 3 9 3 , 1 7 7 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 , 3 2 , 52 0 , 6 , 1 , 1 3 3 0 8 0 2 3 4 5 5 8 7 , 1 6 3 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 , 1 8 , 42 0 , 7 , 1 , 1 3 3 0 8 0 2 3 4 5 7 6 6 , 2 1 7 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 , 1 8 , 45 0 , 8 , 1 , 1 3 3 0 8 0 2 3 4 6 0 0 3 , 1 6 0 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 , 1 2 , 31 0 , 9 , 1 , 1 3 3 0 8 0 2 3 4 6 1 8 0 , 2 5 8 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 , 1 3 , 67 0 , 1 0 , 1 , 1 3 3 0 8 0 2 3 4 6 4 5 9 , 1 9 9 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 , 3 4 , 50 0 , 1 1 , 1 , 1 3 3 0 8 0 2 3 4 6 6 7 5 , 2 4 9 , 0 , 2 0 0 , 1 5 6 1 4 5 , 0 , 0 , 3 1 , 48 A Grinder Analyzer eszköz A Grinder Analyzer (http://track.

sourceforge.net/analyzerhtml) segítségével a mért tesztadatok további feldolgozása valósítható meg, ami a generált log adatok feldolgozására épül. Az eredmény teljesítmény grafikonok (performance graphs) formájában jelennek meg, amelyek a következő elemeket tartalmazzák: • válaszidők (response time) • a tranzakciók sebessége (transactions per second) • hálózati sávszélesség (network bandwidth) Ebben a pontban bemutatjuk, hogy az előzőekben a http://index.hu helyre elvégzett HTTP GET tesztünket milyen módon lehet feldolgozni. Futtassuk le a Grinder analyzer-t a következő paranccsal: j y t h o n . / a n a l y z e r py c s d e v 1 −0. l o g . / l o g / c s d e v 1 −0−d a t a l o g . / l o g /å Ekkor a 1.7-től a 114-ig mutatott ábrákon lévő grafikonok jönnek létre. A parancs paraméterei mindig azok a napló file-ok, amik a tesztelés során keletkeztek Az analyzer parancs 1 oldalas manuálja itt olvasható: http://track.

sourceforge.net/usingAnalyzerhtml A grafikonok mellé egy nagyon informatív összefoglaló táblázat is keletkezik (1.15 ábra) Az értékek jelentése egyértelmű, de nézzük meg együtt is a számokat! length , å • Tests Passed : 117. Ez a sikeresen elvégzett tesztek száma • Tests w/ Errors: 0. Ennyi teszt futott hibára, esetünkben nagyon jól sikerült a mérés, mert ilyen nem volt • Pass Rate: 1.00 A sikeres tesztek aránya, esetünkben ez 100%, azaz 1.00 • Mean Response Time: 194,91 ms. Ennyi volt az átlagos válaszidő, amit a 1.9 ábra egy grafikonnal is részletez. Látható, hogy az X tengely a tesztelés lefutási idejét mutatja 0-25 másodpercig. Az Y tengely pedig másodpercben mutatja a válaszidőket, ami ennek az átlagnak megfelelő ~0.2 sec környékén ingadozik. • Response time standard dev.: 46,97 ms Az értékek szórási intervalluma, ezt szintén a 1.9 ábráról is láthatjuk • Mean Response Length: 156 145 byte, ami az index induló

html lapjának mérete. • Bytes per Sec: 731 665,87. Ekkora sebességgel értük el a hálózatot a teszt alatt, aminek a részleteit a 1.10 ábra tartalmazza • Mean Time Resolve Host: 0,76 másodperc. A 1.11 ábra egyszerre mutatja ennek az elemeit és statisztikáját. 13 Tesztelés Grinder - Az alkalmazások teljesítményének mérése • Mean Time Establish Connection: A TCP/IP kapcsolat kialakítási ideje 21,32 ms. • A táblázat utolsó 4 oszlopa azt mutatja, hogy mennyi tesztfutás volt 1 s alatt, 1-3 s, 3-10 s között vagy 10 s felett. • Mean Time to First Byte: Az első vá- A táblázat Totals sora, illetve az „All” jellegű laszbyte átlagos megjelenési ideje 46,85 grafikonok akkor lényegesek, amikor több szálon ms. futtatjuk a teszteket. 1.7 ábra All Transactionsperfpng 1.8 ábra Request resourceperfpng 14 Tesztelés Grinder - Az alkalmazások teljesítményének mérése 1.9 ábra Mean Response Time - Request resourcemeanMax rtimepng

1.10 ábra Request resourcebandwidthpng 15 Tesztelés Grinder - Az alkalmazások teljesítményének mérése 1.11 ábra Request resourcertimepng 1.12 ábra All TransactionsmeanMax rtimepng 16 Tesztelés Grinder - Az alkalmazások teljesítményének mérése 1.13 ábra All Transactionsbandwidthpng 1.14 ábra All Transactionsrtimepng 17 Tesztelés Grinder - Az alkalmazások teljesítményének mérése 1.15 ábra Grinder Analyzer összefoglaló táblázat Az elemző eszköznek van egy másik utility parancsa is a difftool : jython . / d i f f t o o l py [ options ] <f i l e 1 > <f i l e 2 > i n y i r i @ c s d e v 1 : / home/ t a n u l a s / g r i n d e r $ . / s t a r t P r o x y s h 2012−03−04 0 8 : 3 9 : 1 7 , 2 9 6 INFO tcpproxy : I n i t i a l i s i n g å a s an HTTP/HTTPS p r o x y w i t h t h e p a r a m e t e r s : Request f i l t e r s : HTTPRequestFilter Response f i l t e r s : HTTPResponseFilter Local address : localhost :8001

2012−03−04 0 8 : 3 9 : 1 8 , 2 5 0 INFO tcpproxy : Engine å i n i t i a l i s e d , l i s t e n i n g on p o r t 8001 Itt több teszt naplóit lehet megadni, az eszköz pedig készít egy elemző riportot a különbItt most http proxy üzemmódban indítottuk, ségekről. A használat 1 oldalas manuálja inahol a proxy-zott output konzol tartalmat röpnen olvasható el: http://tracksourceforge tében python kódra alakítja és esetünkben ezt net/usingDifftool.html a grinder.py file-ban tárolja A keletkezett grinderpy kód hosszú, ezért az 1-3 programA Grinder proxy lista ennek csak a részleteit mutatja. Ezzel a A TCP Proxy egy közönséges proxy szerviz és módszerrel a böngészős munkát felvehetjük és ahogy az 1.16 ábra mutatja, képes a böngésző a már remélhetőleg jól ismert tesztkörnyezetés a szerver között átfolyatni a TCP adatfolya- tel akárhányszor le is futtathatjuk Tegyük is meg! A futási eredményt a 1.17 és 118 ábrák mot. érzékeltetik.

Azt gondoljuk, hogy ez a módszer igazán nagyszerű eszköz a végrehajtható tesztesetek generálására. A proxy képes a http és https tartalmat egyaránt kezelni. A HTTP és SSL beállításokról innen olvashatjuk el a részleteket: http://grinder.sourceforgenet/g3/ tcpproxy.html#ssl Sokszor csak a valódi http forgalmat szeretnénk felvenni, ilyenkor a proxy indítása így módosul: 1.16 ábra TCP Proxy A proxy a következő startProxy.sh paranccsal indítható el: // s t a r t P r o x y . sh #! / b i n / b a s h . / s e t G r i n d e r E n v sh j a v a −c l a s s p a t h $CLASSPATH n e t . g r i n d e r å TCPProxy −c o n s o l e −h t t p > g r i n d e r . py Induláskor ez a szöveg kerül ki a képernyőre, azaz a böngésző proxy beállítását a localhost:8001 portra kell majd beállítani: 18 #! / b i n / b a s h . / s e t G r i n d e r E n v sh j a v a −c l a s s p a t h $CLASSPATH n e t . g r i n d e r å TCPProxy −c o n s o l e > g r i n d e

r . l o g ki: Ez esetben a grinder.log egy részlete így néz −−−−−− 1 2 7 . 0 0 1 : 2 2 6 3 − > a d s osdn com : 8 0 −−−−−− GET /? ad id=5839& a l l o c i d =12703& s i t e i d=2&r e q u e s t i d å =8320720&1102173982760 HTTP/ 1 . 1 Host : a d s . osdn com User−Agent : M o z i l l a / 5 . 0 ( Windows ; U ; Windows NT 5 0 ; å en−US ; r v : 1 . 7 5 ) Gecko / 2 0 0 4 1 1 0 7 F i r e f o x / 1 0 A c c e p t : image / png , ∗ / ∗ ; q =0.5 Accept−Language : en−gb , en−u s ; q = 0 . 7 , en ; q =03 Accept−E n c o d i n g : g z i p , d e f l a t e Accept−C h a r s e t : ISO −8859−1, u t f −8; q = 0 . 7 , ∗ ; q =07 Keep−A l i v e : 300 Proxy−C o n n e c t i o n : keep−a l i v e Referer : http :// s o u r c e f o r g e . net / p r o j e c t s / g r i n d e r Tesztelés Grinder - Az alkalmazások teljesítményének mérése −−− a d s . osdn com : 8 0 − > 1 2 7 0 0 1 : 2 2 6 3

opened −− −−−−−− a d s . osdn com : 8 0 − > 1 2 7 0 0 1 : 2 2 7 3 −−−−−− HTTP/ 1 . 1 200 OK Date : Sat , 04 Dec 2004 1 5 : 2 6 : 2 7 GMT S e r v e r : Apache / 1 . 3 2 9 ( Unix ) mod gzip / 1 3 2 6 1 a å mod perl / 1 . 2 9 Pragma : no−c a c h e Cache−c o n t r o l : p r i v a t e Connection : c l o s e T r a n s f e r −E n c o d i n g : chunked Content−Type : image / g i f −−−−−− a d s . osdn com : 8 0 − > 1 2 7 0 0 1 : 2 2 7 3 −−−−−− 80B GIF89ae [ 0 0 ] ) [ 0 0 D50000C3C3C3FEFDFD ] hhhVVVyyy [ å F5CCD2D4D4D4CBCBCBD7 ] ’ F Amennyiben a gépünk eleve csak egy proxy szerveren keresztül éri el az erőforrást, úgy képesek vagyunk a -httpproxy vagy -httpsproxy kapcsolót használni a proxy indításakor. Itt a host:port formában kell a külső proxy-t megadni. Az eszköz még a port továbbításra is alkalmas Aki a TCP Proxy programról még többet szeretne megtudni, itt további olvasnivalót

talál: http://grinder.sourceforgenet/g3/ tcpproxy.html Az NTLM hitelesítés is használható 1.17 ábra Az egyes teszt URL-ek grafikonja 19 Tesztelés Grinder - Az alkalmazások teljesítményének mérése 1.18 ábra Az egyes teszt URL-ek számszerű adatai 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 1 −3. p r o g r a m l i s t a : A h t t p : / / i n d e x hu h e l y r e g e n e r á l t t e s z t python s c r i p t # The Grinder 3 . 7 1 # HTTP s c r i p t r e c o r d e d by TCPProxy a t 2 0 1 2 . 0 3 0 4 8 : 3 9 : 1 7 from n e t . g r i n d e r s c r i p t import Test from n e t . g r i n d e r s c r i p t G r i n d e r import g r i n d e r from n e t . g r i n d e r p l u g i n h t t p import HTTPPluginControl , HTTPRequest from HTTPClient import NVPair c o n n e c t i o n D e f a u l t s = HTTPPluginControl . g e t C o n n e c t i o n D e f a u l t s ( ) h t t p U t i l i t i e s = HTTPPluginControl . g e t H T T P U t i l i t i e s ( ) # To u s e a p r o x y

s e r v e r , uncomment t h e n e x t l i n e and s e t t h e h o s t and p o r t . # c o n n e c t i o n D e f a u l t s . s e t P r o x y S e r v e r (" l o c a l h o s t " , 8001) # These d e f i n i t i o n s a t t h e t o p l e v e l o f t h e f i l e a r e e v a l u a t e d once , # when t h e worker p r o c e s s i s s t a r t e d . 20 Tesztelés 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 Grinder - Az alkalmazások teljesítményének mérése connectionDefaults . defaultHeaders = [ NVPair ( ’ Accept−Language ’ , ’ hu , en−us ; q = 0 . 7 , en ; q =03 ’ ) , NVPair ( ’ Accept−Encoding ’ , ’ g z i p , ␣ d e f l a t e ’ ) , NVPair ( ’ User−Agent ’ , ’ M o z i l l a / 5 . 0 ␣ ( X11 ; ␣Ubuntu ; ␣ Linux ␣ i 6 8 6 ; ␣ r v : 1 0 0 2 ) ␣ Gecko /20100101 ␣å Firefox /10.02 ’ ) , ] h e a d e r s 0= [

NVPair ( ’ Accept ’ , ’ t e x t / html , a p p l i c a t i o n / xhtml+xml , a p p l i c a t i o n /xml ; q = 0 . 9 , ∗ / ∗ ; q =08 ’ ) , ] h e a d e r s 1= [ NVPair ( ’ Accept ’ , ’ t e x t / c s s , ∗ / ∗ ; q =0.1 ’ ) , NVPair ( ’ R e f e r e r ’ , ’ h t t p : / / i n d e x . hu/ ’ ) , ] h e a d e r s 2= [ NVPair ( ’ Accept ’ , ’ ∗/∗ ’ ) , NVPair ( ’ R e f e r e r ’ , ’ h t t p : / / i n d e x . hu/ ’ ) , ] . . # C r e a t e an HTTPRequest f o r each r e q u e s t , t h e n r e p l a c e t h e # r e f e r e n c e t o t h e HTTPRequest w i t h an i n s t r u m e n t e d v e r s i o n . # You can a c c e s s t h e unadorned i n s t a n c e u s i n g r e q u e s t 1 0 1 . target r e q u e s t 1 0 1 = HTTPRequest ( u r l=u r l 0 , h e a d e r s=h e a d e r s 0 ) r e q u e s t 1 0 1 = Test ( 1 0 1 , ’GET␣ / ’ ) . wrap ( r e q u e s t 1 0 1 ) r e q u e s t 1 0 2 = HTTPRequest ( u r l=u r l 0 , h e a d e r s=h e a d e r s 1 )

r e q u e s t 1 0 2 = Test ( 1 0 2 , ’GET␣ c i m l a p d e f a u l t . c s s ’ ) wrap ( r e q u e s t 1 0 2 ) r e q u e s t 1 0 3 = HTTPRequest ( u r l=u r l 0 , h e a d e r s=h e a d e r s 2 ) r e q u e s t 1 0 3 = Test ( 1 0 3 , ’GET␣ e x c a n v a s . c o m p i l e d j s ’ ) wrap ( r e q u e s t 1 0 3 ) r e q u e s t 1 0 4 = HTTPRequest ( u r l=u r l 0 , h e a d e r s=h e a d e r s 2 ) r e q u e s t 1 0 4 = Test ( 1 0 4 , ’GET␣ p r o t o t y p e . min j s ’ ) wrap ( r e q u e s t 1 0 4 ) . . c l a s s TestRunner : """A TestRunner i n s t a n c e i s c r e a t e d f o r each worker t h r e a d . """ # A method f o r each r e c o r d e d page . def page1 ( s e l f ) : """GET / ( r e q u e s t s 101 −115) . """ r e s u l t = r e q u e s t 1 0 1 .GET( ’ / ’ ) s e l f . token il = h t t p U t i l i t i e s . valueFromBodyURI ( ’ i l ’ ) # ’ / ’ s e l f . token t = h t t p U t i l i t i e

s . valueFromBodyURI ( ’ t ’ ) # ’/24 ora / ’ # 10 d i f f e r e n t v a l u e s f o r token rov found i n r e s p o n s e , u s i n g t h e f i r s t one . s e l f . token rov = h t t p U t i l i t i e s . valueFromBodyURI ( ’ r o v ’ ) # ’ / ’ # 13 d i f f e r e n t v a l u e s f o r token a found i n r e s p o n s e , u s i n g t h e f i r s t one . s e l f . token a = h t t p U t i l i t i e s . valueFromBodyURI ( ’ a ’ ) # ’ h t t p : / / t o t a l c a r hu / ’ # 145 d i f f e r e n t v a l u e s f o r tok en id found i n r e s p o n s e , u s i n g t h e f i r s t one . s e l f . token id = h t t p U t i l i t i e s . valueFromBodyURI ( ’ i d ’ ) # ’ i n x c l ’ . . 21 Tesztelés 2. Egységtesztelés a JUnit használatával Egységtesztelés a JUnit használatával Bizonyára sok olvasónk hallott már az egységtesztek fontosságáról és a teszt vezérelt (Test Driven) fejlesztés hatékonyságáról. A módszer alapvető

gondolata, hogy a fejlesztés során, azzal párhuzamosan készítjük a teszteseteket is, amivel azonnal ellenőrizni tudjuk a kód működését Jelentős előny, hogy ezek a tesztesetek a fejlesztés során mindvégig rendelkezésünkre állnak, azokat akármennyiszer újra és újra lefuttathatjuk, amikor változtatunk a kódon. Szép látni, hogy egy-egy nagyobb alkalmazás esetén akár több száz ilyen teszteset is végrehajtódhat minimális energiabefektetés mellett. Írásunkban az egységtesztelést mutatjuk be az elterjedten használt JUnit keretrendszer használata segítségével (webhelye: http://www. junit.org/) A JUnit egy szabad forráskódú modultesztelő rendszer, amely Java programjaink automatikus teszteléséhez nyújt segítséget, így javíthatjuk programjaink minőségét, ugyanakkor hibakeresési időt takaríthatunk meg. A teszt vezérelt fejlesztés (TDD=Test Driven Development) szabályai szerint a kód írásával párhuzamosan fejlesztjük a kódot

tesztelő osztályokat is, ezek az egységtesztek. A JUnit az egységtesztek karbantartására és csoportos futtatására szolgál. A teszteket gyakran a build folyamat részeként szokták beépíteni. JUnit framework fizikailag egy java jar fájlba van csomagolva. A keretrendszer jelenleg 2 elterjedtebb verzióban használatos, amelyekben az osztályok a következő alapcsomag alatt találhatóak: • JUnit 3.8-as vagy korábbi verzióiban a junitframework • JUnit 4-es org.junit vagy későbbi verzióiban Erich Gamma alkotta meg. A JUnit Hello Word A JUnit tesztek a junit.frameworkTestCase osztály leszármazottjai, és a tesztelendő kódot publikus testMetodus() nevű metódusokban adjuk meg. A teszt futása során az összes ilyen metódus futtatásra kerül, amelynek háromféle eredménye lehet: 1. Sikeres végrehajtás(pass): ez azt jelenti, hogy a teszt rendben lefutott, és az ellenőrzési feltételek mind teljesültek. 2. Sikertelen végrehajtás (failure): a teszt

lefutott, de valamelyik ellenőrzési feltétel nem teljesült. 3. Hiba(error): A teszt futása során valami komolyabb hiba merült fel, például egy Exception dobódott valamelyik tesztmetódus futása során, vagy nem volt tesztmetódus a megadott tesztosztályban. A lenti példában az első tesztosztályunk neve a JUnit-ot alapul véve más programozási nyel- NehanyTest lesz, amit parancssorból így tudunk vekre is portolták ezt az egységteszt keretrend- végrehajtani: szert: C (CUnit), C# (NUnit), C++ (CPPj a v a j u n i t . t e x t u i TestRunner NehanyTest Unit), Delphi (DUnit), Pascal (FPCUnit), PHP Az osztály kódját a 2-1. programlista tartal(PHPUnit), Python (PyUnit) A JUnit keretrendszert 2 világhírű fejlesztő, Kent Beck és mazza 22 Tesztelés 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 Egységtesztelés a JUnit használatával // 2−1. p r o g r a m l i s t a : Az e l s ő t e s z t o s z t á l y u n

k ( JUnit 3 8 ) package o r g . c s ; import j u n i t . framework TestCase ; import j u n i t . framework T e s t S u i t e ; public c l a s s NehanyTest extends TestCase { public NehanyTest ( ) { } public NehanyTest ( S t r i n g name ) { super ( name ) ; } public void t e s t H e l l o ( ) { f a i l ( " Egyből ␣ i t t ␣ kiakadtam ␣ : −) " ) ; } public void t e s t S t r i n g S i z e ( ) { S t r i n g s = "Alma␣a␣ f a ␣ a l a t t " ; a s s e r t T r u e ( " Hosszabb , ␣ mint ␣ 1 0 : ␣ " , s . l e n g t h ( ) < 1 0 ) ; } } public s t a t i c void main ( S t r i n g [ ] a r g s ) { j u n i t . t e x t u i TestRunner run (new T e s t S u i t e ( NehanyTest c l a s s ) ) ; } A 8. sor mutatja, hogy ez egy TestCase osztály, ahol a 14-17 sorok közötti String paraméteres konstruktort is megadtuk Ez nem kötelező, de amennyiben egy-egy tesztkészletet metódusonként akarunk összeépíteni, úgy egy String paramétert át

kell venni, és azt a super(name) hívással továbbítani. A használatot a későbbiekben mutatjuk be A NehanyTest class egy teljes tesztosztály, aminek az összes tesztmetódusát lefuttatja a 32. sorban lévő main()-beli hívás. Ennek eredményét mutatja a lenti 2-1 Futási kép. Lényeges megjegyezni, hogy a JU- nit csak azokat a publikus metódusokat tekinti automatikusan tesztmetódusoknak, amik a test előtaggal kezdődnek. Jelen példában 2 ilyen metódusunk van: testHello(), testStringSize() Az összes többi metódus csak utility funkciót tölt be az osztályon belül. A példában a TestRunner class run() metódusát programból használtuk, amihez még fontos megérteni a tesztkészlet (TestSuite) fogalmát is. A tesztkészlet azoknak a metódusoknak a halmaza, amiket a TestRunner le fog futtatni. Példánkban egy olyan névtelen TestSuite objektumot adtunk át a run() me23 Tesztelés tódusnak, ami azokat a tesztmetódusokat tartalmazza, amik a NehanyTest

osztályban test szócskával kezdődnek. Megjegyezzük, hogy létezik a JUnit csomagon belül grafikus felületű tesztfuttató is, de azt ebben a cikkben nem tárgyaljuk, ugyanis a jelentősége nem túl nagy az automatikus tesztfuttatások során. Egységtesztelés a JUnit használatával tályokból, hogyan lehet tesztkészletet készíteni, ugyanis az ebben lévő konkrét elemi tesztek végigfuttatása jelenti a unit teszt elvégzését. A 2-2 programlista egy nagyon triviális másik tesztosztályt is mutat, szerepe csak annyi, hogy ne csak 1 db tesztosztályunk legyen, amikor összeépítjük a tesztkészletet. A 2-3. programlista egy nagyon egyszerű, külön TesztelendoTestSuite nevű osztályTalán az előző példából is kitűnt, hogy a JU- ban megvalósított tesztkészlet összeállítást és nit lényeges része, hogy a meglévő TestCase osz- annak futtatását mutatja. Tesztkészletek létrehozása 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 2−2. p r o g

r a m l i s t a : Egy másik t e s z t o s z t á l y ( JUnit 3 8 ) package o r g . c s ; import j u n i t . framework TestCase ; public c l a s s NehanyMasikTest extends TestCase { public void t e s t P r o b a 1 ( ) { System . out p r i n t l n ( " Próba ␣ 1 " ) ; } } public void t e s t P r o b a 2 ( ) { System . out p r i n t l n ( " Próba ␣ 2 " ) ; } A 16-21 sorokban a suite() statikus metódus egy tesztkészlet, azaz TestSuite objektumot ad vissza. A működés itt is az, hogy a 19 sorban átadott NehanyTest.class minden test kezdetű metódusa kerüljön be a készletbe. A tesztelés meghívását, azaz a tesztkészlet végrehajtásának módját a main() metódusból olvashatjuk ki. A következő példában bemutatjuk azokat a technikákat, amikkel kifinomultabb módon is össze tudunk tesztkészleteket állítani. Az eddigiekhez képest meg fogjuk tanulni, hogy más test suiteökből, vagy akár az egyes metódusokból hogyan építhetünk a

céljainkhoz illeszkedő olyan teszt24 készletet, aminek egyes elemeit más-más tesztosztályból emeljük ki. Nézzük a 2-4 programlistát! A 17 sortól kezdődő statikus suite() metódus most különféle módszerekkel állítja össze a tesztkészlet tesztmetódus halmazát. A 21-22 sorok hagyományos módon létrehoznak 2 új készletet, aminek suite1 és suite2 a neve, majd ezeket a 25-26 sorokban hozzáfűzzük a mi felépítendő suite objektumunkhoz. A példa 27-28 sorai mutatják, hogy a NehanyTest és NehanyMasikTest osztályokra mindezt hogyan lehet 1 lépésben is elvégezni, köztes teszt suite létrehozása nélkül. Tesztelés 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // 2−3. p r o g r a m l i s t a : Egy k ü l ö n t e s z t k é s z l e t k i a l a k í t á s a package o r g . c s ; import j u n i t . framework Test ; import j u n i t . framework TestCase ; import j u n i t . framework T e s t S u i t e ; public c l a s s

T e s z t e l e n d o T e s t S u i t e extends TestCase { public T e s z t e l e n d o T e s t S u i t e ( S t r i n g testName ) { super ( testName ) ; } public s t a t i c Test s u i t e ( ) { T e s t S u i t e s u i t e = new T e s t S u i t e ( " T e s z t e l e n d o T e s t S u i t e " ) ; s u i t e . a d d T e s t S u i t e ( NehanyTest c l a s s ) ; return s u i t e ; } } public s t a t i c void main ( S t r i n g [ ] a r g s ) { j u n i t . t e x t u i TestRunner run ( s u i t e ( ) ) ; } A 30-39 sorokból azt tanulhatjuk meg, hogy 1 db metódust (esetünkben most ez a testStringSize() metódus) hogyan tudunk a tesztkészlethez fűzni. A 42 és 43 sorokból szintén azt leshetjük el, hogy egy-egy metódust mi módon tehetünk a teszthalmazba. Remélhetőleg még 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Egységtesztelés a JUnit használatával emlékszünk arra, hogy a NehanyTest class részére implementeltáltunk egy String paraméterű konstruktort is. Nos itt

van ennek jelentősége, mert ezen keresztül adható meg, hogy az osztálynak melyik tesztmetódusát szeretnénk befűzni a készlethez. // 2−4. p r o g r a m l i s t a : Egy t e s z t k é s z l e t f e j l e t t e b b l é t r e h o z á s a package o r g . c s ; import j u n i t . framework Test ; import j u n i t . framework TestCase ; import j u n i t . framework T e s t S u i t e ; public c l a s s T e s z t e l e n d o T e s t S u i t e extends TestCase { public T e s z t e l e n d o T e s t S u i t e ( S t r i n g testName ) { super ( testName ) ; 25 Tesztelés 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 Egységtesztelés a JUnit használatával } public s t a t i c Test s u i t e ( ) { T e s t S u i t e s u i t e = new T e s t S u i t e ( " T e s z t e l e n d o T e s t S u i t e " ) ; T e s t S u i t e s u i t e 1 = new T e s t S u i t e ( NehanyTest . c l a s s ) ; T e s t S u i t

e s u i t e 2 = new T e s t S u i t e ( NehanyMasikTest . c l a s s ) ; // T e s z t k é s z l e t e k ö s s z e f ű z é s e s u i t e . addTest ( s u i t e 1 ) ; s u i t e . addTest ( s u i t e 2 ) ; s u i t e . a d d T e s t S u i t e ( NehanyTest c l a s s ) ; s u i t e . a d d T e s t S u i t e ( NehanyMasikTest c l a s s ) ; // 1 darab metódus h o z z á a d á s a a t e s z t k é s z l e t h e z TestCase t e s t e k = new NehanyTest ( "Ez␣ 1 ␣ darab ␣ metódus ␣ t e s z t j e ! " ) { @Override public void runTest ( ) { testStringSize () ; } }; s u i t e . addTest ( t e s t e k ) ; // További metódusok h o z z á a d á s a a t e s z t k é s z l e t h e z − másképpen s u i t e . addTest ( new NehanyTest ( " t e s t H e l l o " ) ) ; s u i t e . addTest ( new NehanyTest ( " t e s t S t r i n g S i z e " ) ) ; } } return s u i t e ; // v i s s z a a d j u k a t e s z t k é s z l e t e t public s t a t i c void main ( S t r i n

g [ ] a r g s ) { j u n i t . t e x t u i TestRunner run ( s u i t e ( ) ) ; } mindkét tesztesetünk sikertelen lett, ahogy azt a 20. sorból ki is olvashatjuk Kijavítva mindA korábbiakhoz képest itt csak össze szeretnénk két tesztmetódust, a teszt is sikeresen lefut (24. foglalni, hogy mit jelent egy automatizált teszt sor), amiről csak egy szerény OK (2 tests) inforfuttatása. Azt már tudjuk, hogy ez mindig egy mál bennünket. előzetesen összeállított TestSuite objektum futtatását jelenti, amit viszont már több módon Eddig csak a tesztek szerkezeti összeállítáis el tudunk készíteni, amire a 2-5. program- sára és végrehajtására koncentráltunk, így itt az lista csak egy újabb példa. Futtatásakor a 2-1 ideje annak is, hogy megnézzük miket kínál neFutási kép-nek megfelelő outputot kapjuk, azaz künk a JUnit a tesztmetódusok megalkotásához Egy tesztkészlet futtatása 26 Tesztelés 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

21 22 23 24 25 26 27 28 29 30 Egységtesztelés a JUnit használatával // 2−5. p r o g r a m l i s t a : A t é s z t k é s z l e t ö s s z e á l l í t á s a package o r g . c s ; import j u n i t . framework Test ; import j u n i t . framework TestCase ; import j u n i t . framework T e s t S u i t e ; public c l a s s T e s z t e l e n d o T e s t S u i t e extends TestCase { public T e s z t e l e n d o T e s t S u i t e ( S t r i n g testName ) { super ( testName ) ; } public s t a t i c Test s u i t e ( ) { T e s t S u i t e s u i t e = new T e s t S u i t e ( " T e s z t e l e n d o T e s t S u i t e " ) ; s u i t e . addTest ( new NehanyTest ( " t e s t H e l l o " ) ) ; s u i t e . addTest ( new NehanyTest ( " t e s t S t r i n g S i z e " ) ) ; } } return s u i t e ; public s t a t i c void main ( S t r i n g [ ] a r g s ) { j u n i t . t e x t u i TestRunner run ( s u i t e ( ) ) ; } 1 2 −1. F u t á s i kép : A t e s z t f

u t t a t á s i eredménye : 2 −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− 3 .FF 4 Time : 0 , 0 0 4 5 There were 2 f a i l u r e s : 6 1 ) t e s t H e l l o ( o r g . c s NehanyTest ) j u n i t framework A s s e r t i o n F a i l e d E r r o r : Egyből i t t å kiakadtam : −) 7 a t o r g . c s NehanyTest t e s t H e l l o ( NehanyTest j a v a : 2 0 ) 8 a t sun . r e f l e c t NativeMethodAccessorImpl i n v o k e 0 ( N a t i v e Method ) 9 a t sun . r e f l e c t NativeMethodAccessorImpl i n v o k e ( NativeMethodAccessorImpl å java :3 9 ) 10 a t sun . r e f l e c t D e l e g a t i n g M e t h o d A c c e s s o r I m p l i n v o k e ( å DelegatingMethodAccessorImpl . java : 2 5 ) 11 a t o r g . c s T e s z t e l e n d o T e s t S u i t e main ( T e s z t e l e n d o T e s t S u i t e j a v a : 5 3 ) 12 2 ) t e s t S t r i n g S i z e ( o r g . c s NehanyTest ) j u n i t

framework A s s e r t i o n F a i l e d E r r o r : å Hosszabb , mint 1 0 : 13 a t o r g . c s NehanyTest t e s t S t r i n g S i z e ( NehanyTest j a v a : 2 6 ) 14 a t sun . r e f l e c t NativeMethodAccessorImpl i n v o k e 0 ( N a t i v e Method ) 15 a t sun . r e f l e c t NativeMethodAccessorImpl i n v o k e ( NativeMethodAccessorImpl å java :3 9 ) 27 Tesztelés 16 Egységtesztelés a JUnit használatával a t sun . r e f l e c t D e l e g a t i n g M e t h o d A c c e s s o r I m p l i n v o k e ( å DelegatingMethodAccessorImpl . java : 2 5 ) a t o r g . c s T e s z t e l e n d o T e s t S u i t e main ( T e s z t e l e n d o T e s t S u i t e j a v a : 5 3 ) 17 18 19 FAILURES ! ! ! 20 T e s t s run : 2 , F a i l u r e s : 2 , E r r o r s : 0 21 22 2 −2. F u t á s i kép : A metódusok j a v í t á s a után : 23 −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− 24 OK ( 2

t e s t s ) szi, azaz megbuktatja. Ezt a hívást a nyelv beépített feltételvizsgálataival lehet használni, de Miről is szól egy unit teszt? Általánosságban az is lehet, hogy csak azt akarjuk megakadáfogalmazva olyan állítások vizsgálatáról, amik lyozni, hogy ezen a ponton továbblépjen a teszt a program futása során vagy teljesülnek, vagy futása. nem teljesülnek. Ezek az állítások a program éppen elérhető állapota, azaz a változói fölött megfogalmazott kijelentések (assert) A programassertTrue és assertFalse ban ezeket az assert-eket bárhova elhelyezhetjük és vizsgálható velük, hogy odaérve teljesülnek- Az assertTrue( boolean ) ellenőrzi, hogy a parae azok az elvárások, amik programunk helyes, méter logikai kifejezés igaz-e. Ennek ellentéte az vagy éppen helytelen működését hivatottak iga- assertFalse( boolean ). zolni. Amennyiben egy assert hamisnak minősül, úgy a tesztmetódus futása megszakad és a JUnit számára ez a

teszteset a sikertelen futást assertEquals jelenti. Az eddig leírtak alapján látható, hogy a tesztmetódusok akkor igazán hatékonyak, ha Az assertsEquals([String message], Object, Obazokba kellő helyen és kellő számban el tudunk ject) ellenőrzi, hogy az értékek egyenlőek-e (tömhelyezni jól megfogalmazott assert állításokat. bök esetében a referenciát ellenőrzi, nem az érAz összes, assert-tel kezdődő nevű ellenőrző me- tékeket), amihez az objektum equals() metótódusnak van olyan változata is, amelynek az dusát használja Ennek a vizsgálatnak van 2 első paramétere egy hibaüzenet. Ezt a tesztelő speciális esete Az egyik esetben assertsEqukörnyezet írja ki akkor, amikor a teszt megbu- als([String message], int, int) vizsgálatot végkik (azaz nem teljesül a feltétel) Nézzük meg zünk, mint egyszerű skalár egyenlőség A astehát, hogy milyen típusú assert-eket biztosít a sertEquals( [String message], double, double, double) 2 double

skalár egyezőségét vizsgálja a keretrendszer! megadott tolerancián belül, amit az utolsó double értékben tudunk definiálni. fail Tesztprogramok írása Ez az egyetlen olyan eszköz, ami bár nem assert, mégis hatékonyan használható. A fail(String) assertNull „sikertelenné teszi a metódust”, segítségével ellenőrizhetjük, hogy elérünk-e egy bizonyos kód- Az assertNull([message], object) ellenőrzi, hogy részt. Ideérve a tesztet azonnal sikertelenné te- null objektumról van-e szó 28 Tesztelés assertNotNull Egységtesztelés a JUnit használatával A Netbeans JUnit plugin Az assertNotNull([message], object) ellenőrzi, A továbbiakban megnézzük, hogy a Netbeans környezet hogyan támogatja a JUnit tesztek íráhogy a paraméter nem null objektum-e. sát. Már itt az elején kiemeljük, hogy egy új Netbeans projekt esetén szinte mindig létrejön assertSame a Source Packages mellé egy Test Packages ág, Az assertSame([String], expected,

actual) akkor ugyanis a JUnit ide pakolja a teszteléshez kötődő teljesül, ha a két változó ugyanazon objektumra TestCase és TestSuite osztályokat. Ez kellemes tulajdonság, mert a kód üzleti és tesztelő része mutat, azaz a referenciájuk megegyezik. mindig elszeparálódik egymástól. A példa kedvéért a 2-5 programlista egy tesztelendő üzleti assertNotSame osztály kódját tartalmazza. A példa természeteAz assertNotSame([String], expected, actual) ak- sen nagyon egyszerű most is, mindössze 2 metókor teljesül, ha a két változó eltérő objektumra dust tartalmaz, de ez a céljainknak tökéletesen mutat, azaz a referenciájuk nem egyezik meg. megfelelő. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 2−5. p r o g r a m l i s t a : Egy t e s z t e l e n d ő o s z t á l y package o r g . c s ; public c l a s s T e s z t e l e n d o { public int add ( int a , int b ) { return a+b ; } } public int minimum ( int a , int b ) { i f ( a < b ) return a ; e l s

e return b ; } A Netbeans támogatás talán leghasznosabb része, hogy minden ilyen osztályra egy TestCase osztály generálható, ahogy a 2.1 ábra is mutatja. Mindössze annyi a feladat, hogy jobb click az egérrel és a tesztosztály hozzáadása egy létező osztályra funkciót választjuk ki. Ennek hatására esetünkben létrejön egy ugyanabba a csomagba tartozó üres tesztosztály, azaz az org.csTesztelendoTest 2.1 ábra Egy tesztosztály generálása 29 Tesztelés Egységtesztelés a JUnit használatával Mindegyik tesztelendő metódusra egy tesztmetódust generálódott, ahogy azt a 2-6. programlista mutatja is (a metódusok implementációját persze kézzel írtuk meg) A 22 ábra a Netbeans másik JUnit támogatását mutatja, segítségével egy tesztkészlet osztályt adhatunk a projektünkhöz. A generálás során megkérdezi, hogy 3.8 vagy 4x-es JUnit-ot szeretnénk-e használni A 2-5 programlista induló kódját is így állítottuk elő, de persze azt

utána már jelentősen átalakítottuk. A Netbeans lényegében az itt leírtakkal támogatja a JUnit-ot Az Eclipse és más hasonló környezetek is hozzávetőleg ilyen támogatást adnak. 2.2 ábra Egy tesztkészlet generálása 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // 2−6. p r o g r a m l i s t a : A g e n e r á l t t e s z t e s e t o s z t á l y package o r g . c s ; import j u n i t . framework TestCase ; public c l a s s T e s z t e l e n d o T e s t extends TestCase { public T e s z t e l e n d o T e s t ( S t r i n g testName ) { super ( testName ) ; } public void testAdd ( ) { T e s z t e l e n d o t = new T e s z t e l e n d o ( ) ; int c = t . add ( 3 , 1 1 ) ; System . out p r i n t l n ( "3+11␣=␣ " + c ) ; a s s e r t E q u a l s (14 , 14) ; } } 30 public void testMinimum ( ) { T e s z t e l e n d o t = new T e s z t e l e n d o ( ) ; int min = t . minimum ( 1 2 , 8 ) ; a s s e r t E q u a l s ( 1 2 , min ) ;

} Tesztelés Egységtesztelés a JUnit használatával Annotációk metódusokra - JUnit 4 tesztelési környezet előkészítésére alkalmazzuk. Ez a lehetőség a JUnit 3 TestCase osztályának felülírható setUp() metódusát valósítja meg. A cél az, hogy legyen egy olyan inicializálási lehetőség minden tesztmetódus hívás előtt, ami beállítja a változókat, megnyitja a file-okat és/vagy adatbázis kapcsolatokat, stb. Fontos kiemelni, hogy a tesztmetódusok hívási sorrendje általában nem garantált, így nem szabad kihasználni @Test a tesztlépések mellékhatásait, azaz az általuk beAmikor egy bármilyen nevű publikus metódust állított előző állapotot. @Test jelzéssel látunk el, úgy az a JUnit számára jelzi, hogy teszt metódusról van szó. A @After @Test annotáció 2 hasznos paraméterezési lehetőséggel is rendelkezik. Az első esetben egy el- Minden tesztmetódus után végre lesz hajtva, a lefoglalt erőforrások

felszabadítására alkalmazvárt exception adható meg: zuk. A JUnit 3-ban ez a tearDown() felülírásra import o r g . j u n i t ∗ ; tervezett metódusnak felel meg. A JUnit 4 hasznos lehetősége, hogy kihasználva a modernebb Java nyelv lehetőségét, annotációkkal szabályozza a tesztelés körülményeit, illetve pontosabban fogalmazva egy új TestCase osztály létrehozását. Tekintsük át emiatt először, hogy melyek ezek a „@”-os utasítások! public c l a s s Junit4ExceptionTest { @Test ( e x p e c t e d = A r i t h m e t i c E x c e p t i o n . c l a s s ) public void divisionWithException ( ) { int i = 1/0; } } @BeforeClass A tesztek futtatása előtt lesz végrehajtva, előkészítő műveletek elvégzésére alkalmazzuk (pl. adatbázis kapcsolat megnyitása). Ebben az esetben teszteljük, hogy a metódus egy bizonyos típusú kivételt dob-e, azaz ilyenkor a metódus futása nem számít sikertelennek. @AfterClass A másik paraméterezési lehetőség

egy timeout Az összes teszt lefuttatása után hívódik meg, deklarálása a következő formátumban: erőforrások felszabadítására alkalmazzuk (pl. import o r g . j u n i t ∗ ; adatbázis kapcsolat bezárása). p u b l i c c l a s s Junit4TimeTest { @Test ( t i m e o u t = 5 0 0 ) public void i n f i n i t y ( ) { while ( true ) ; } } @Ignore Az így megjelölt tesztmetódus figyelmen kívül hagyódik, azaz nem futtatja le a TestRunner. A teszt vezérelt programozás során ezzel jelezSikertelen a teszt, ha a metódus futása tovább zük, hogy a hozzátartozó kód változott és a teszt tart mint 500 milliszekundum, azaz fél másod- kódja még nem került megfelelően frissítésre. perc. import o r g . j u n i t ∗ ; @Before Az ilyen címkével ellátott metódus minden tesztmetódus hívás előtt végre lesz hajtva, emiatt a public c l a s s J u n i t 4 I g n o r e T e s t { @Ignore ( "Not␣Ready␣ t o ␣Run" ) @Test public void d i v i s i o n W i

t h E x c e p t i o n ( ) { 31 Tesztelés } } Egységtesztelés a JUnit használatával System . out p r i n t l n ( "Method␣ i s ␣ not ␣ r e a d y ␣å yet " ) ; @Parameters Sok esetben előfordul, hogy ugyanazt a tesztmetódust különféle inputokon akarjuk tesztelni, amire kényelmes megoldást ad a @Parameters használata arra a metódusra, amelyik a paraméterek halmazát előállítja. A lenti példában ez a data(). A konstruktor feladata ilyenkor, hogy ebből a halmazból mindig vegye a következőt, ahogy ezt a JUnit4ParameterizedTest konstruktorának number tagjára is látjuk. package o r g . c s a d a p t e r as400dq c o r e ; import j a v a . u t i l import j a v a . u t i l import o r g . j u n i t import o r g . j u n i t import o r g . j u n i t import o r g . j u n i t Parameters ; . Arrays ; . Collection ; . Test ; . r u n n e r RunWith ; . runners Parameterized ; . runners Parameterized å @RunWith ( v a l u e = P a r a m e t e r

i z e d . c l a s s ) public c l a s s J U n i t 4 P a r a m e t e r i z e d T e s t { private i n t number ; public J U n i t 4 P a r a m e t e r i z e d T e s t ( i n t number ) { t h i s . number = number ; } @Parameters public s t a t i c C o l l e c t i o n <O b j e c t [] > data ( ) { O b j e c t [ ] [ ] data = new O b j e c t [ ] [ ] { { 1 } , { 2 } , { 3 } , {4} }; return Arrays . a s L i s t ( data ) ; } } 32 @Test public void pushTest ( ) { System . out p r i n t l n ( " P a r a m e t e r i z e d ␣å Number␣ i s ␣ : ␣ " + number ) ; } 2.3 ábra A teszt futtatása Netbeans-ben A 2.3 ábra mutatja JUnit4ParameterizedTest futási képét grafikus tesztfuttató környezetben. Ehhez csak annyit kellett tennünk, hogy jobb egér click és futtat, a többit a Netbeans JUnit plugin elintézi. Amennyiben szöveges módban magunk akarjuk a futtató kódot is megírni, úgy ez a main() metódus hozzáadásával a legkönnyebb: p u b l i c s t a t i c v o i d

main ( S t r i n g [ ] a r g s ) { R e s u l t r = o r g . j u n i t r u n n e r JUnitCore å runClasses ( JUnit4ParameterizedTest . å class ) ; } f o r ( i n t i =0; i <r . g e t F a i l u r e C o u n t ( ) ; i ++) { System . out p r i n t l n ( r g e t F a i l u r e s ( ) g e t ( å i)); } @RunWith és @Suite Ezzel a 2 annotációval létező tesztosztályokból (esetünkben ez most a következő kettő: JunitTest1.class és JunitTest2class) építhetünk össze egy futtatható tesztkészletet a következő módon: im por t o r g . j u n i t r u n n e r RunWith ; im por t o r g . j u n i t r u n n e r s S u i t e ; @RunWith ( S u i t e . c l a s s ) @Suite . S u i t e C l a s s e s ( { JunitTest1 . class , JunitTest2 . c l a s s }) public c l a s s Junit4Test { } Tesztelés Egységtesztelés a JUnit használatával Egy JUnit 4 tesztosztály Az előzőekben tanult lehetőségeket a 2-7. programlista segítségével szeretnénk röviden bemutatni A 12 sortól

kezdődő checkConfigFile() tesztmetódus már jobban hasonlít egy éles teszt1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 esethez, láthatjuk, hogy az assert-ek segítségével a futás során különféle helyeken végezhetőek státusz ellenőrzések. Szeretnénk kiemelni, hogy a 41. sorban lévő anException() metódus kivételt dob, de ez nem lesz hibás tesztfutás, hiszen a @Test annotációnál ezt jeleztük. // 2−7. p r o g r a m l i s t a : Egy JUnit 4 t e s z t o s z t á l y package o r g . c s a d a p t e r as400dq c o r e ; import j a v a . i o F i l e ; import s t a t i c o r g . j u n i t A s s e r t ∗ ; import o r g . j u n i t Test ; public c l a s s B a s i c T e s t s { @Test public void c h e c k C o n f i g F i l e ( ) throws E x c e p t i o n { // I s i t c o r r e c t name? S t r i n g name = " as400dq−adapter −c o n f i g . xml" ; // I s i t c o r r e c t f i l e

name t y p e ? i f ( ! name . endsWith ( "xml" ) ) { f a i l ( " Hibás ␣a␣ k o n f i g u r á c i ó s ␣ f i l e ␣ k i t e r j e s z t é s e " ) ; } // C o r r e c t name? a s s e r t E q u a l s ( " C o r r e c t ␣ c o n f i g ␣ f i l e ␣name? " , name , " as400dq−adapter −c o n f i g . å xml" ) ; } // E x i s t c o n f i g f i l e on p a t h ? F i l e f i l e = new F i l e ( "/ bea / m o l c o n f i g / AdminServer / " + name ) ; a s s e r t F a l s e ( " Check ␣ c o n f i g ␣XML" , f i l e . e x i s t s ( ) ) ; @Test public void runToFail ( ) throws E x c e p t i o n { System . out p r i n t l n ( " I n d u l " ) ; System . out p r i n t l n ( " I d e é r t " ) ; i f ( 2 == 2 ) { f a i l ( "Bummm! " ) ; } System . out p r i n t l n ( "Nem␣ j ö t t ␣ i d e " ) ; } @Test ( e x p e c t e d = E x c e p t i o n . c l a s s ) public void anException (

) throws E x c e p t i o n { 33 Tesztelés 43 44 45 } Egységtesztelés a JUnit használatával throw new E x c e p t i o n ( "Dobtam␣ egy ␣ k i v é t e l t ! " ) ; } // end c l a s s A fenti BasicTests osztályra épülve a 2-8. dunk készíteni JUnit 3 típusú tesztkészletet JUprogramlista bemutatja a tesztkészlet egy lehet- nit 4 tesztosztály használatával, amihez a JUséges létrehozását és futtatását A példa ér- nit4TestAdapter osztályt kell alkalmazni dekessége, hogy megmutatja azt is, hogyan tu1 // 2−8. p r o g r a m l i s t a : Egy t e s z t k é s z l e t , ami t a r t a l m a z z a a B a s i c T e s t s o s z t á l y t 2 3 package o r g . c s a d a p t e r as400dq c o r e ; 4 5 import j u n i t . framework JUnit4TestAdapter ; 6 import j u n i t . framework T e s t S u i t e ; 7 import o r g . j u n i t A f t e r C l a s s ; 8 import o r g . j u n i t B e f o r e C l a s s ; 9 import o r g . j u n i t Test ; 10 import o r g . j

u n i t ru n n e r RunWith ; 11 import o r g . j u n i t r u n n e r s S u i t e ; 12 13 @RunWith( S u i t e . c l a s s ) 14 @Suite . S u i t e C l a s s e s ( 15 { 16 o r g . c s a d a p t e r as400dq c o r e AS400DataQueueAdaptersTest c l a s s , 17 o r g . c s a d a p t e r as400dq c o r e B a s i c T e s t s c l a s s 18 } ) 19 public c l a s s AS400DataQueueAdaptersTestSuite 20 { 21 @BeforeClass 22 public s t a t i c void s e t U p C l a s s ( ) throws E x c e p t i o n 23 { 24 } 25 26 @AfterClass 27 public s t a t i c void tearDownClass ( ) throws E x c e p t i o n 28 { 29 } 30 31 public s t a t i c j u n i t . framework Test suiteAS400DataQueueAdaptersTest ( ) 32 { 33 return new JUnit4TestAdapter ( AS400DataQueueAdaptersTest . c l a s s ) ; 34 } 35 36 public s t a t i c j u n i t . framework Test s u i t e B a s i c T e s t s ( ) 37 { 38 return new JUnit4TestAdapter ( B a s i c T e s t s . c l a s s ) ; 39 } 40 41 public s t a t i c void main ( S t r i n g [ ] a

r g s ) 34 Tesztelés 42 43 44 45 46 47 48 { // } } Egységtesztelés a JUnit használatával System . out p r i n t l n ( " S t a r t ␣ t e s t s " ) ; j u n i t . t e x t u i TestRunner run ( s u i t e B a s i c T e s t s ( ) ) ; j u n i t . t e x t u i TestRunner run ( suiteAS400DataQueueAdaptersTest ( ) ) ; System . out p r i n t l n ( "End␣ t e s t s " ) ; A példában látott JUnit4TestAdapter osztályon keresztül használt megoldás lehetővé teszi a korábban megismert összetett JUnit 3 típusú készletek összeépítését is, ahogy a következő 3 sor demonstrálja: ramozási módszertan (extreme programmming XP) nagy hangsúlyt fektet a programok tesztelésére. Eszerint a teszteket a programok előtt kell megírni, ezzel mintegy specifikálva a feladatot. j u n i t . framework Test s u i t e = s u i t e B a s i c T e s t s ( ) ; T e s t S u i t e s 1 = new T e s t S u i t e ( ) ; s 1 . addTest ( s u i t e ) ; A munka

hatékonyságnövelése Záró gondolatok Minőségbiztosítás A tesztrendszer rögtön környezetet teremt a tesztelendő kód számára, így egyből a funkcionalitásra koncentrálhatunk. A JUnit tesztek automatikusak, azaz nem kell kézzel összehasonlítani a kapott és a várt eredményeket. A tesztek futtatása után azonnal megkapjuk a választ, hogy a programunk jó-e vagy sem. A JUnit tesztek csoportosíthatósága miatt könnyű egybefogni egy nagyobb programrendszer összes tesztjét, és azokat egyszerre futtatni. Ugyanakkor a teszthierarchia egyes részei egyenként is futtathatók Sokszor a teszteket ugyanolyan csomaghierarchiában tárolják mint a tesztelendő osztályokat. Ez abból a szempontból hasznos, hogy így az osztályok csomagszintű láthatósággal rendelkező tagjai is közvetlenül tesztelhetők. Egy alapos tesztrendszer mindig pontos képet ad arról, milyen állapotban van a programunk, emiatt segít annak a tervezésében is, hogy mikor adhatunk

ki új verziót. Bátrabban állhatunk neki nagyobb kódátszervezési feladatoknak is (refaktorálás), hiszen ha a végén a teszt lefut, akkor biztosak lehetünk benne, hogy nem rontottunk el semmit. Ugyanakkor a tesztek mintegy minőségi tanúsítványt is jelentenek A tesztek írása hibakereséssel és javítással töltött időt takarít meg. Amikor egy hibajelentés érkezik a programról, érdemes az új hibát is bevenni a tesztrendszerbe, így biztosíthatjuk, hogy ugyanaz a bug remélhetőleg nem kerül ismét vissza a prog- Speciális tesztelési feladatok ramba. Sajnos sok hasznos tulajdonsága ellenére a JUnit rendszer nem minden tesztelési feladatra Teszt vezérelt programozás alkalmas. A JUnit honlapján számos kiterA tesztek írását érdemes párhuzamosan végezni jesztése is elérhető Ilyen például a GUI kéa programok írásával A tesztjeinket mindig szítésekhez használható Abbot (http://abbot tartsuk 100%-ig szinkronban a tesztelendő kód-

sourceforge.net/doc/overviewshtml) A dal, ugyanis ha a tesztek nem az aktuálisak, ak- JUnit további kiterjesztései lehetőséget adnak kor nem sokat érnek. Érdemes rendszeres időkö- párhuzamos programok, program hatékonyság zönként lefuttatni a teszteket. Az Extrém Prog- (sebesség), JEE programok tesztelésére is 35 Tesztelés 3. Grinder - Java Enterprise programok tesztelése Grinder - Java Enterprise programok tesztelése Az első írásunk a Grindert átfogóan bemutatta, ezért itt azt a célt tűzzük ki, hogy az eszköz általános használati módját is bemutassuk. Mindezt gyakorlati példákon keresztül, elsősorban a Java Enterprise Edition (JEE) lehetőségeire fókuszálva. Véleményünk szerint ez egy izgalmas lehetőség, hogy a teszteljük adatbázis, message queue és más JEE komponensek terhelhetőségét és funkcionális működését. A Python tesztprogramok értéket adjon vissza. A függvény által visszaadott értékek egymásnak

paraméterként is átadhatók Csak egyetlen érték vagy objektum visszaadása engedélyezett, de tuple vagy lista adattípust használva több értéket is vissza tudunk adni. Az eljárás egy olyan függvény, amely a speciális NONE értéket adja vissza. Példa egy függvényre, aminek page a neve: A Grinder tesztprogramok python nyelven készülnek és azt annak egy Java implementációja, a jython futtatja. Ezzel olyan python programokat készíthetünk, ahol a teljes Java apparátust is megmozgathatjuk, így a JEE megoldások tesztelésének ez egy remek eszköze. A python nyelvet nem kell teljes mélységben érteni, de d e f page ( ) : r e q u e s t .GET( ’ / c o n s o l e ’ ) azért bizonyos nyelvi ismeretek elengedhetetler e q u e s t .GET( ’ / c o n s o l e / l o g i n / LoginForm j s p ’ ) nek egy-egy tesztprogram megírásához. Termér e q u e s t GET( ’ / c o n s o l e / l o g i n / bea logo g i f ’ ) szetesen itt nincs hely a nyelv bemutatására, de Ez

pedig egy másik példa (a lenti jmssena példákat azért igyekszünk úgy ismertetni, hogy der.py példából kiemelve), ahol 2 db paraméter azokat az is megértse, aki ezen a nyelven nem is van, illetve a message egy visszaadott érték: szerzett még tapasztalatokat, de a Java, C++ def createBytesMessage ( session , s i z e ) : vagy C# nyelvek valamelyikét ismeri. A tesztbytes = zeros ( size , ’b ’ ) random . nextBytes ( bytes ) programokban vannak közös megoldások, amit message = s e s s i o n . c r e a t e B y t e s M e s s a g e ( ) itt előre összefoglalunk, hogy a konkrét példákmessage . w r i t e B y t e s ( b y t e s ) r e t u r n message nál már ne kelljen azokat elmagyarázni. A következőkben ezeket ismertetjük röviden, itt sok A függvény hívása így néz ki, de a ’;’ karakter helyen felhasználtuk az ELTE következő okta- nem kötelező. tási anyagát: http://nyelvek.infeltehu/ message = c r e a t e B y t e s M e s s a g e ( s e s s i o n , 1

0 2 4 ) ; leirasok/Python/index.php A függvényekre alias neveket is megadhatunk, például: Függvények csinaldUzenet = createBytesMessage ; message = c s i n a l d U z e n e t ( s e s s i o n , 1 0 2 4 ) ; A függvény definiálásának szintaxisa: d e f <fvnév> ( <p a r a m l i s t > ) : <m e g v a l ó s í t á s > A függvény törzse a következő sorban beljebb kezdve kell, hogy legyen. A törzs opcionálisan kezdődhet egy string literállal, ami a függvény dokumentáció stringje (docstring). A return utasítás teszi lehetővé, hogy a függvény 36 Lambda kifejezések A funkcionális programozás hívőinek megmelengetheti a szívét az egyszerű lambda-függvények kezelése. A lambda kulcsszó használatával kisméretű anonymous függvények definiálhatók, amire egy példa: f v = lambda a , b : a+b Tesztelés Grinder - Java Enterprise programok tesztelése Ez a két paraméter összegét adja vissza. A Lambda-formák bárhol

használhatóak, ahol függvényobjektumokra van szükség. Szintaktikusan egy kifejezésre korlátozottak, szemantikusan pedig csak "szintaktikus cukra" a normál függvénydefiníciónak. Például az fv(3, 4) hívás eredménye 7 lesz. hogy a Python csomagként kezelje a könyvtárakat. Ez lehet üres, vagy tartalmazhat inicializációs kódot, ami a csomag importálásánál fut le. Modulok és importok A Python lehetővé teszi, hogy nagyobb terjedelmű scripteket, programcsomagokat a belső struktúrájának megfelelően tagoljunk és ezt a programszövegben is jelezzük. Erre szolgálnak a modulok, amelyek logikailag összetartozó programrészeket fognak egybe. A modul neve az azt tartalmazó file neve. A modulok kiterjesztése py Egy modul neve a modulon belül a name modul-globális szimbólumon keresztül férhető hozzá. Modulokat más scriptekből az import vagy from module import * utasításokkal importálhatunk. Az első forma a teljes modult, a

második a modul bizonyos függvényeit importálja A ’*’ helyett vesszővel elválasztott felsorolás is állhat. A második formában a ’*’ használatánál az ’ ’-fal kezdődő függvények nem importálódnak. A modulok állományrendszerbeli keresése a PYTHONPATH környezeti változóban felsorolt könyvtárak sorrendjében történik Hasonlóan a C++-beli namespace-ekhez, a különböző nyelvi elemeknek pythonban is létezik struktúrája A struktúra különböző szintjeit ’’-tal választjuk el egymástól A névterek általában láthatósági tartományokat is jelölnek. A névtér tulajdonképpen név objektum leképezések halmaza, jelenleg így is vannak implementálva (szótárakkal) Az általános felhasználásra szánt programcsomagokat csomagokba szervezzük, melyek tetszőleges mélységű könyvtár-struktúrával rendelkezhetnek, amit a Java-hoz hasonlóan a file rendszer szerkezetével reprezentálunk. Az init py nevű speciális

állományokra minden csomagban szükség van, Osztályok A python osztályok szerepe megegyezik a más objektumorientált nyelveknél megszokottakkal. Lehetőség van többszörös öröklődésre, a származtatott osztály átdefiniálhatja az ősosztálya metódusait és egy metódus hívhatja az ősosztály metódusát ugyanazon a néven. Az objektumok tartalmazhatnak privát adatokat. A dupla ’ ’-sal kezdődő tagokat a parser a szintaktikai előfordulástól függetlenül ’ classname member ’-re cseréli ki, ahol classname az aktuális osztály neve. Hasonlóan a Javahoz, minden tagfüggvény virtuális Az osztályok maguk is objektumok, ugyanis a Pythonban minden adattípus objektum. Ugyanakkor minden objektumnak van osztálya Az osztályobjektumokon kétfajta műveletet végezhetünk Az egyik művelet az attribútumhivatkozások létrehozása, a másik pedig az osztály egy példányának létrehozása. A beépített típusokat nem bővítheti a felhasználó, azaz

nem örökölhet tőlük Példa egy osztály létrehozására és példányosítására: c l a s s Complex : d e f init ( s e l f , r e a l p a r t , i m a g p a r t ) : s e l f . r = realpart s e l f . i = imagpart x = Complex ( 3 . 0 , − 4 5 ) Amennyiben létezik egy init () konstruktor metódusa az osztálynak, akkor példányosításkor az objektum létrehozása után meghívódik, átadva a példányosításkor esetleg megadott paramétereket. A python osztályok rendelkeznek még néhány előre elkészített metódussal, de a Grinder példákhoz most csak a call (self, *argumentumok) megértése lényeges, ami meghívódik, amikor egy példányt függvényként hívunk meg. Tegyük fel, hogy a fenti Complex 37 Tesztelés Grinder - Java Enterprise programok tesztelése osztálynak van egy call (arguments) metódusa. A példában az x változóra ezt így hívhatnánk meg: x call (arguments), de ezt megtehetjük így is: x(arguments). Talán még a

következő 2 metódust is érdemes megértenünk: p r i n t tupleObj d i c t O b j = { " k1 " : 7 , " k2 " : 1 5 , " k3 " : " aaaaa " } print dictObj F u t á s i eredmény : [ 1 2 , ’ Alma ’ , 3 4 ] ( 1 2 , ’ Alma ’ , 3 4 ) { ’ k3 ’ : ’ aaaaa ’ , ’ k2 ’ : 1 5 , ’ k1 ’ : 7} • isinstance(ob1, ob2), ahol az első argumentum egy példányobjektum, a második pedig egy osztályobjektum vagy típusobjekAlias név egy objektumra tum. Azt vizsgálja, hogy az ob1 az ob2 osztály egy példánya-e, vagy, hogy az ob1 Az alias egy másik név egy osztályra vagy vatípusa megegyezik-e az ob2 típusával. lamilyen adatra. A legegyszerűbb formája az ugyanarra az objektumra való hivatkozás: • issubclass(osztály1, osztály2): megvizsv a r = ’ Valami ’ gálja, hogy az osztály1 az osztály2 v a r 2 = v a r alosztálya-e. Egy osztály a saját alosztáEz más nyelveken is így van, de a python lyának tekinthető. lehetővé

teszi, hogy egy class nevére is aliast-t tegyünk, hiszen ez is egy objektum: Listák, Sorok és Szótárak A python lista (list) és sor (tuple) ugyanolyan lista adatszerkezet, a különbség az közöttük, hogy a tuple egyszer felveszi az értékét és már nem változtatható meg, azaz például nem ragasztható hozzá új elem. Mindkettőnek van literálja, azaz konstans alakja A lista konstansot [.], míg a tuple-t () jelek között kell megadnunk Mindkét típus képes egy olyan másolatot adni magáról, ami a másik típusba tartozik. A tuple azért jelenik meg külön elemként a nyelvben, mert kihasználva annak konstans jellegét, hatékonyabban valósítható meg, így ezekben a szituációkban nem érdemes listát használni. A szótárak (dictionary vagy map) az ismert kulcs, érték párok halmazának megvalósítása, ahol a kulcs alapján asszociatív (direkt) elérés valósul meg. Ennek a konstansát {} jelek között adhatjuk meg Mindhárom típusszerkezet

eltérő típusú értékeket tartalmazhat Az eddigiekre egy rövid szemléltető példa a következő: # c o d i n g=u t f −8 l i s t a O b j = [ 1 2 , "Alma " , 34 ] print listaObj t u p l e O b j = ( 1 2 , "Alma " , 34 ) 38 c l a s s MyReallyBigClassNameWhichIHateToType : d e f init ( s e l f ) : <blah> [.] És e r r e az a l i a s név : ShortName = MyReallyBigClassNameWhichIHateToType Megjegyezzük az első cikkben már megismert metódusra, a grinder.loggerinfo() függvényre így tettünk alias nevet: log = grinder . logger info A TestRunner class Amikor egy Grinder worker process elindul, akkor az éppen megadott teszt script lefut. Minden ilyen script kötelezően tartalmaz egy TestRunner osztályt, amiből a Grinder motor létrehoz az összes worker futási szál számára egy-egy példányt, ezek természetesen a szálra jellemző specifikus információkat is tárolhatnak. Ezek a példányváltozók callable típusúak, mert az

osztálynak kötelezően tartalmaznia kell a már megismert call (.) metódust A teszt scriptek a szolgáltatásokat a grinder objektumon keresztül érhetik el Tesztelés Grinder - Java Enterprise programok tesztelése A Test class teszt esetét, aminek az azonosítója 1, rövid neve pedig First page lett. Itt a page1Test változó már ennek a teszt esetnek a becsomagolt változata, ahol a csomag a page1() metódust hajtja végre, mert konkrét tesztet. Korábban már tanultuk, hogy a Grinder úgy hívja meg a tesztet, hogy a TestRunner -t futtatja, ennek pedig most csak ez az egyetlen page1Test() hívás a feladata. Az említett runs=0 miatt ezt most annyiszor hívja, amennyiszer tudja. A futás naplójának egy részletét mutatja a 3-1. Naplórészlet Látható a végén, hogy 34786 ms alatt 7320 TestRunner futás sikerült A fontosabb statisztikai értékek a következők, amiket a napló legvégéből lehet kiolvasni: A net.grinderscriptTest class reprezentál egy

konkrét tesztesetet, ami a Grinder statisztikában is megjelenik a tesztfutás során. Van egy egyedi azonosítója és rövid szöveges leírása, amely 2 paramétert mindig a konstruktornak kell átadni. Egy Test instance használatánál a wrap(java.langObject target) metódus a legfontosabb, ugyanis ez egy proxy objektumot hoz létre, aminek használati felülete a paraméterként átadott objektuméval egyezik meg. A hívást a Grinder fogja delegálni ezen a proxy-n keresztül a célobjektum felé. Ez a metódus sokszor meghívható. A wrap() metódus egy python algoritmus becsomagolása tesztelési célból • Mean Test: 3,96 ms Egy HTTP tesztprogram Az első JEE tesztprogram az Oracle Weblogic 11g webes konzol login képernyőjének elérését méri. A Weblogic a localhost:7001 porton hallgatózik A grinderproperties file-ban a runs értéke 0, azaz addig hajtódik majd végre a 3-1 programlista, ameddig a konzolról le nem állítjuk azt. A 14 sorban létrehozott request

objektum http kéréseket tud adni az előbb megnevezett portra A 16-19 sorok között definiáljuk a page1() metódust, ami 3 http request-et ad ki. A 21. sorban hozzuk létre a tesztprogram egyetlen 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 • Standard Deviation: 6,26 ms • TPS: 210,43 • Mean response length: 4934,00 byte • Response bytes per second: 1038259,07 A 3.1 és 32 ábrák a Grinder elemző által generált grafikonok, ahol fel lett tüntetve a teszt First page neve. A grafikonok az idő függvényében mutatják a fenti számok részleteinek alakulását // 3 −1. p r o g r a m l i s t a : w e b l o g i c −h t t p py # Recording many HTTP i n t e r a c t i o n s as one t e s t # # This example shows how many HTTP i n t e r a c t i o n s can be grouped as a # s i n g l e t e s t by wrapping them i n a f u n c t i o n . from from from from n e t . g r i n d e r s c r i p t Grinder import g r i n d e r n e t . g r i n d e r s c r i p t import Test n e t . g r i n d e

r p l u g i n h t t p import HTTPRequest HTTPClient import NVPair # We d e c l a r e a d e f a u l t URL f o r t h e HTTPRequest . r e q u e s t = HTTPRequest ( u r l = " h t t p : / / l o c a l h o s t : 7 0 0 1 " ) 39 Tesztelés 16 17 18 19 20 21 22 23 24 25 Grinder - Java Enterprise programok tesztelése def page1 ( ) : r e q u e s t .GET( ’ / c o n s o l e ’ ) r e q u e s t .GET( ’ / c o n s o l e / l o g i n / LoginForm j s p ’ ) r e q u e s t .GET( ’ / c o n s o l e / l o g i n / bea logo g i f ’ ) page1Test = Test ( 1 , " F i r s t ␣ page " ) . wrap ( page1 ) c l a s s TestRunner : def call ( s e l f ) : page1Test ( ) // 3 −1. N a p l ó r é s z l e t 2012−04−09 1 4 : 1 0 : 3 5 , 0 8 7 INFO csdev1 −0 t h r e a d −0 [ run −7317 , t e s t −1 ] : h t t p : / / l o c a l h o s t : 7 0 0 1 / å c o n s o l e / l o g i n / LoginForm . j s p −> 200 OK, 3161 b y t e s 2012−04−09 1 4 : 1 0 : 3 5 , 0 8 8 INFO csdev1

−0 t h r e a d −0 [ run −7317 , t e s t −1 ] : h t t p : / / l o c a l h o s t : 7 0 0 1 / å c o n s o l e / l o g i n / bea logo . g i f −> 401 Unauthorized , 1518 b y t e s 2012−04−09 1 4 : 1 0 : 3 5 , 0 8 9 INFO csdev1 −0 t h r e a d −0 [ run −7318 , t e s t −1 ] : h t t p : / / l o c a l h o s t : 7 0 0 1 / å c o n s o l e −> 302 Moved Temporarily , 255 b y t e s [ R e d i r e c t , e n s u r e t h e n e x t URL i s h t t p : / / å l o c a l h o s t :7001/ console /] 2012−04−09 1 4 : 1 0 : 3 5 , 0 9 0 INFO csdev1 −0 t h r e a d −0 [ run −7318 , t e s t −1 ] : h t t p : / / l o c a l h o s t : 7 0 0 1 / å c o n s o l e / l o g i n / LoginForm . j s p −> 200 OK, 3161 b y t e s 2012−04−09 1 4 : 1 0 : 3 5 , 0 9 1 INFO csdev1 −0 t h r e a d −0 [ run −7318 , t e s t −1 ] : h t t p : / / l o c a l h o s t : 7 0 0 1 / å c o n s o l e / l o g i n / bea logo . g i f −> 401 Unauthorized , 1518 b y t e s 2012−04−09 1 4 :

1 0 : 3 5 , 0 9 2 INFO csdev1 −0 t h r e a d −0 [ run −7319 , t e s t −1 ] : h t t p : / / l o c a l h o s t : 7 0 0 1 / å c o n s o l e −> 302 Moved Temporarily , 255 b y t e s [ R e d i r e c t , e n s u r e t h e n e x t URL i s h t t p : / / å l o c a l h o s t :7001/ console /] 2012−04−09 1 4 : 1 0 : 3 5 , 0 9 8 INFO csdev1 −0 : r e c e i v e d a s t o p message 2012−04−09 1 4 : 1 0 : 3 5 , 0 9 8 INFO csdev1 −0 t h r e a d −0 [ run −7319 , t e s t −1 ] : h t t p : / / l o c a l h o s t : 7 0 0 1 / å c o n s o l e / l o g i n / LoginForm . j s p −> 200 OK, 3161 b y t e s 2012−04−09 1 4 : 1 0 : 3 5 , 0 9 9 INFO csdev1 −0 t h r e a d −0 [ run −7319 , t e s t −1 ] : h t t p : / / l o c a l h o s t : 7 0 0 1 / å c o n s o l e / l o g i n / bea logo . g i f −> 401 Unauthorized , 1518 b y t e s 2012−04−09 1 4 : 1 0 : 3 5 , 1 0 1 INFO csdev1 −0 t h r e a d −0 [ run −7320 ] : s h u t down . f i n i s h e d 7320 r u n s e l

a p s e d time i s 34786 ms 3.1 ábra Hálózati sávszélesség 40 Tesztelés Grinder - Java Enterprise programok tesztelése 3.2 ábra TPS és átlagos válaszidő Véletlenszám alapú tesztprogram A 3-2. programlista bemutatja a tesztprogramok általános szerkezetét, ugyanis a doIt() helyére bármit írhatunk, mint tesztelő kód Elöljáróban a 18 sorban lévő tests változó, mint lista létrehozásának működését értsük meg, ami szintén egy python lehetőség. A beépített range() függvény egy listát ad vissza, aminek az első elemét és a végét lehet megadni, ami azonban már nem eleme a gyűjteménynek. A példa jól szemlélteti, hogy ilyen módszerrel miképpen jön létre a 0–9 számok listája: l i s t a = [ i f o r i in range (0 , 10) ] print l i s t a Futás : [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9] A 18. sorban ugyanez a nyelvi konstrukció használatos, de itt a kialakított lista nem egyszerűen a ciklusváltozó értékeiből, hanem Test

objektumokból fog állni, azaz így 1 sorban 10 darabot is létre tudtunk hozni, amit a tests változón keresztül érünk el. Ugyanezen sorban azt is látjuk, hogy a tesztbe a szokásos módon – wrap() metódussal – be is csomagoltuk a tesztelés algoritmusát, ami a doIt(). Mit is csinál ő? Nézzük a 13-16 sorok közötti implementációját, de előtte vegyük észre, hogy egy véletlenszám generátort inicializálunk a 11. sorban, majd annak a következő értékeit mindig az r változón keresztül érhetjük majd el. Ezzel az eszközzel a [0.0, 10) balról zárt, jobbról nyílt tartományból lebegőpontos véletlenszámok kérhetők le A használt nextGaussian() metódus ezt transzformálja a [-1.0, 10) intervallumra Az alábbiakban látható erre egy egyszerű tesztprogram, ami modellezi a doIt() működését is! A visszaadott számok az 500-as érték körül változnak, amire az is hatással van, hogy a list változónak éppen melyik elemére hívtuk a doIt()

metódust, azaz a 41 Tesztelés Grinder - Java Enterprise programok tesztelése v paraméter értéke mekkora. from j a v a . u t i l import Random from j a v a . l a n g import Math r = Random ( ) def doIt (v ) : t = 500 + r . n e x t G a u s s i a n ( ) ∗ v ∗ 10 print int ( t ) , t l i s t = [ i f o r i in range (0 , 9) ] for e in l i s t : doIt ( e ) Futás : 500 5 0 0 . 0 503 5 0 3 . 3 1 5 3 3 3 0 2 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 509 457 490 543 544 598 423 509.084804867 457.299418618 490.223846563 543.25559885 544.201668026 598.924652992 423.717224324 Visszatérve a console.py tesztprogram doIt() metódusához, annak működését már egyszerűen megérthetjük. Generál egy t véletlenszámot, majd egyszerűen elaltatja ennyivel a tesztfutást, ezzel szimulálva a teszt végrehajtási idejét. A pass a már ismert üres utasítás. // 3 −2. p r o g r a m l i s t a : c o n s o l e py # Test s c r i p t which g e n e r a

t e s some random d a t a f o r t e s t i n g t h e # console . from from from from n e t . g r i n d e r s c r i p t Grinder import g r i n d e r n e t . g r i n d e r s c r i p t import Test j a v a . u t i l import Random j a v a . l a n g import Math r = Random ( ) def d o I t ( v ) : t = 500 + r . n e x t G a u s s i a n ( ) ∗ v ∗ 10 g r i n d e r . s l e e p ( i n t ( t ) , 0) pass t e s t s = [ Test ( i , " Test ␣%s " % i ) . wrap ( d o I t ) f o r i in r a n g e ( 0 , 1 0 ) ] c l a s s TestRunner : def call ( s e l f ) : # s t a t i s t i c s = grinder . s t a t i s t i c s # s t a t i s t i c s . delayReports = 1 f o r t e s t in t e s t s : t e s t ( t e s t . test number ) Végezetül nézzük meg a mindegyik Grinder teszt script részére szükséges tesztfuttató TestRunner osztály felépítését, amit a 20-27 sorok között tanulmányozhatunk. Csak emlékeztetőül jegyezzük meg, hogy a Grinder motor mindig ezt az osztályt

példányosítja és úgy hívja meg, 42 ahogy a pythonban egy objektumot függvényként hívunk. A korábbiakban elmagyaráztuk, hogy ehhez a call() metódus implementációja a követelmény, hiszen ennek a hívása jelenti az objektum futtatását, amiatt a 21-27 sorok között implementáltuk. A jobb megértés ér- Tesztelés Grinder - Java Enterprise programok tesztelése dekében a következő kis program ugyanúgy működik, ahogy a Grinder engine. Ugyanaz a kimenete, mint az előző kis tanuló programnak, de itt láthatjuk, hogy egy MyTestRunner osztályt, illetve annak call () metódusát miképpen írtuk meg, illetve a myEngine változót milyen módon hívtuk függvényként, átadva neki a 10 értéket. A 3-2 programlista 27 sorában a test egy Test osztálybeli objektum, hiszen a tests listának ilyen elemei vannak. Ráismerhetünk, hogy a test objektumot itt függvényként hívjuk. A paraméterezése a doIt() függvényével egyezik, mert azt csomagoltuk

be korábban, ahol az átadott paraméter a teszt azonosító száma. for e in l i s t : doIt ( e , c ) myEngine=MyTestRunner ( ) myEngine ( 10 ) A 3-2. Naplórészlet a consolepy tesztfutás naplójának részleteit mutatja. A napló elején lévő tesztfutások bejegyzéseit nagyrészt kihagytuk, mert azok a 3-25 sorok között megfigyelhető típusúak és azok ismétlődnek több száz soron keresztül. A 26 sorban láthatjuk, hogy a Grinder konzolról mikor küldtük ki a tesztelés befejezésének kérelmét (ez az érték = 16:37:06,565). A 3. sor alapján a tesztelést 16:36:29,343 időpillanatban kezdtük, de ha kíváncsiak vagyunk a tényleges nettó tesztidőre, akkor azt a 29. sorból olvashatjuk ki: elapsed time is 37244 ms A 28. sor szerint ezen idő alatt 7 tesztet tudtunk elvégezni, melynek a teljes statisztikáját a 3248 sorok mutatják. Mindezek grafikus elemzéséből két jellemző grafikont is betettünk, láthatjuk őket a 3.3 és 34 ábrákról Az egyik csak

a Test 9 esetet, a másik az összes teszt együttes vizsgálatát jeleníti meg. from j a v a . u t i l import Random from j a v a . l a n g import Math r = Random ( ) def doIt (v , c ) : t = 500 + r . n e x t G a u s s i a n ( ) ∗ c ∗ 10 print int ( t ) , t l i s t = [ i f o r i in range (0 , 9) ] c l a s s MyTestRunner : d e f call ( s e l f , c ) : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // 3 −2. N a p l ó r é s z l e t . 2012−04−28 1 6 : 3 6 : 2 9 , 3 4 3 2012−04−28 1 6 : 3 6 : 2 9 , 8 4 4 2012−04−28 1 6 : 3 6 : 3 0 , 3 4 3 2012−04−28 1 6 : 3 6 : 3 0 , 8 2 3 2012−04−28 1 6 : 3 6 : 3 1 , 3 3 7 2012−04−28 1 6 : 3 6 : 3 1 , 8 4 5 2012−04−28 1 6 : 3 6 : 3 2 , 4 4 3 2012−04−28 1 6 : 3 6 : 3 2 , 9 9 7 2012−04−28 1 6 : 3 6 : 3 3 , 5 1 1 2012−04−28 1 6 : 3 6 : 3 4 , 0 1 0 2012−04−28 1 6 : 3 6 : 3 4 , 5 5 6 2012−04−28 1 6 : 3 6 : 3 5 , 0 5 7 2012−04−28 1 6 : 3 6 : 3 5 , 5 5 1

2012−04−28 1 6 : 3 6 : 3 6 , 0 5 0 2012−04−28 1 6 : 3 6 : 3 6 , 5 7 7 2012−04−28 1 6 : 3 6 : 3 7 , 0 6 5 2012−04−28 1 6 : 3 6 : 3 7 , 5 4 5 2012−04−28 1 6 : 3 6 : 3 8 , 0 2 6 2012−04−28 1 6 : 3 6 : 3 8 , 5 1 0 2012−04−28 1 6 : 3 6 : 3 8 , 9 8 5 . 2012−04−28 1 6 : 3 7 : 0 5 , 6 0 9 2012−04−28 1 6 : 3 7 : 0 6 , 1 0 9 2012−04−28 1 6 : 3 7 : 0 6 , 5 6 5 2012−04−28 1 6 : 3 7 : 0 6 , 5 6 8 2012−04−28 1 6 : 3 7 : 0 6 , 5 6 8 2012−04−28 1 6 : 3 7 : 0 6 , 5 7 2 INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d

−0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 t h r e a d −0 [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ run −0 , run −0 , run −0 , run −0 , run −0 , run −0 , run −0 , run −0 , run −0 , run −0 , run −1 , run −1 , run −1 , run −1 , run −1 , run −1 , run −1 , run −1 , run −1 , run −1 , t e s t −0 t e s t −1 t e s t −2 t e s t −3 t e s t −4 t e s t −5 t e s t −6 t e s t −7 t e s t −8 t e s t −9 t e s t −0 t e s t −1 t e s t −2 t e s t −3 t e s t −4 t e s t −5 t e s t −6 t e s t −7 t e s t −8 t e s t −9 ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: ]: sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping sleeping for for for for for for for for for for

for for for for for for for for for for 500 497 479 513 507 597 553 513 498 544 500 493 498 526 484 479 480 483 474 507 ms ms ms ms ms ms ms ms ms ms ms ms ms ms ms ms ms ms ms ms INFO INFO INFO INFO INFO INFO csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 csdev1 −0 t h r e a d −0 [ run −7 , t e s t −1 ] : s l e e p i n g f o r 499 ms t h r e a d −0 [ run −7 , t e s t −2 ] : s l e e p i n g f o r 528 ms : r e c e i v e d a s t o p message t h r e a d −0 [ run−7 ] : s h u t down t h r e a d −0: f i n i s h e d 7 r u n s : e l a p s e d time i s 37244 ms 43 Tesztelés 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 Grinder - Java Enterprise programok tesztelése 2012−04−28 1 6 : 3 7 : 0 6 , 5 7 2 INFO csdev1 −0 : F i n a l s t a t i s t i c s f o r t h i s p r o c e s s : 2012−04−28 1 6 : 3 7 : 0 6 , 5 8 6 INFO csdev1 −0 : Tests Errors Mean Test Test Time TPS Time ( ms ) Standard Deviation ( ms ) Test Test Test Test Test

Test Test Test Test Test 0 1 2 3 4 5 6 7 8 9 Totals 8 8 7 7 7 7 7 7 7 7 0 0 1 0 0 0 0 0 0 0 500 ,62 499 ,62 487 ,86 520 ,14 509 ,71 517 ,43 526 ,57 495 ,00 492 ,00 558 ,29 0 ,70 6 ,84 20 ,95 34 ,90 23 ,19 59 ,07 39 ,21 34 ,26 24 ,51 70 ,03 0 ,21 0 ,21 0 ,19 0 ,19 0 ,19 0 ,19 0 ,19 0 ,19 0 ,19 0 ,19 72 1 510 ,43 41 ,81 1 ,93 3.3 ábra A Test 9 teljesítmény grafikonja 3.4 ábra Az összes teszt együttes teljesítmény grafikonja 44 " Test " Test " Test " Test " Test " Test " Test " Test " Test " Test 0" 1" 2" 3" 4" 5" 6" 7" 8" 9" Tesztelés Grinder - Java Enterprise programok tesztelése HTTP Cookie tesztprogram építhetünk saját kezelőt. Most csak 2 metódust írunk felül, azaz megmondjuk a acceptCookie() és sendCookie() speciális működését. A kódok meglehetősen egyszerűek, kizárólag csak naplózzák ezeket az eseményeket (20. és 24 sorok) A 27.

sor beállítja az eseménykezelést a most megalkotott osztályunk egy objektumára, ugyanis a paraméterként átadott MyCookiePolicyHandler() egy konstruktor hívás, így eredményeképpen egy névtelen objektum jön létre. A test1 nevű Test objektumot a 29-30 sorok között definiáltuk, most csak 1 van belőle és a tesztelő algoritmusa a HTTPRequest() objektum függvényként való hívása lesz. Ezzel minden előkészítettünk, hogy a TestRunner call () metódusát elkészíthessük (34-64 sorok) A további példáinkban általában már nem fogjuk bemutatni a tesztfutásokat, jellemzően csak igyekszünk elmagyarázni, hogy a bemutatott teszt script mit csinál, így az olvasó további ötleteket meríthet saját scriptjeinek elkészítéséhez. Ebből a példából a HTTP cookie tesztelési lehetőségét ismerhetjük meg A HTTPClient library-t fogjuk használni, ami támogatja a sütikkel való manipulációkat, amihez a CookiePolicyHandler class alkalmazása is

fontos. Ez utóbbi osztályt definiálja majd a 3-3. programlista cookiespy scriptje is A példa a cookie tárolását, hozzáadását és törlését is bemutatja A 1825 sorok közötti MyCookiePolicyHandler class őse a CookiePolicyHandler osztály, így könnyen 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // 3 −3. p r o g r a m l i s t a : c o o k i e s py # HTTP c o o k i e s # # I f you r e a l l y want d i r e c t c o n t r o l o v e r t h e c o o k i e h e a d e r s , you # can d i s a b l e t h e a u t o m a t i c c o o k i e h a n d l i n g w i t h : # HTTPPluginControl . g e t C o n n e c t i o n D e f a u l t s ( ) u s e C o o k i e s = 0 from from from from from n e t . g r i n d e r s c r i p t Grinder import g r i n d e r n e t . g r i n d e r s c r i p t import Test n e t . g r i n d e r p l u g i n h t t p import HTTPRequest , HTTPPluginControl HTTPClient import Cookie , CookieModule , C o o k i e P o l i c y H a n d l

e r j a v a . u t i l import Date log = grinder . logger info # S e t up a c o o k i e h a n d l e r t o l o g a l l c o o k i e s t h a t a r e s e n t and r e c e i v e d . c l a s s MyCookiePolicyHandler ( C o o k i e P o l i c y H a n d l e r ) : def a c c e p t C o o k i e ( s e l f , c o o k i e , r e q u e s t , r e s p o n s e ) : l o g ( " a c c e p t ␣ c o o k i e : ␣%s " % c o o k i e ) return 1 def sendCookie ( s e l f , c o o k i e , r e q u e s t ) : l o g ( " send ␣ c o o k i e : ␣%s " % c o o k i e ) return 1 CookieModule . s e t C o o k i e P o l i c y H a n d l e r ( MyCookiePolicyHandler ( ) ) t e s t 1 = Test ( 1 , " Request ␣ r e s o u r c e " ) r e q u e s t 1 = t e s t 1 . wrap ( HTTPRequest ( ) ) 45 Tesztelés 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 Grinder - Java Enterprise programok tesztelése c l a s s TestRunner : def call ( s e l f ) : # The

cache o f c o o k i e s f o r each # t h e s t a r t o f each run . worker t h r e a d w i l l be r e s e t a t r e s u l t = r e q u e s t 1 .GET( " h t t p : / / l o c a l h o s t : 7 0 0 1 / c o n s o l e /? r e q u e s t 1 " ) # I f t h e f i r s t r e s p o n s e s e t any c o o k i e s f o r t h e domain , # t h e y w i l l l be s e n t b a c k w i t h t h i s r e q u e s t . r e s u l t 2 = r e q u e s t 1 .GET( " h t t p : / / l o c a l h o s t : 7 0 0 1 / c o n s o l e /? r e q u e s t 2 " ) # Now l e t ’ s add a new c o o k i e . t h r e a d C o n t e x t = HTTPPluginControl . getThreadHTTPClientContext ( ) e x p i r y D a t e = Date ( ) e x p i r y D a t e . y e a r += 10 c o o k i e = Cookie ( " key " , " v a l u e " , " l o c a l h o s t " , " / " , expiryDate , 0 ) CookieModule . addCookie ( c o o k i e , t h r e a d C o n t e x t ) r e s u l t = r e q u e s t 1 .GET( " h t t p : / / l o c a l h o s t :

7 0 0 1 / c o n s o l e /? r e q u e s t 3 " ) # Get a l l c o o k i e s f o r t h e c u r r e n t t h r e a d and w r i t e them t o t h e l o g c o o k i e s = CookieModule . l i s t A l l C o o k i e s ( t h r e a d C o n t e x t ) f o r c in c o o k i e s : l o g ( " r e t r i e v e d ␣ c o o k i e : ␣%s " % c ) # Remove any c o o k i e t h a t i s n ’ t o u r s . f o r c in c o o k i e s : i f c != c o o k i e : CookieModule . removeCookie ( c , t h r e a d C o n t e x t ) r e s u l t = r e q u e s t 1 .GET( " h t t p : / / l o c a l h o s t : 7 0 0 1 / c o n s o l e /? r e q u e s t 4 " ) A 38. és 42 sorok HTTP kéréseinek hatását olvassuk el a script-ben hozzájuk fűzött megjegyzésekből! A 45-52 sorok egy új cookie létrehozását mutatják, ami amiatt az 54 sor HTTP kérésénél már el fog menni a kérésben. Az 57-58 sorokban naplózzuk az összes ide tartozó sütit, majd a 61-62 sorokban mindegyiket kitöröljük. Ezzel azt érjük

el, hogy a 64. sorban lévő request már egyetlen sütit sem fog küldeni a kéréssel. nem tudjuk áttekinteni. A 3-4 programlista a form alapú hitelesítési lépésekre tartalmaz egy tesztprogramot, így aki ezt nem ismeri mindenképpen nézze át a példa elolvasása előtt. Amikor egy nem hitelesített user próbál elérni egy védett URL-en lévő erőforrást, akkor a jogosultságkezelés alapja a felhasználó azonosságának az ismerete. Amennyiben a hitelesítés form alapú, úgy a webszerver egy login lapra irányítja át a kérést, de megjegyzi az eredeti erőforrásra hivatkozó URL-t is. Ez a lap fogja elküldeni (HTTP Form alapú hitelesítés tesztprogram POST) a username és password értékeket egy A HTTP alapú hitelesítéseket (basic, form, cer- speciális j security check action részére. A 8 tificate) ismertnek tételezzük fel, azokat itt most és 9. sorok egy-egy teszt objektumot határoznak 46 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

22 23 24 25 26 27 28 29 30 31 32 33 34 Tesztelés Grinder - Java Enterprise programok tesztelése meg az erőforrás lekérésére, illetve a hitelesítési információk (credential) elküldésére. A 11-20 sorok között kifejtett TestRunner 13. sorában a request hivatkozik a becsomagolt HTTPRequest(.) függvényhívásra, ami az első teszt A másik teszt wrap-pelése a 31. sorban történik majd, a maybeAuthenticate() metódus belsejében. Emiatt nézzük meg előbb ezt a függvénytagot, amely a 22-34 sorok közé került A paraméterként kapott lastResult természetesen a megelőző request eredménye lesz, így amennyiben a statusCode értéke ezt indokolja végrehajtja a hitelesítést (username=weblogic, pass- word=weblogic), majd a 34. sorban az eredeti URL-re posztólja vissza a kérést. Ennyi előkészítés után a TestRunner megértése már nagyon egyszerű lesz. A 13 sor becsomagolásáról már említést tettünk, a 16. sor viszont el is végzi a konkrét HTTP

kérést. A 18 sorban elegánsan használjuk a megírt segédmetódusunkat, majd ezután a 20. sor hívása már sikeres lesz, ugyanis akkor már a HTTP session tartalmazni fogja a szükséges security tokent. A form alapú hitelesítésről javasoljuk elolvasni a következő összefoglalót: http://sling.apacheorg/site/ form-based-authenticationhandler.html // 3 −4. p r o g r a m l i s t a : f b a py ( Form Bases h i t e l e s í t é s ) from from from from n e t . g r i n d e r s c r i p t Grinder import g r i n d e r n e t . g r i n d e r s c r i p t import Test n e t . g r i n d e r p l u g i n h t t p import HTTPRequest HTTPClient import NVPair p r o t e c t e d R e s o u r c e T e s t = Test ( 1 , " Request ␣ r e s o u r c e " ) a u t h e n t i c a t i o n T e s t = Test ( 2 , "POST␣ t o ␣ j s e c u r i t y c h e c k " ) c l a s s TestRunner : def call ( s e l f ) : r e q u e s t = p r o t e c t e d R e s o u r c e T e s t . wrap (

HTTPRequest ( u r l=" h t t p : / / l o c a l h o s t : 7 0 0 1 / c o n s o l e " ) ) r e s u l t = r e q u e s t .GET( ) r e s u l t = maybeAuthenticate ( r e s u l t ) r e s u l t = r e q u e s t .GET( ) def maybeAuthenticate ( l a s t R e s u l t ) : i f l a s t R e s u l t . s t a t u s C o d e == 401 or l a s t R e s u l t . t e x t f i n d ( " j s e c u r i t y c h e c k " ) != −1: g r i n d e r . l o g g e r i n f o ( " Challenged , ␣ a u t h e n t i c a t i n g " ) a u t h e n t i c a t i o n F o r m D a t a = ( NVPair ( " j username " , " w e b l o g i c " ) , NVPair ( " j password " , " w e b l o g i c " ) , ) r e q u e s t = a u t h e n t i c a t i o n T e s t . wrap ( HTTPRequest ( u r l="%s / j s e c u r i t y c h e c k " % l a s t R e s u l t . o r i g i n a l U R I ) ) return r e q u e s t .POST( a u t h e n t i c a t i o n F o r m D a t a ) 47 Tesztelés Grinder - Java

Enterprise programok tesztelése Adatbázis alapú tesztprogram lehet, mert mindkettőnek van close() metódusa. A 23. sor az adatbázishoz való kapcsolódás, míg a 24. egy SQL statement objektum létrehozását jelenti. Ez utóbbi teszi lehetővé az SQL parancsok kiadását, ahogy azt a 26 sorban látjuk is. Ezután a 29 sorban létrehozunk egy grinder fun nevű táblát, látható, hogy 2 oszlopa van (thread, run). A 31-32 sorokban lezárjuk a statement-et és a connection-t is. Mindezen lépések a tesztelés előkészítésére szolgálnak, azaz legyen egy üres grinder fun nevű táblánk. Most pedig nézzük meg, hogy maga a tesztelés mit jelent, azaz a TestRunner mit csinál! A kód nagyon egyszerű, a test1 insert-eket ad ki, amihez inputként a threadNumber és runNumber értékeket használja. Nincs ebben semmi ismeretlen A test2 ugyanezen a táblán select-et ad ki, ez is könnyen érthető. Egy tesztfutás végén pedig bezárjuk az erőforrásokat (52-53 sorok). Az

adatbázisok megfelelő teljesítményének kérdését nem lehet elég alkalommal kiemelni, most megtanuljuk ezt mérni is. Példánkban (3-5 programlista) egy Oracle adatbázist teszteltünk, amihez a thin driver-t használtuk (ennek a CLASSPATH-on kell lennie). A 8 és 9 sorok 2 Test típusú változója (test1 és test2 ) már sejteti, hogy az SQL INSERT és SELECT műveletekre végzünk teszteket. A tesztelésnél kihasználjuk, hogy jython a futtató környezet, így a Java JDBC apparátust tudjuk használni. Ennek megfelelően a 12 sorban regisztráljuk is a Java OracleDriver -t. A 14-16 sorok egy metódusba teszik az adatbázishoz kapcsolódás műveletét, míg a 18-20 sorok pedig a lekapcsolódást Ez utóbbiban az object paraméter egy Java Connection vagy Statement osztálybeli objektum is 1 // 3 −5. p r o g r a m l i s t a : j d b c py 2 3 from j a v a . s q l import DriverManager 4 from n e t . g r i n d e r s c r i p t Grinder import g r i n d e r 5 from n e t . g

r i n d e r s c r i p t import Test 6 from o r a c l e . j d b c import O r a c l e D r i v e r 7 8 t e s t 1 = Test ( 1 , " Database ␣ i n s e r t " ) 9 t e s t 2 = Test ( 2 , " Database ␣ query " ) 10 11 # Load t h e O r a c l e JDBC d r i v e r . 12 DriverManager . r e g i s t e r D r i v e r ( O r a c l e D r i v e r ( ) ) 13 14 def g e t C o n n e c t i o n ( ) : 15 return DriverManager . g e t C o n n e c t i o n ( 16 " j d b c : o r a c l e : t h i n : @127 . 0 0 1 : 1 5 2 1 : mysid " , " w l s " , " w l s " ) 17 18 def e n s u r e C l o s e d ( o b j e c t ) : 19 try : o b j e c t . c l o s e ( ) 20 except : pass 21 22 # One time i n i t i a l i s a t i o n t h a t c l e a n s o u t o l d d a t a . 23 c o n n e c t i o n = g e t C o n n e c t i o n ( ) 24 s t a t e m e n t = c o n n e c t i o n . c r e a t e S t a t e m e n t ( ) 25 26 try : s t a t e m e n t . e x e c u t e ( " drop ␣ t a b l e ␣ g r i n d e r

f u n " ) 27 except : pass 28 48 Tesztelés 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 Grinder - Java Enterprise programok tesztelése s t a t e m e n t . e x e c u t e ( " c r e a t e ␣ t a b l e ␣ g r i n d e r f u n ( t h r e a d ␣number , ␣ run ␣number ) " ) ensureClosed ( statement ) ensureClosed ( connection ) c l a s s TestRunner : def call ( s e l f ) : c o n n e c t i o n = None s t a t e m e n t = None try : connection = getConnection ( ) statement = connection . createStatement ( ) t e s t I n s e r t = t e s t 1 . wrap ( s t a t e m e n t ) t e s t I n s e r t . e x e c u t e ( " i n s e r t ␣ i n t o ␣ g r i n d e r f u n ␣ v a l u e s (%d , ␣%d ) " % ( g r i n d e r . threadNumber , g r i n d e r runNumber ) ) t e s t Q u e r y = t e s t 2 . wrap ( s t a t e m e n t ) t e s t Q u e r y . e x e c u t e ( " s e l e c t ␣∗ ␣ from ␣ g r i n d e r f u n ␣ where ␣ t h

r e a d=%d" % g r i n d e r . threadNumber ) finally : ensureClosed ( statement ) ensureClosed ( connection ) JMS Sender alapú tesztprogram A most bemutatandó tesztprogram (3-6. programlista) a Java Message Queue-ra (röviden: JMSQ) történő írást modellezi. Mindegyik worker szál létrehoz egy queue session-t, küld a queue-ra 10 darab üzenetet, majd bezárja a kapcsolatot. Mindehhez az Oracle Weblogic környezetet fogjuk használni, így eközben látni fogjuk azokat a technikai lépéseket is, amiket ebben a konténerben kell mindehhez elvégeznünk Ilyen lesz például a JNDI1 fához való hozzáférés. A tesztelésnél a CLASSPATH-on kell lennie a weblogicjar file-nak A 16 sor a hálózaton keresztül is működő JNDI kontextus objektumot (neve: initialContext) szerzi meg, amihez a 12-14 sorokban felépített properties objektumot használja A távoli szerveren lévő weblogicexamplesjmsexampleQueue nevű queue-ra szeretnénk majd írni, ezért a 18. sorban meg1

szerezzük a connectionFactory-t, majd ezután a távoli Queue objektumra egy referenciát kérünk le, amit a 19. sorban a queue változóban fogunk tárolni. Ezután már nincs szükségünk a JNDI elérésre, ezért azt lezárjuk. A 23 sorban a connectionFactory segítségével létrehozunk egy kapcsolatot a távoli JMS szerverhez és el is indítjuk ennek a használatát a start() metódussal. A későbbiekben véletlen számokat dobunk majd a JMSQ-ra, ezért a már ismert módszerrel létrehozunk egy Random típusú random változót. A 28-33 sorok közötti createBytesMessage() metódus generálja a JMSQ-ra dobott tényleges tartalmakat, felhasználva a véletlenszám generálást, majd a 31-32 sorokban a JMS API üzenetlétrehozási metódusát, amely üzenetet a végén visszaadja ez a metódus. Végeztünk a szokásos előkészítésekkel, jöhet ismét a TestRunner megírása, őt láthatjuk a 35-54 sorok között. Ennyi JNDI=Java Naming and Directory Interface 49

Tesztelés Grinder - Java Enterprise programok tesztelése munka után ezt elkészíteni már nem túl nehéz, nézzük csak! Aki ismeri a Java JMS API-t, annak minden sor ismerős, aki pedig nem az próbálja először azt áttekinteni. A 40 sorban a szokásos módon hozzuk létre a session változót, hogy abból és a korábban már elkészített queue változóból egy sender -t készíthessünk. Az instrumentedSender lesz az egyetlen Test változónk, ebbe csomagoljuk be a sender objektumot is. A 45 sorban legyártjuk az üzenetet (message), majd egy for ciklussal azt 10 alkalommal rádobjuk a queue-ra. Közben mindig tartunk egy rövid szünetet is. A teszt a session bezárásával fejeződik be 1 // 3 −6. p r o g r a m l i s t a : j m s s e n d e r py 2 3 from n e t . g r i n d e r s c r i p t Grinder import g r i n d e r 4 from n e t . g r i n d e r s c r i p t import Test 5 from j a r r a y import z e r o s 6 from j a v a . u t i l import P r o p e r t i e s , Random

7 from j a v a x . jms import S e s s i o n 8 from j a v a x . naming import Context , I n i t i a l C o n t e x t 9 from w e b l o g i c . j n d i import W L I n i t i a l C o n t e x t F a c t o r y 10 11 # Look up c o n n e c t i o n f a c t o r y and queue i n JNDI . 12 p r o p e r t i e s = P r o p e r t i e s ( ) 13 p r o p e r t i e s [ Context .PROVIDER URL] = " t 3 : / / l o c a l h o s t : 7 0 0 1 " 14 p r o p e r t i e s [ Context .INITIAL CONTEXT FACTORY] = W L I n i t i a l C o n t e x t F a c t o r y name 15 16 i n i t i a l C o n t e x t = I n i t i a l C o n t e x t ( p r o p e r t i e s ) 17 18 c o n n e c t i o n F a c t o r y = i n i t i a l C o n t e x t . lookup ( " w e b l o g i c examples jms å QueueConnectionFactory " ) 19 queue = i n i t i a l C o n t e x t . lookup ( " w e b l o g i c examples jms exampleQueue " ) 20 i n i t i a l C o n t e x t . c l o s e ( ) 21 22 # C r e at e a c o n n e c t i o n . 23 c o n n e c t i o

n = c o n n e c t i o n F a c t o r y . c r e a t e Q u e u e C o n n e c t i o n ( ) 24 c o n n e c t i o n . s t a r t ( ) 25 26 random = Random ( ) 27 28 def c r e a t e B y t e s M e s s a g e ( s e s s i o n , s i z e ) : 29 bytes = zeros ( size , ’b ’ ) 30 random . n e x t B y t e s ( b y t e s ) 31 message = s e s s i o n . c r e a t e B y t e s M e s s a g e ( ) 32 message . w r i t e B y t e s ( b y t e s ) 33 return message 34 35 c l a s s TestRunner : 36 def call ( s e l f ) : 37 log = grinder . logger info 38 39 l o g ( " C r e a t i n g ␣ queue ␣ s e s s i o n " ) 40 s e s s i o n = c o n n e c t i o n . c r e a t e Q u e u e S e s s i o n ( 0 , S e s s i o n AUTO ACKNOWLEDGE) 41 50 Tesztelés 42 43 44 45 46 47 48 49 50 51 52 53 54 Grinder - Java Enterprise programok tesztelése s e n d e r = s e s s i o n . c r e a t e S e n d e r ( queue ) i n s t r u m e n t e d S e n d e r = Test ( 1 , " Send ␣ a ␣ message " ) . wrap ( s e n d

e r ) message = c r e a t e B y t e s M e s s a g e ( s e s s i o n , 1 0 0 ) l o g ( " Sending ␣ t e n ␣ messages " ) f o r i in r a n g e ( 0 , 1 0 ) : i n s t r u m e n t e d S e n d e r . send ( message ) grinder . sleep (100) l o g ( " C l o s i n g ␣ queue ␣ s e s s i o n " ) session . close () A Grinder statisztika API A Grinder ezen API-ja lehetővé teszi, hogy új statisztikákkal egészítsük ki méréseinket. Mindegyik statisztika egy egyedi névvel rendelkezik Az alap statisztikák (Basic statistics) egy long vagy double értéket jelentenek. A minták statisztikája (Sample statistics) pedig egy összegzett (aggregated) long vagy double értékeket jelentenek Ennek van 3 gyakran használt esete a count ( a minták száma), a sum ( a minta értékeinek összege) és a variance (a minta varianciája). Nézzük milyen statisztikák vannak: kező eszközöket fogjuk majd használni a grinder.statistics csomagból: •

registerDataLogExpression(displayName, expression) metódus: Egy új részletező statisztikát regisztrál. • registerSummaryExpression(displayName, expression) metódus: Egy új összegző statisztikát regisztrál. • forCurrentTest: Beállítható ezzel, hogy a mérést mire gyűjtjük. • errors: A hibával végződött tesztelések Az expression string a statisztikai nevekből és száma. műveleti jelekből áll, amiket postfix módon kell leírnunk. Példa: (/ (sum timedTests) • timedTests: A jól elvégzett tesztek, azaz (count timedTests)) jelentése a teszt 1 futásáez egy mintastatisztika. nak átlagos ideje milliszekundumban. A sta• userLong0, userLong1, userLong2, user- tisztika API további részleteiről itt olvashaLong3, userLong4 : Saját célú statisztika, tunk: http://grindersourceforgenet/g3/ statistics.html ahol long a mért típus. • userDouble0, userDouble1, userDouble2, userDouble3, userDouble4 : Saját célú sta- JMS Receiver alapú tesztprogram

tisztika, ahol double a mért típus. Most végezzük el az előző teszt fordítottját, azaz • untimedTests: Sikeres tesztek, de nem olvassunk a queue-ról! Ennek a módszerét részben demonstrálta a 3-6. programlista, aminek mértünk időzítés információt. bemutatásánál már nem írjuk le újra azokat a A következő példában bemutatjuk hogyan lehet lépéseket (egészen a 25. sorig), amiket korábhasználni a statisztikai apparátust Ott a követ- ban megismertünk A program létrehozza a JMS 51 Tesztelés Grinder - Java Enterprise programok tesztelése session-t, olvas 10 üzenetet a már ismert queueról, majd lezárja a kapcsolatot. További érdekesség lesz, hogy a jmsreceiverpy új elemként bemutatja a Grinder statistics API használatát is, ahol a delivery time egy testre szabott értékfigyelés lesz. Ennek megfelelően a 33 sorban rögzítünk egy userLong0 nevű statisztikát, ami a riportokban Delivery time néven fog megjelenni. A 34 sorban pedig

egy összegző statisztikát regisztrálunk, ahol a kiszámítás módját postfix alakban adtuk meg A jelentése az, hogy adja össze az időzített és nem időzített sikeres tesztek számát (ezt a Grinder méri, előre definiálva van), majd ezzel ossza el a korábban mért userLong0 értéket. A 40-41 sorok között definiált recordDeliveryTime() függvény rendeli a deliveryTime idő értékét (ezt a 67. sorban megadott módon tudjuk megszerezni) a userLong0 -hoz. A scriptben a 43 sorban adtuk meg a mérés egyetlen Test osztálybeli objektumát recordTest néven, ami már az említett recordDeliveryTime() függvényt csomagolja be, azaz ez lesz a teszt algoritmusa. Ahogy azt már megszokhattuk, befejezésül nézzük meg a TestRunner class felépítését A 47-49 sorok közötti init () az osztály konstruktora, most az inicializálási feladatok miatt ezt is elkészítettük Tekintettel arra, hogy most üzeneteket fogadunk, így az 57-58 sorokban most a receiver

objektumot kellett létrehozni a JMS API-nál ismert módon. Az 58 sorban beállítjuk magunkra (self ) a messageListener -t, azaz működni fog a 85-96 sorok közötti onMessage() metódus, ez fogja egymás után levenni az üzeneteket, kiszámítja a deliveryTime értékét (91. sor) és eltárolja Mindeközben van egy Condition objektum, ami a szálak kezelését támogatja, segítségével most szinkronizálunk a mérés algoritmusa és az asszinkron onMessage() között. A már említett becsomagolt recordTest nevű Test objektumot a 74. sorban futtatjuk 1 // 3 −7. p r o g r a m l i s t a : j m s r e c e i v e r py 2 3 from j a v a . l a n g import System 4 from j a v a . u t i l import P r o p e r t i e s 5 from j a v a x . jms import M e s s a g e L i s t e n e r , S e s s i o n 6 from j a v a x . naming import Context , I n i t i a l C o n t e x t 7 from n e t . g r i n d e r s c r i p t Grinder import g r i n d e r 8 from n e t . g r i n d e r s c r i p t import Test 9

from t h r e a d i n g import C o n d i t i o n 10 from w e b l o g i c . j n d i import W L I n i t i a l C o n t e x t F a c t o r y 11 12 # Look up c o n n e c t i o n f a c t o r y and queue i n JNDI . 13 p r o p e r t i e s = P r o p e r t i e s ( ) 14 p r o p e r t i e s [ Context .PROVIDER URL] = " t 3 : / / l o c a l h o s t : 7 0 0 1 " 15 p r o p e r t i e s [ Context .INITIAL CONTEXT FACTORY] = W L I n i t i a l C o n t e x t F a c t o r y name 16 17 i n i t i a l C o n t e x t = I n i t i a l C o n t e x t ( p r o p e r t i e s ) 18 19 c o n n e c t i o n F a c t o r y = i n i t i a l C o n t e x t . lookup ( " w e b l o g i c examples jms å QueueConnectionFactory " ) 20 queue = i n i t i a l C o n t e x t . lookup ( " w e b l o g i c examples jms exampleQueue " ) 21 i n i t i a l C o n t e x t . c l o s e ( ) 22 23 # C r e at e a c o n n e c t i o n . 24 c o n n e c t i o n = c o n n e c t i o n F a c t o r y . c r e a t e Q u e u e C o

n n e c t i o n ( ) 25 c o n n e c t i o n . s t a r t ( ) 52 Tesztelés 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 # # # # # Grinder - Java Enterprise programok tesztelése Add two s t a t i s t i c s e x p r e s s i o n s : 1 . D e l i v e r y time :− t h e mean time t a k e n b e t w e e n t h e s e r v e r s e n d i n g t h e message and t h e r e c e i v e r r e c e i v i n g t h e message . 2 . Mean d e l i v e r y time :− t h e d e l i v e r y time a v e r a g e d o v e r a l l t e s t s We use t h e userLong0 s t a t i s t i c t o r e p r e s e n t t h e " d e l i v e r y time " . g r i n d e r . s t a t i s t i c s r e g i s t e r D a t a L o g E x p r e s s i o n ( " D e l i v e r y ␣ time " , " userLong0 " ) grinder . s t a t i s t i c s registerSummaryExpression ( "Mean␣ d e l i v e r y ␣ time " , "

( / ␣ userLong0 (+␣ t i m e d T e s t s ␣ untimedTests ) ) " ) # We r e c o r d each message r e c e i p t a g a i n s t a s i n g l e t e s t . The # t e s t time i s m e a n i n g l e s s . def r e c o r d D e l i v e r y T i m e ( d e l i v e r y T i m e ) : g r i n d e r . s t a t i s t i c s f o r C u r r e n t T e s t s e t V a l u e ( " userLong0 " , d e l i v e r y T i m e ) r e c o r d T e s t = Test ( 1 , " R e c e i v e ␣ mess age s " ) . wrap ( r e c o r d D e l i v e r y T i m e ) c l a s s TestRunner ( M e s s a g e L i s t e n e r ) : def init ( s e l f ) : s e l f . messageQueue = [ ] recorded . s e l f . cv = C o n d i t i o n ( ) # Queue o f r e c e i v e d messages not y e t å # Used t o s y n c h r o n i s e t h r e a d a c t i v i t y . def call ( s e l f ) : log = grinder . logger info l o g ( " C r e a t i n g ␣ queue ␣ s e s s i o n ␣and␣ a ␣ r e c e i v e r " ) s e s s i o n = c o n n e c t i o

n . c r e a t e Q u e u e S e s s i o n ( 0 , S e s s i o n AUTO ACKNOWLEDGE) r e c e i v e r = s e s s i o n . c r e a t e R e c e i v e r ( queue ) r e c e i v e r . messageListener = s e l f # Read 10 messages from t h e queue . f o r i in r a n g e ( 0 , 1 0 ) : # Wait u n t i l we have r e c e i v e d a message . s e l f . cv a c q u i r e ( ) while not s e l f . messageQueue : s e l f cv w a i t ( ) # Pop d e l i v e r y time from f i r s t message i n message queue d e l i v e r y T i m e = s e l f . messageQueue pop ( 0 ) s e l f . cv r e l e a s e ( ) l o g ( " R e c e i v e d ␣ message " ) # We r e c o r d t h e t e s t a h e r e r a t h e r than i n onMessage # b e c a u s e we must do so from a worker t h r e a d . recordTest ( deliveryTime ) l o g ( " C l o s i n g ␣ queue ␣ s e s s i o n " ) 53 Tesztelés 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 Grinder - Java Enterprise programok tesztelése session . close () #

Rather than o v e r c o m p l i c a t e t h i n g s w i t h e x p l i c t message # acknowledgement , we s i m p l y d i s c a r d any a d d i t i o n a l messages # we may have read . l o g ( " R e c e i v e d ␣%d␣ a d d i t i o n a l ␣ m ess age s " % l e n ( s e l f . messageQueue ) ) # C a l l e d a s y n c h r o n o u s l y by JMS when a message a r r i v e s . def onMessage ( s e l f , message ) : s e l f . cv a c q u i r e ( ) # In WebLogic S e r v e r JMS, t h e JMS timestamp i s s e t by t h e # s e n d e r s e s s i o n . A l l we need t o do i s e n s u r e our c l o c k s a r e # synchronised . d e l i v e r y T i m e = System . c u r r e n t T i m e M i l l i s ( ) − message getJMSTimestamp ( ) s e l f . messageQueue append ( d e l i v e r y T i m e ) s e l f . cv n o t i f y A l l ( ) s e l f . cv r e l e a s e ( ) E-Mail alapú tesztprogram A levélküldő rendszerünk teljesítményének egy lehetséges mérését valósítja a 3-8.

programlista A háttérben a JavaMail API-t használjuk, így az ahhoz szükséges jar file-oknak elérhetőnek kell lenniük. Akit ez a téma jobban érdekel, olvassa el ezt a dokumentumot: http://www.oraclecom/technetwork/ java/javamail-1-149769.pdf A tesztprogramunk egyébként kiváló eszköz lehet SPAM-ek nagyszámú küldésére, így erre ezt ne használjuk! A script szerzőit a példaprogram elején lévő megjegyzésből tudhatjuk meg. A script elég egyszerű felépítésű, az egyetlen Grinder Test objektuma (neve: emailSendTest1 ) a 15 sorban lett létrehozva és már nézhetjük is a TestRunner által elvégzett feladatot. A 19 sornál a saját mail szerverünkre kell állítani az smtpHost változót. Ez a gmail esetén például smtp.gmailcom érték 1 // 3 −8. p r o g r a m l i s t a : e m a i l py 2 3 # 4 # C o p y r i g h t (C) 2004 Tom P i t t a r d 5 # C o p y r i g h t (C) 2004−2008 P h i l i p Aston 54 A 26-34 sorok között az elküldendő MIME message

formátumnak megfelelő levélüzenetet állítjuk össze, ahogy láthatjuk ez egy generált tartalom. A 37-43 sorok között pedig a levél fizikai elküldése valósul meg. A Java levélkezelő API a system property beállításokból veszi ki a levelező szerver nevét, emiatt szükséges a mail.smtphost Java környezeti változót a 22. sorban a célszerverre állítani A 23 sorban szerezzük meg a session objektumot, amit a levél összeállítása és elküldése (transport) során is használunk A message tárgy (subject) részébe itt is – hasonlóan az adatbázis-kezelés részhez – a runNumber és threadNumber értékeket tesszük be. A setFrom() helyes kitöltése mindig fontos, ha hitelesítés kell a levélküldéshez. Itt a 40 sor connect() metódushívása hitelesítést is kér, emiatt ez most is fontos. A levél szövege egy konstans tartalom lesz. Tesztelés Grinder - Java Enterprise programok tesztelése 6 # D i s t r i b u t e d under t h e terms o f The

Grinder l i c e n s e . 7 8 from n e t . g r i n d e r s c r i p t Grinder import g r i n d e r 9 from n e t . g r i n d e r s c r i p t import Test 10 11 from j a v a . l a n g import System 12 from j a v a x . m a i l import Message , S e s s i o n 13 from j a v a x . m a i l i n t e r n e t import I n t e r n e t A d d r e s s , MimeMessage 14 15 e m a i l S e n d T e s t1 = Test ( 1 , " Email ␣ Send ␣ Engine " ) 16 17 c l a s s TestRunner : 18 def call ( s e l f ) : 19 smtpHost = " m a i l h o s t " 20 21 p r o p e r t i e s = System . g e t P r o p e r t i e s ( ) 22 p r o p e r t i e s [ " m a i l . smtp h o s t " ] = smtpHost 23 s e s s i o n = S e s s i o n . g e t I n s t a n c e ( System g e t P r o p e r t i e s ( ) ) 24 s e s s i o n . debug = 1 25 26 message = MimeMessage ( s e s s i o n ) 27 message . setFrom ( I n t e r n e t A d d r e s s ( " TheGrinder@yourtestdomain n e t " ) ) 28 message . a d d R e c i p i e n t

( Message R e c i p i e n t T y p e TO, 29 I n t e r n e t A d d r e s s ( " you@yourtestdomain . n e t " ) ) 30 message . s u b j e c t = " Test ␣ e m a i l ␣%s ␣ from ␣ t h r e a d ␣%s " % ( g r i n d e r runNumber , 31 g r i n d e r . threadNumber ) 32 33 # One c o u l d v a r y t h i s by p o i n t i n g t o v a r i o u s f i l e s f o r c o n t e n t 34 message . s e t T e x t ( "SMTPTransport␣ Email ␣ works ␣ from ␣The␣ G r in d e r ! " ) 35 36 # Wrap t r a n s p o r t o b j e c t i n a Grinder Jython Test Wrapper 37 t r a n s p o r t = e mai lSe nd Tes t1 . wrap ( s e s s i o n g e t T r a n s p o r t ( "smtp" ) ) 38 39 t r a n s p o r t = e mai lSe nd Tes t1 . wrap ( t r a n s p o r t ) 40 t r a n s p o r t . c o n n e c t ( smtpHost , " username " , " password " ) 41 t r a n s p o r t . sendMessage ( message , 42 message . g e t R e c i p i e n t s ( Message R e c i p i e n t T y p e TO) ) 43

transport . close () Amennyiben a gmail rendszert akarjuk hasz- dig így történhet: nálni, úgy a proprty értékek ezek legyenek: S e s s i o n s e s s i o n = S e s s i o n . g e t I n s t a n c e ( props , P r o p e r t i e s p r o p s = new P r o p e r t i e s ( ) ; p r o p s . put ( " m a i l smtp auth " , " t r u e " ) ; p r o p s . put ( " m a i l smtp s t a r t t l s e n a b l e " , " t r u e " ) ; p r o p s . put ( " m a i l smtp h o s t " , " smtp g m a i l com " ) ; p r o p s . put ( " m a i l smtp p o r t " , " 5 8 7 " ) ; Látható, hogy itt a megszokott titkosított SSL csatornát használjuk. A session lekérése pe- new j a v a x . m a i l A u t h e n t i c a t o r ( ) { protected PasswordAuthentication å getPasswordAuthentication () { r e t u r n new P a s s w o r d A u t h e n t i c a t i o n ( å username , password ) ; } }) ; 55 Security 4. A Java biztonsági

rendszere - Kerberos alapú SSO A Java biztonsági rendszere - Kerberos alapú SSO A Kerberos egy régóta létező Computer Network Authentication Protocol amit a MIT-en (Massachusetts Institute of Technology) fejlesztettek ki. A név a görög mitológiában szereplő 3 fejű kutyára utal, amit még Cerberus néven is emlegetnek. A 41 ábrán látható KDC, Szerver és Kliens, mint az együttműködés 3 oldala ihlette az elnevezést A cikk egy Windows infrastruktúrát használó Java szerver környezetben mutatja be a Kerberos használatát, szakítva a Windows NTLM v1/v2-re épülő megközelítéssel. A Kerberos áttekintése 4.1 ábra A Kerberos működése A mai Windows környezetek használata közben többször botlunk a Kerberos névbe. Mi ez valójában? A Windows 2000 óta Ő a Windows alapértelmezett hitelesítő protokollja. A Kerberos biztonsági protokollt az 1980-as években fejlesztették ki az USA-ban. Jelenleg elterjedt 5. verziója az IETF (Internet

Engineering Task Force) RFC 1510 szabványban van specifikálva. A kompatibilitás megőrzése érdekében a Windows infrastruktúrák még elterjedten használják a régebbi hitelesítési és biztonsági eljárásokat is. Ilyen az NTLM v1/v2, de a fő protokollá a Kerberos vált Az RFC 1510 szabvány pontosan meghatározza azt is, hogy milyen egyéb biztonsági szolgáltatásokkal tud a Kerberos együttműködni. Feladata a felhasználók hitelesítése a hálózati szolgáltatások eléréséhez. Támogatja, hogy a hitelesítési kommunikáció során az adatok titkosítva haladjanak át a háló56 zaton, továbbá harmadik személy ne tudja elolvasni vagy módosítani azokat. Az operációs rendszerben rendelkezésre álló kódolási eljárásokat lehet használni, de a titkosítás – ha lehetséges – az úgynevezett titkos kulcsú (secret key encryption) kódolási algoritmus szerint történik. Ilyenkor egy titkos kulccsal megy végbe az adatok kódolása a küldő

oldalon, a fogadónál pedig ugyanarra a kulcsra van szükség a visszafejtéshez. A küldő biztos lehet benne, hogy az információt csak a fogadó tudja értelmezni, a fogadó pedig megbízhat abban, hogy az információ a küldőtől érkezett. A felhasználói fiókok és jelszavak a KDC-ben (Key Distribution Center =Kulcskiosztó Központ) tárolódnak. Ez a Windows szerver környezetben a tartományvezérlőkön, az Active Directory-ban kapott helyet és ott erős titkosítással vannak védve az adatok. A protokoll a többféle titkosítási algoritmuson kívül a különböző hosszúságú titkosítási kulcsokat is megérti. Használhatjuk például a DES (Data Encryption Standard ) RC4 algoritmusának a 128 bites verzióját is. A Kerberos jegyrendszer A jegyrendszer működése a háttérben, láthatatlanul zajlik. A felhasználó annyit vesz észre belőle, hogy kéri az operációs rendszer a felhasználói nevét és jelszavát, ő ezt megadja és elérhetővé válnak

számára a jogosultságának megfelelő erőforrások. A KDC rendelkezésre állása magas A 4.1 ábra mutatja a hitelesítés kommunikációs Security A Java biztonsági rendszere - Kerberos alapú SSO szekvenciáját, illetve a közben használt security jegyeket (ticket). A hitelesítés 1. A felhasználó megadja a nevét és jelszavát a KDC-nek (ez történhet egy bejelentkező dialógus ablakkal vagy akár smart kártyával is). 2. A KDC kiad egy TGT (Ticket Granting Ticket=Jegymegadási Jegyet). 3. Ezzel a TGT -vel lehet elérni a TGS -t (Ticket Granting Service=Jegymegadási Szolgáltatás) 4. A TGS kiad az ügyfélnek egy szolgáltatásjegyet 5. A hálózati szolgáltatások eléréséhez az ügyfél ezt a szolgáltatás jegyet fogja bemutatni. Ezt nevezik kölcsönös hitelesítésnek, mert a jegy hitelesíti az ügyfelet a szolgáltatásnak, a szolgáltatás pedig hitelesíti magát az ügyfélnek. Az adatok sérthetetlensége A szigorú hitelesítési eljárás

keveset ér, ha a kiszolgáló és az ügyfél között módosulhat az információ. Harmadik fél, aki az adatokat megfejteni nem tudja, esetleg módosíthatja, ezzel értéktelenné téve azokat. Ez ellen úgy védekezik a Kerberos, hogy minden csomaghoz képez egy ellenőrző összeget (checksum) Bármilyen adatváltozás történik, az ellenőrző összeg alapján azonnal kiderül A biztonság fokozása érdekében az adatcsomagok és az ellenőrző összegek kódolva, egymástól függetlenül, külön kerülnek átvitelre. Delegálás A delegálás a hitelesítési jog átruházása egy közbenső személy vagy számítógépfiók számára: • Az ügyfél nem a KDC-hez, hanem a köztes állomáshoz fordul TGT kéréssel. • Az állomás továbbítja a kérést a KDC-hez, a kommunikáció ezután köztük zajlik. • Az állomás az ügyfél nevében hozzájut a szolgáltatásjegyhez. • Ezzel a jeggyel pedig a szolgáltatást nyújtó kiszolgálóhoz fordul. •

Megtörténik a hitelesítés. • Ezen a ponton megszűnik a köztes állomás szerepe, ettől kezdve a kommunikáció már az ügyfél és a szolgáltatás közt zajlik. Alapértelmezésben delegálási joggal csak a tartománygazdák rendelkeznek. A Kerberos és az NTLM összehasonlítása A Windows 2000 előtti operációs rendszerek bevált hitelesítési eljárása az NTLM volt, de azóta a Kerberos lett, aminek a fontosabb okai a következőek: • A Kerberos oda - vissza alapú hitelesítést tesz lehetővé az ügyfél és a szolgáltatás között. • Delegálható a hitelesítés. • Gyorsabb. • Nem kell a szolgáltatás kiszolgálójának minden alkalommal a tartományvezérlőhöz fordulnia az ügyfél hitelesítése érdekében. • Más platformokon (pl. Unix) is használható, nem csak a Windows alapú rendszerekben 57 Security A Java biztonsági rendszere - Kerberos alapú SSO Gyakorlati tudnivalók A Kerberos az alapértelmezett hitelesítési protokoll a

Windows 2000-től. így csak akkor nincs használatban, ha Windows 2000 előtti rendszerek hitelesítésére van szükség, ekkor az NTLM alkalmazására kerül sor. A csoportházirendben vannak előírva a Kerberos irányelvek. Indítsuk el a Felügyeleti eszközökTartományi biztonsági házirend és/vagy a Tartományvezérlő biztonsági házirend MMC konzolokat. Tallózzunk el a Biztonsági beállításokFiókházirendKerberos irányelv beállításokhoz Az alábbi állítási lehetőségeink vannak: • Szolgáltatásjegy maximális élettartama. A TGS által kiadott jegyek (amellyel elérhetők a szolgáltatások) ennyi ideig használhatók fel. Ez folyamatos szolgáltatás használatot jelenti, lejárta után meg kell újítani Alapértelmezett érték: 600 perc Tartományvezérlőkön folyamatosan kell futnia a KDC-nek. Ez szolgáltatás formában van jelen és a Felügyeleti eszközökSzolgáltatások között a "Kerberos kulcs elosztó központ" nevű

automatikusan induló alkalmazást kell keresnünk. A bemutatandó konkrét feladat • Felhasználói bejelentkezési korlátozások érvényesítése. Engedélyezése esetén min- Célunk, hogy bemutassuk a Windows alapú Kerden felhasználói kérés a KDC-n keresztül beros autentikáció használatát egy Java szerver környezetben, esetünkben ez most Oracle Webkerül érvényesítésre. logicra telepített web alkalmazás Windows in• Felhasználói jegy maximális élettartama. tegrált SSO használatának ismertetése lesz A Ez a TGT (jegymegadási jegy) felhasznál- 4.2 ábra foglalja össze a megoldandó feladatot, hatóságának élettartama. Alapértelme- ahol jelöltük a példa konfigurációkban alkalmazett érték: 10 óra zott konkrét elnevezéseket (host név, stb.) is • Felhasználói jegy megújításának maximá- Ezeket az elnevezéseket természetesen az aktulis élettartama. Maximum ennyi ideig le- ális telepítési környezetben érvényes értékekkel

het megújítani a szolgáltatás jegyet, mi- kell helyettesíteni. A Windows domainben lévő előtt újat kell igényelni. Alapértelmezett desktop az Internet Explorer böngésző segítségével éri el egy Weblogicba telepített web alérték: 7 nap kalmazást. A desktop gép egy Windows doma• Számítógép időszinkronjának maximális inben van (DEVAD), a web alkalmazást pedig tűrése. Maximum ennyi eltérés lehet a kli- úgy konfiguráltuk, hogy SPNEGO autentikációt ens és a kiszolgáló órái között. Ha ennél használjon Windows domainben a SPNEGO2 nagyobb, a kliens órája igazodik a kiszol- két konkrét autentikációs algoritmust használgálóéhoz. Ezzel kiszűrhetők az ismétlődő hat: NTLM vagy Kerberos Jelen dokumentum támadások (replay attacks), illetve az út- azzal foglalkozik, hogy tudunk Kerberos autenközben elfogott és esetleg sikeresen módo- tikációt használni SPNEGO-val. Amennyiben sított csomagok, amikor visszakerülnek a a

környezetet helyesen konfiguráljuk, az Interhálózatra érvénytelenné válnak. A Kerbe- net Explorer elküldi a Kerberos ticketet a Webros protokoll időbélyegeket épít be a há- logic alkalmazásnak, ami validálja azt a birtokálózatra küldött csomagokba, ezért fontos, ban lévő kulcs segítségével Amennyiben a tichogy ne legyen nagy különbség a számító- ket helyes, a Weblogic szerver a ticketből meggépek rendszerideje között állapítja a desktopon a domainbe bejelentkezett 2 58 SPNEGO=Simple and Protected GSSAPI Negotiation Mechanism Security A Java biztonsági rendszere - Kerberos alapú SSO user azonosítóját, és LDAP-on keresztül lekérdezi az AD-ból a user csoportjait, ezáltal létrejön a Weblogicban egy JAAS módon autentikációt subject. Ezt a subjectet a Weblogicba telepített alkalmazás már úgy használhatja, mint egy normál JAAS security context. Az ábrán a Kerberos protokollon történő kommunikációt nem tüntettük fel.

A következő fejezetek azt írják le, hogy a fenti módon működő autentikációt hogyan lehet bekonfigurálni A Weblogicban futó minta web alkalmazás URL-je: http: //dkeaapp.dkhu:15301/ssologin 4.2 ábra A Kerberos hitelesítés használata Weblogicban Windows Server oldali konfiguráció Active Directory (AD) Az encryption type beállítás A Windows Serveren engedélyezni kell minden encryption type-ot a Kerberoshoz. Ehhez használni kell a Group Policy Management Console programot (command line-ból: gpmcmsc) A 4.3 ábra mutatja a beállítás módját, szürkével kiemelve látszanak a kiválasztandó menüpontok A feljövő dialógus ablak Security Policy Setting fülén ki kell választani az összes elérhető encryption type-ot. A beállítás után a Windows Server újraindítása szükséges. Az Active Directory-ban az alábbi konfigurációk elvégzésére van szükség. Egy technikai user létrehozása Szükséges egy technikai felhasználó létrehozása, amit

hozzárendelünk a Weblogic-ban futó szolgáltatáshoz a Weblogic host neve alapján. Ez azért szükséges, mert ehhez a felhasználóhoz generáljuk le azt a kulcsot, amivel az adott Weblogic alkalmazás autentikációjához szükséges Kerberos ticketek titkosítva és hitelesítve lesz59 Security A Java biztonsági rendszere - Kerberos alapú SSO nek. A kulcsot csak a Windows Server (KDC) és a Weblogic alkalmazás fogja ismerni. Amikor az Internet Explorerben megnyitjuk a http:// dkeaapp.dkhu:15301/ssologin URL-t, azaz hozzáférést kezdeményezünk a Weblogic alkalmazáshoz, az IE Kerberos protokollon lekér egy titkosító kulcsot a KDC-től (Windows Server) az URL-ből képzett SPN (Service Principal Name) alapján. Az SPN formátuma: HTTP/< t e l j e s h o s t név>@<K e r b e r o s realm név , å nagybetűkkel> A mi esetünkben: A titkosító kulcsot a KDC az SPN-hez rendelt technikai felhasználó jelszavából képzi, tehát a desktop géppel a

kulcsokat ténylegesen nem osztja meg. Az Internet Explorer ezzel a titkosító kulccsal készít egy Kerberos tokent, amit a Weblogicnak elküld a http://dkeaapp. dk.hu:15301/ssologin kérés HTTP headerében A Weblogic alkalmazás a nála lévő, technikai felhasználóhoz tartozó 1 kulcs segítségével validálja le a tokent, és fejti belőle vissza a desktopon a Windows domainbe bejelentkezett user nevét. h t t p / dkeaapp . dk hu@DEVADLOCAL 4.3 ábra Group Policy Management Console 60 Security A Java biztonsági rendszere - Kerberos alapú SSO 4.4 ábra Active Directory Users and Computers alkalmazás A technikai user létrehozásának menete: 1. Hozzuk létre a usert (Jelen példában az Administrator usert használjuk erre a célra). Fontos megjegyezni, hogy mivel a user jelszavát nem kell megosztani a Weblogic oldallal sem, ezért a megoldás biztonságosnak tekinthető. 2. Hozzuk létre az SPN-t a Windows gépen a setspn utility segítségével, amihez a

következő parancsot kell lefuttatni: s e t s p n −A <SPN neve a K e r b e r o s realm névå n é l k ü l > <t e c h n i k a i u s e r neve> Példánkban ez: s e t s p n −A HTTP/ dkeaapp . dk hu å Administrator 4.5 ábra A delegáció beállítása 61 Security A Java biztonsági rendszere - Kerberos alapú SSO Érdemes létrehozni egy olyan SPN-t is, megnézzük, hogy az adott SPN hányszor szereamiben csak a host név szerepel, nem a pel az AD-ban. Ha többször, akkor a nem megfehost teljes neve Példánkban ez ezt jelenti: lelő bejegyzéseket a setspn –D <SPN> <user> paranccsal törölhetjük. s e t s p n −A HTTP/ dkeaapp A d m i n i s t r a t o r 3. Hozzuk létre a technikai userhez tartozó kulcsot, amit a Weblogic alkalmazás alá másolunk majd. Ehhez a ktpass utility-t kell lefuttatni a Windows Serveren a következőképpen: k t p a s s −out <h o s t név >. h o s t keytab −å mapuser <t e c h n i k a i u s e r

neve>@<å K e r b e r o s realm név> −p r i n c <SPN å t e l j e s neve> −p a s s <a t e c h n i k a i u s e r å j e l s z a v a > −ptype KRB5 NT PRINCIPAL Példánkban ez így néz ki: k t p a s s −out dkeaapp . h o s t keytab −mapuserå Administrator@DEVAD .LOCAL −p r i n c å HTTP/ dkeaapp . dk hu@DEVADLOCAL −p a s s å P@ssword −ptype KRB5 NT PRINCIPAL A parancs lefuttatása után létrejön egy .keytab fájl, ami esetünkben dkeaapphostkeytab és őt a Weblogic-ot futtató szerverre kell majd másolni. Egy normál domain user létrehozása Hozzunk létre egy normál domain usert, amivel az SSO-t fogjuk tesztelni a desktopon (ez esetünkben a dkeaapp nevű user lesz). Ennek a usernek az megadott beállításokat kell megadni az ismertetett Account fülön. Egy LDAP lekérdező user létrehozása Hozzunk létre egy usert, amit arra használunk, hogy a nevében a Weblogic LDAP lekérdezéseket tudjon futtatni. Windows kliens oldali

konfiguráció A Windows desktopon az Internet Explorert úgy kell beállítani, hogy az adott URL-re használja a Windows Integrated Authenticationt (IWA). Ehhez az Internet Explorerben a Tools/Internet Settings menübe lépve a Security fülön fel kell venni a Local Intranet helyei közé a Weblogic hostot (esetünkben: http://dkeaapp.dkhu), és az Advanced fülön engedélyezni kell az Integrated Windows Authenticationt. 4. Állítsuk be a technikai userre a következőket az Active Directory Users and Computers alkalmazás segítségével (44 ábra, Account fül). Érdemes beállítani azt is itt, hogy a user jelszava soha ne járjon le (Password never expires). Ugyanezen ablak DeWeblogic oldali konfiguráció legation fülén válasszuk ki a 4.5 ábrán látható középső rádió gombot. A következőkben az Oracle Weblogic szerverben elvégzendő beállításokat ismertetjük. 5. Reseteljük a technikai user jelszavát, ami egy biztonságnövelő lépés csupán. Active

Directory Authentication Provider Megjegyzés: Fontos, hogy az előző pontokban létrehozott SPN-ekhez ne legyen több user bekonfigurálva, mert ez a legkülönfélébb hibákat okozhatja. Ezt a legkönnyebben úgy ellenőrizhetjük, hogy egy szöveges fájlba dumpoljuk az AD tartalmát (ldifde –f <fájl neve> parancs), és 62 Az első lépés az Active Directory Authentication Provider bekonfigurálása, amit a Weblogic Admin Console Security Realm Providers Authentication New Active Directory kiválasztásával tehetünk meg. Példánkban a következő paramétereket használtuk: Security A Java biztonsági rendszere - Kerberos alapú SSO name : A c t i v e D i r e c t o r y c o n t r o l −f l a g : OPTIONAL h o s t : dkbcc . devad l o c a l u s e r −name−a t t r i b u t e : sAMAccountName p r i n c i p a l : CN=A d m i n i s t r a t o r ,CN=Users ,DC=devad ,DC=l o c a l u s e r −base−dn : dc=devad , dc=l o c a l c r e d e n t i a l −e n c r y p t e

d : P@ssword u s e r −from−name− f i l t e r : (&(sAMAccountName=%u ) ( o b j e c t c l a s s=u s e r ) ) group−base−dn : dc=devad , dc=l o c a l use−r e t r i e v e d −u s e r −name−as−p r i n c i p a l : t r u e Ez a hitelesítő szolgáltató (provider) bizto- től. sítja, hogy a Weblogic képes legyen a bekonfigu- WWW - Authenticate : Negotiate rált AD-t felhasználókat hitelesítő adatbázisként használni. A helyes sorrend beállítása SPNEGO Identity Asserter SPNEGO Identity Asserter konfigurálása. Ehhez egyszerűen alap beállításokkal létre kell hozni egy új NegotiateIdentityAsserter -t (Security Realm Providers Authentication New NegotiateIdentityAsserter). Ügyeljünk, hogy az asserter mindkét token típusa aktív legyen, bár alapértelmezetten az asserter így jön létre. A SPNEGO védelme alatt álló erőforrás esetén a szerver a HTTP fejlécben ezt kéri a kliens- Adjuk meg a helyes sorrendet az authentikátoroknak. Legyen

az első az SPNEGO-ra létrehozott asserter, a második az AD-hoz létrehozott authentikátor, majd tegyünk minden egyéb authentikátort opcionálisra (control flag: OPTIONAL). A Kerberos bekonfigurálása Hozzuk létre a Weblogic Server domain könyvtárában egy spnego conf nevű könyvtárat, és helyezzük el benne a következő fájlokat: 1. Az előzőleg létrehozott keytab fájlt, ami példánkban most a dkeaapphostkeytab 2. Egy loginconf nevű fájlt a következő tartalommal: com . sun s e c u r i t y j g s s krb5 i n i t i a t e { com . sun s e c u r i t y auth module Krb5LoginModule r e q u i r e d s t o r e K e y=t r u e debug=t r u e useKeyTab=t r u e keyTab="<keytab f á j l h e l y e >" u s e T i c k e t C a c h e=t r u e p r i n c i p a l ="<SPN neve >"; }; com . sun s e c u r i t y j g s s krb5 a c c e p t { 63 Security }; A Java biztonsági rendszere - Kerberos alapú SSO com . sun s e c u r i t y auth module

Krb5LoginModule r e q u i r e d s t o r e K e y=t r u e debug=t r u e useKeyTab=t r u e keyTab="<keytab f á j l h e l y e >" u s e T i c k e t C a c h e=t r u e p r i n c i p a l ="<SPN neve >"; Példánkban ezt a fájlt így kell konkrét értékekkel felruházni: com . sun s e c u r i t y j g s s krb5 i n i t i a t e { com . sun s e c u r i t y auth module Krb5LoginModule r e q u i r e d s t o r e K e y=t r u e debug=t r u e useKeyTab=t r u e keyTab="/home/ wlsapp /ad domain/ spnego conf / dkeaapp . h o s t keytab " u s e T i c k e t C a c h e=t r u e p r i n c i p a l ="HTTP/ dkeaapp . dk hu@DEVADLOCAL" ; }; com . sun s e c u r i t y j g s s krb5 a c c e p t { com . sun s e c u r i t y auth module Krb5LoginModule r e q u i r e d s t o r e K e y=t r u e debug=t r u e useKeyTab=t r u e keyTab="/home/ wlsapp /ad domain/ spnego conf / dkeaapp . h o s t keytab " u s e T i c k e t C a c h e=t r u e p r i n c

i p a l ="HTTP/ dkeaapp . dk hu@DEVADLOCAL" ; }; A debug=true értéket éles környezetben érdemes debug=false-ra változtatni. A Kerberos realm nevét nagybetűvel kell írni (a példában ez DEVAD.LOCAL) 3. Egy krb5conf fájlt a következő tartalommal: [ libdefaults ] d e f a u l t r e a l m = <Kerb e r o s realm neve n a g y b e t ű v e l > d e f a u l t t k t e n c t y p e s = rc4−hmac aes256 −c t s des−cbc−md5 d e f a u l t t g s e n c t y p e s = rc4−hmac aes256 −c t s des−cbc−md5 permitted enctypes = aes256 −c t s aes128 −c t s rc4−hmac des3−cbc−sha1 å des−cbc−md5 des−cbc−c r c [ realms ] <Kerberos realm neve n a g y b e t ű v e l > = { kdc = <KDC h o s t neve> default domain = <Windows DNS domain név> } [ domain realm ] .<Windows DNS domain név> = <K e r b e r o s realm neve n a g y b e t ű v e l > 64 Security A Java biztonsági rendszere - Kerberos alapú SSO Esetünkben

ez a fájl így néz ki: [ libdefaults ] d e f a u l t r e a l m = DEVAD.LOCAL d e f a u l t t k t e n c t y p e s = rc4−hmac aes256 −c t s des−cbc−md5 d e f a u l t t g s e n c t y p e s = rc4−hmac aes256 −c t s des−cbc−md5 permitted enctypes = aes256 −c t s aes128 −c t s rc4−hmac des3−cbc−sha1 å des−cbc−md5 des−cbc−c r c [ realms ] DEVAD.LOCAL = { kdc = dkbccdevad . devad l o c a l default domain = devad . l o c a l } [ domain realm ] . devad l o c a l = DEVADLOCAL Java system property beállítás A Weblogic indító szkriptjében adjuk meg a következő Java system property-ket: −Djava . s e c u r i t y k r b 5 c o n f=<k r b 5 c o n f path>s p n e g o c o n f /å krb5 . c o n f −Djava . s e c u r i t y auth l o g i n c o n f i g=<l o g i n c o n f path>å spnego conf / l o g i n . c o n f −Djavax . s e c u r i t y auth u s e S u b j e c t C r e d s O n l y= f a l s e −D w e b l o g i c . s e c u r i t y

e n a b l e N e g o t i a t e=t r u e Példánkban: −Djava . s e c u r i t y k r b 5 c o n f=s p n e g o c o n f / k r b 5 c o n f −Djava . s e c u r i t y auth l o g i n c o n f i g=s p n e g o c o n f / l o g i n å conf −Djavax . s e c u r i t y auth u s e S u b j e c t C r e d s O n l y= f a l s e −D w e b l o g i c . s e c u r i t y e n a b l e N e g o t i a t e=t r u e Amennyiben hibakereséshez debugolni szeretnénk: −D w e b l o g i c . S t d o u t DebugEnabled=t r u e −D w e b l o g i c . S t d o u t S e v e r i t y L e v e l =64 −Dsun . s e c u r i t y k r b 5 debug=t r u e −Dsun . s e c u r i t y j g s s debug=t r u e A beállítások után indítsuk újra a Weblogic szervert! A minta webalkalmazás A Kerberos SSO használatához úgy kell elkészíteni a web alkalmazást, hogy használja a HTTP kommunikációban szereplő Kerberos ticketeket. Ehhez a web.xml fájlt kell megfelelő módon előállítani, más különösebb teendő

nincsen (4-1 programlista). Magát a fájlt ugyanúgy kell megadni, mint minden más normál JAAS -t használó web alkalmazásnál A 27 sorban megadott autentikáció módja tehát mindenképpen CLIENT-CERT kell, hogy legyen, de opcionálisan lehet alkalmazni fallback mechanizmust. Ez a példában egy form based autentikáció, aminek a konfigurációját a 28-31 sorok között határoztuk meg. Ehhez kellett definiálni a form-loginconfig részt a login és error oldalak megadásával Erre akkor lehet szükség, ha az alkalmazást olyan desktopról is el lehet érni, ami nincs benne a Windows domainben, és ahol a desktop nem küld SPNEGO tokeneket. Ilyenkor explicit szükséges bekérni a felhasználótól a user nevet és a jelszót a megadott login oldal felmutatásával. Természetesen továbbra is a Windows domainben érvényes user nevek és jelszavak használatosak, hiszen ekkor is az AD authentikátor végzi az autentikációt (nem csak a csoporttagság lekérdezését). A

példához készített mintaalkalmazás az ssologinwar fájlban található Ezt lehet telepíteni a Weblogic szerverbe Ügyelni kell arra, hogy a weblogic.xml fájlban (4-2 programlista) lévő principal-name tag-ek között szereplő user neveket/user csoportokat a helyi környezethez igazítsuk Ezek a userek, illetve cso65 Security A Java biztonsági rendszere - Kerberos alapú SSO portok az Active Directory-ban kell, hogy létez- programlista mutatja az ehhez tartozó login és zenek. Amennyiben beállítottuk az összes, előző error oldalt Esetleges hibák: pontokban leírt konfigurációt és telepítettük a • Amennyiben a következő hiba jön a WebWeblogic-on az ssologin.war alkalmazást, a DElogic oldalon: „KDC has no support for VAD domainben lévő Windows desktop gépen encryption type (14)”, valószínűleg a klia dkeaapp userrel bejelentkezve indítsuk el az ensről érkező token nem olyan kódolással Internet Explorert a következő URL-el: http:

érkezik, amilyen kódoláshoz a keytab fájl//dkeaapp.dkhu:15301/ssologin Amennyiban kulcs található Ilyenkor célszerű megben minden rendben van, a 46 ábrán mutatott nézni wiresharkkal a http forgalmat, megtartalmat kell látnunk. Az alkalmazás visszanézni, hogy milyen kódolással érkezik a írja a képernyőre a domainbe bejelentkezett felWeblogicba a token (a wireshark ezt meghasználó nevét, ezzel bizonyítva, hogy a Kerbemutatja, ismeri a KRB5 protokollt), és ezros autentikáció sikerült a Weblogic oldalon. Ezt zel újragenerálni a keytab fájlt (ilyenkor a működést a 4-3. programlistán tanulmányoza ktpass parancsnak –crypto kapcsolóval ható content.jsp lap biztosítja, ami a hitelesímeg kell adni ugyanazt a kódolási típust, tést követően hajtódhat csak végre. Ez a JSP amit a wiresharkban látunk) lap használja a 4-4-től a 4-8. programlistán szereplő Java osztályokat, ami a példa teljes köz• Ügyelni kell arra, hogy a 3

kommunikációlése érdekében változatlan formában helyeztünk ban szereplő gép (kliens, Windows Server, el. Amennyiben mégis FORM alapú hitelesítésre Weblogic) órája szinkronban legyen! irányít minket a szerver, úgy a 4-9. és 410 4.6 ábra A minta SSO webalkalmazás 66 Security 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 A Java biztonsági rendszere - Kerberos alapú SSO // 4 −1. p r o g r a m l i s t a : web xml <?xml version=" 1 . 0 " e n c o d i n g="UTF−8" ?> <web−app i d="WebApp ID" version=" 2 . 4 " xmlns=" h t t p : // j a v a sun com/xml/ ns / j 2 e e " x m l n s : x s i=" h t t p : //å www. w3 o r g /2001/XMLSchema−i n s t a n c e " x s i : s c h e m a L o c a t i o n=" h t t p : // j a v a sun com/xml/ ns / j 2 e e ␣å h t t p : // j a v a . sun

com/xml/ ns / j 2 e e /web−app 2 4 xsd "> <d i s p l a y −name> s s o l o g i n</ d i s p l a y −name> <welcome−f i l e − l i s t> <welcome− f i l e >s e c u r e d c o n t e n t / c o n t e n t . j s p</ welcome− f i l e > </ welcome−f i l e − l i s t> <s e c u r i t y −c o n s t r a i n t> <web−r e s o u r c e −c o l l e c t i o n> <web−r e s o u r c e −name>Content</web−r e s o u r c e −name> <u r l −p a t t e r n>/ s e c u r e d c o n t e n t /∗</ u r l −p a t t e r n> <http−method>GET</ http−method> <http−method>POST</ http−method> </web−r e s o u r c e −c o l l e c t i o n> <auth−c o n s t r a i n t> <r o l e −name>∗</ r o l e −name> </ auth−c o n s t r a i n t> <u s e r −data−c o n s t r a i n t> <t r a n s p o r t −g u a r a n t e e>NONE</ t r a n s p o r t −g

u a r a n t e e> </ u s e r −data−c o n s t r a i n t> </ s e c u r i t y −c o n s t r a i n t> <l o g i n −c o n f i g> <auth−method>CLIENT−CERT,FORM</ auth−method> <form−l o g i n −c o n f i g> <form−l o g i n −page>/ l o g i n . j s p</ form−l o g i n −page> <form−e r r o r −page>/ e r r o r . j s p</ form−e r r o r −page> </ form−l o g i n −c o n f i g> </ l o g i n −c o n f i g> <s e c u r i t y −r o l e> <r o l e −name>sso users or groups</ r o l e −name> </ s e c u r i t y −r o l e> </web−app> // 4 −2. p r o g r a m l i s t a : w e b l o g i c xml <?xml version=" 1 . 0 " e n c o d i n g="UTF−8" ?> <w l s : w e b l o g i c −web−app x m l n s : w l s=" h t t p : // xmlns . o r a c l e com/ w e b l o g i c / w e b l o g i c −web−app " x m l n s : x s i="å h t t p : //www.

w3 o r g /2001/XMLSchema−i n s t a n c e " x s i : s c h e m a L o c a t i o n=" h t t p : // j a v a sun com/xml/ ns /å j a v a e e ␣ h t t p : // j a v a . sun com/xml/ ns / j a v a e e /web−app 2 5 xsd ␣ h t t p : // xmlns o r a c l e com/ w e b l o g i c /å w e b l o g i c −web−app ␣ h t t p : // xmlns . o r a c l e com/ w e b l o g i c / w e b l o g i c −web−app / 1 0 / w e b l o g i c −web−app xsd "å > <w l s : w e b l o g i c −version>1 0 . 3 2</ w l s : w e b l o g i c −version> <w l s : c o n t e x t −r o o t> s s o l o g i n</ w l s : c o n t e x t −r o o t> <w l s : s e c u r i t y −r o l e −a s s i g n m e n t> <w l s : r o l e −name>sso users or groups</ w l s : r o l e −name> < !−− An AD group name s h o u l d come h e r e −−> <w l s : p r i n c i p a l −name>t e c h i c c m</ w l s : p r i n c i p a l −name> </ w l s : s e c u r

i t y −r o l e −a s s i g n m e n t> <w l s : c h a r s e t −params> <w l s : i n p u t −c h a r s e t> <w l s : r e s o u r c e −path>/∗</ w l s : r e s o u r c e −path> <w l s : j a v a −c h a r s e t −name>UTF−8</ w l s : j a v a −c h a r s e t −name> </ w l s : i n p u t −c h a r s e t> </ w l s : c h a r s e t −params> 67 Security 20 21 22 23 24 25 26 27 28 29 30 31 32 A Java biztonsági rendszere - Kerberos alapú SSO <w l s : s e s s i o n −d e s c r i p t o r> < w l s : p e r s i s t e n t −s t o r e −t y p e>async−r e p l i c a t e d −i f −c l u s t e r e d</ w l s : p e r s i s t e n t −s t o r e −t y p e> </ w l s : s e s s i o n −d e s c r i p t o r> <w l s : j s p −d e s c r i p t o r> <w l s : k e e p g e n e r a t e d>t r u e</ w l s : k e e p g e n e r a t e d> <w l s : p a g e −check−s e c o n d s>−1</ w l s

: p a g e −check−s e c o n d s> <w l s : p r e c o m p i l e> f a l s e</ w l s : p r e c o m p i l e> <w l s : e n c o d i n g>UTF−8</ w l s : e n c o d i n g> </ w l s : j s p −d e s c r i p t o r> </ w l s : w e b l o g i c −web−app> Ez a lap fog megjelenni hitelesítés után: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 // 4 −3. p r o g r a m l i s t a : c o n t e n t j s p < !DOCTYPE HTML PUBLIC "−//W3C/DTD␣HTML␣ 4 . 0 ␣ T r a n s i t i o n a l //EN"> <html> <head>< t i t l e>A Form l o g i n a u t h e n t i c a t i o n f a i l u r e o c c u r r e d</ head></ t i t l e> <body> <P>A t t r i b u t e s o f t h e c u r r e n t u s e r : <OL> <LI>User name: <%=r e q u e s t . g e t U s e r P r i n c i p a l ( )%> <LI>A u t h e n t i c a t i o n method:

<%=r e q u e s t . getAuthType ( )%> </OL> </P> <% i f ( r e q u e s t . g e t P a r a m e t e r ( " u r l " ) != n u l l && r e q u e s t g e t P a r a m e t e r ( " soap " ) != n u l l ) { try { hu . a l e r a n t spnego SpnegoSOAPConnection s o a p C o n n e c t i o n = new hu a l e r a n t spnego å SpnegoSOAPConnection ( ) ; j a v a x . xml soap MessageFactory msgFactory = j a v a x xml soap Me ssa ge Fac to ry n e w I n s t a n c e ( ) ; j a v a x . xml soap MimeHeaders mimeHeaders = new j a v a x xml soap MimeHeaders ( ) ; mimeHeaders . addHeader ( " Content−Type" , " t e x t /xml ; ␣ c h a r s e t=UTF−8" ) ; j a v a . i o ByteArrayInputStream stream = new j a v a i o ByteArrayInputStream ( r e q u e s t g e t P a r a m e t e r ( "å soap " ) . g e t B y t e s ( "UTF−8" ) ) ; j a v a x . xml soap SOAPMessage s o a p R e q u e s t = msgFactory c r e a t e M e s

s a g e ( mimeHeaders , stream ) ; j a v a x . xml soap SOAPMessage s oapResp ons e = s o a p C o n n e c t i o n c a l l ( soapRequest , r e q u e s t å getParameter ( " u r l " ) ) ; j a v a . i o ByteArrayOutputStream bos = new j a v a i o ByteArrayOutputStream ( ) ; soap Respons e . wr iteTo ( bos ) ; %> <p> Response from WIDOR: <br /> <p r e> <%=new S t r i n g ( bos . toByteArray ( ) )%> </ p r e> </p> <% } catch ( Exception e ) { %> <p> E r r o r o c c u r e d w h i l e a c c e s s i n g WIDOR: <br /> <p r e> <%=e . g e t M e s s a g e ( )%> See t h e s e r v e r l o g f i l e s </ p r e> </p> <% 68 for detailes ! Security 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 } A Java biztonsági rendszere - Kerberos alapú SSO e .

printStackTrace () ; } %> <p>C a l l WIDOR W e b S e r v i c e : <form method="POST" a c t i o n=" / s s o l o g i n / s e c u r e d c o n t e n t / c o n t e n t . j s p "> <i n p u t t y p e=" submit " v a l u e " Submit "> <br /> URL t o be c a l l e d : <i n p u t t y p e=" t e x t " s i z e=" 120 " name=" u r l " v a l u e= ’<%=r e q u e s t . g e t P a r a m e t e r ( " u r l " ) å %> ’ /> <br /> <t e x t a r e a rows=" 100 " c o l s=" 120 " name=" soap "> <%=r e q u e s t . g e t P a r a m e t e r ( " soap " )%> </ t e x t a r e a> </ form> </p> </ body> </ html> // 4−4. p r o g r a m l i s t a : package hu . a l e r a n t spnego ; public f i n a l c l a s s Base64 { private s t a t i c f i n a l S t r i n g ALPHABET =

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ; private Base64 ( ) { // d e f a u l t p r i v a t e } public s t a t i c S t r i n g encode ( f i n a l byte [ ] b y t e s ) { int length = bytes . length ; i f ( l e n g t h == 0 ) { return " " ; } final StringBuilder buffer = new S t r i n g B u i l d e r ( ( i n t ) Math . c e i l ( l e n g t h / 3d ) ∗ 4 ) ; f i n a l int remainder = le ngth % 3 ; l e n g t h −= r e m a i n d e r ; int block ; int idx = 0 ; while ( i d x < l e n g t h ) { b l o c k = ( ( b y t e s [ i d x ++] & 0 x f f ) << 1 6 ) | ( ( b y t e s [ i d x ++] & 0 x f f ) << 8 ) | ( b y t e s [ i d x ++] & 0 x f f ) ; b u f f e r . append (ALPHABET charAt ( b l o c k >>> 1 8 ) ) ; b u f f e r . append (ALPHABET charAt ( ( b l o c k >>> 1 2 ) & 0 x 3 f ) ) ; b u f f e r . append (ALPHABET charAt ( ( b l o c k >>> 6 ) & 0 x 3 f ) ) ; 69 Security 33 34 35 36 37 38

39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 } A Java biztonsági rendszere - Kerberos alapú SSO b u f f e r . append (ALPHABET charAt ( b l o c k & 0 x 3 f ) ) ; } i f ( r e m a i n d e r == 0 ) { return b u f f e r . t o S t r i n g ( ) ; } i f ( r e m a i n d e r == 1 ) { b l o c k = ( b y t e s [ i d x ] & 0 x f f ) << 4 ; b u f f e r . append (ALPHABET charAt ( b l o c k >>> 6 ) ) ; b u f f e r . append (ALPHABET charAt ( b l o c k & 0 x 3 f ) ) ; b u f f e r . append ( "==" ) ; return b u f f e r . t o S t r i n g ( ) ; } b l o c k = ( ( ( b y t e s [ i d x ++] & 0 x f f ) << 8 ) | ( ( b y t e s [ i d x ] ) & 0 x f f ) ) << 2 ; b u f f e r . append (ALPHABET charAt ( b l o c k >>> 1 2 ) ) ; b u f f e r . append (ALPHABET charAt ( ( b l o c k >>> 6 ) & 0 x 3 f ) ) ; b u f f e r .

append (ALPHABET charAt ( b l o c k & 0 x 3 f ) ) ; b u f f e r . append ( "=" ) ; return b u f f e r . t o S t r i n g ( ) ; public s t a t i c byte [ ] decode ( f i n a l S t r i n g s t r i n g ) { f i n a l int len gth = s t r i n g . le ngth ( ) ; i f ( l e n g t h == 0 ) { return new byte [ 0 ] ; } } } f i n a l i n t pad = ( s t r i n g . charAt ( l e n g t h − 2 ) == ’= ’ ) ? 2 : ( s t r i n g . charAt ( l e n g t h − 1 ) == ’= ’ ) ? 1 : 0 ; f i n a l i n t s i z e = l e n g t h ∗ 3 / 4 − pad ; byte [ ] b u f f e r = new byte [ s i z e ] ; int block ; int idx = 0 ; int index = 0 ; while ( i d x < l e n g t h ) { b l o c k = (ALPHABET. i nde xOf ( s t r i n g charAt ( i d x++)) & 0 x f f ) << 18 | (ALPHABET. i nde xOf ( s t r i n g charAt ( i d x++)) & 0 x f f ) << 12 | (ALPHABET. i nde xOf ( s t r i n g charAt ( i d x++)) & 0 x f f ) << 6 | (ALPHABET. i nde xOf ( s t r i n g charAt ( i d x++)) & 0 x f f

) ; b u f f e r [ i n d e x ++] = ( byte ) ( b l o c k >>> 1 6 ) ; i f ( index < s i z e ) { b u f f e r [ i n d e x ++] = ( byte ) ( ( b l o c k >>> 8 ) & 0 x f f ) ; } i f ( index < s i z e ) { b u f f e r [ i n d e x ++] = ( byte ) ( b l o c k & 0 x f f ) ; } } return b u f f e r ; // 4−5. p r o g r a m l i s t a : package hu . a l e r a n t spnego ; public c l a s s C o n s t a n t s { private C o n s t a n t s ( ) { // d e f a u l t p r i v a t e } public s t a t i c f i n a l S t r i n g BASIC HEADER = " B a s i c " ; public s t a t i c f i n a l S t r i n g NEGOTIATE HEADER = " N e g o t i a t e " ; public s t a t i c f i n a l S t r i n g AUTHN HEADER = "WWW −A u t h e n t i c a t e " ; 70 Security 16 17 18 19 A Java biztonsági rendszere - Kerberos alapú SSO public s t a t i c f i n a l S t r i n g AUTHZ HEADER = " A u t h o r i z a t i o n " ; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 // 4−6. p r o g r a m l i s t a : package hu . a l e r a n t spnego ; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 4−7. p r o g r a m l i s t a : f i n a l c l a s s SpnegoAuthScheme { private s t a t i c f i n a l transient byte [ ] EMPTY BYTE ARRAY = new byte [ 0 ] ; private f i n a l transient S t r i n g scheme ; private f i n a l transient S t r i n g t o k e n ; private f i n a l transient boolean b a s i c S c h e m e ; private f i n a l transient boolean n e g o t i a t e S c h e m e ; public SpnegoAuthScheme ( f i n a l S t r i n g authScheme , f i n a l S t r i n g authToken ) { t h i s . scheme = authScheme ; t h i s . t o k e n = authToken ; } t h i s . n e g o t i a t e S c h e m e = C o n s t a n t s NEGOTIATE HEADER e q u a l s I g n o r e C a s e ( authScheme ) ; t h i s . b a s i c S c h e m e = C o n s t a n t s BASIC HEADER e q u a l s I g n o r e C a s e ( authScheme ) ; boolean i s B a

s i c S c h e m e ( ) { return t h i s . b a s i c S c h e m e ; } boolean i s N e g o t i a t e S c h e m e ( ) { return t h i s . n e g o t i a t e S c h e m e ; } public S t r i n g getScheme ( ) { return t h i s . scheme ; } } public byte [ ] getToken ( ) { return ( n u l l == t h i s . t o k e n ) ? EMPTY BYTE ARRAY : Base64 d ec od e ( t h i s t o k e n ) ; } package hu . a l e r a n t spnego ; import import import import import import import import import import import import import import import j a v a . i o ByteArrayOutputStream ; j a v a . i o IOException ; j a v a . i o InputStream ; j a v a . i o OutputStream ; j a v a . n e t HttpURLConnection ; j a v a . n e t URL; java . s e c u r i t y PrivilegedActionException ; j a v a . u t i l Arrays ; j a v a . u t i l LinkedHashMap ; java . u t i l List ; j a v a . u t i l Map ; java . u t i l Set ; j a v a . u t i l c o n c u r r e n t l o c k s Lock ; j a v a . u t i l c o n c u r r e n t l o c k s

ReentrantLock ; java . u t i l I t e r a t o r ; 71 Security 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 A Java biztonsági rendszere - Kerberos alapú SSO import import import import import import org . org . org . org . org . org . ietf ietf ietf ietf ietf ietf . . . . . . jgss jgss jgss jgss jgss jgss . GSSContext ; . GSSCredential ; . GSSException ; . GSSManager ; . GSSName ; . Oid ; import import import import import weblogic . security com . bea s e c u r i t y sun . s e c u r i t y j g s s sun . s e c u r i t y j g s s sun . s e c u r i t y j g s s . Security ; u t i l s . negotiate CredentialObject ; . GSSCredentialImpl ; . GSSManagerImpl ; . s p i GSSCredentialSpi ; public f i n a l c l a s s SpnegoHttpURLConnection { private s t a t i c f i n a l Lock LOCK = new ReentrantLock ( ) ; private s t a t i c f i n a l byte [ ] EMPTY BYTE = new byte [ 0 ] ; private s t a t i c f i n a l

GSSManager MANAGER = GSSManager . g e t I n s t a n c e ( ) ; private transient boolean c o n n e c t e d = f a l s e ; private transient S t r i n g requestMethod = "GET" ; private f i n a l transient Map<S t r i n g , L i s t <S t r i n g >> r e q u e s t P r o p e r t i e s = new LinkedHashMap<S t r i n g , L i s t <S t r i n g >>() ; private transient G S S C r e d e n t i a l c r e d e n t i a l ; private transient boolean c n t x t E s t a b l i s h e d = f a l s e ; private transient HttpURLConnection conn = n u l l ; private transient boolean r e q C r e d D e l e g = f a l s e ; public SpnegoHttpURLConnection ( ) throws E x c e p t i o n { i f ( S e c u r i t y . g e t C u r r e n t S u b j e c t ( ) == n u l l ) { throw new E x c e p t i o n ( "No␣ v a l i d ␣ s u b j e c t ␣ i n ␣ t h e ␣ t h r e a d ␣ c o n t e x t . ␣The␣ u s e r ␣ i s ␣ not ␣å authenticated ! ") ; } 59 60 61 62 63 64 System . out p r i

n t l n ( " S e c u r i t y g e t C u r r e n t S u b j e c t ( ) g e t P r i v a t e C r e d e n t i a l s ( ) : ␣ " + Security . getCurrentSubject () getPrivateCredentials () ) ; for ( I t e r a t o r i t = S e c u r i t y . getCurrentSubject ( ) g e t P r i v a t e C r e d e n t i a l s ( ) i t e r a t o r ( ) ; i t å hasNext ( ) ; ) { Object o = i t . next ( ) ; System . out p r i n t l n ( " o g e t C l a s s ( ) getName ( ) : ␣ " + o g e t C l a s s ( ) getName ( ) ) ; i f ( o instanceof C r e d e n t i a l O b j e c t ) { System . out p r i n t l n ( " ( ( C r e d e n t i a l O b j e c t ) o ) g e t C r e d e n t i a l ( ) : ␣ " + ( ( C r e d e n t i a l O b j e c t ) å o) . getCredential () ) ; System . out p r i n t l n ( " ( ( C r e d e n t i a l O b j e c t ) o ) g e t D e l e g a t e d S u b ( ) : ␣ " + ( ( å CredentialObject ) o ) . getDelegatedSub ( ) ) ; this . c r e d e n t i a l = ( ( CredentialObject )

o ) getCredential ( ) ; // System . o u t p r i n t l n ( " ( ( C r e d e n t i a l O b j e c t ) o ) g e t D e l e g a t e d S u b ( ) å getPrivateCredentials () : " + (( CredentialObject )o) . getDelegatedSub () å getPrivateCredentials () ) ; // System . o u t p r i n t l n ( " ( ( C r e d e n t i a l O b j e c t ) o ) g e t D e l e g a t e d S u b ( ) å getPrivateCredentials () . s i z e () : " + (( CredentialObject )o) getDelegatedSub () å getPrivateCredentials () . size () ) ; // f o r ( I t e r a t o r i t 2 = ( ( C r e d e n t i a l O b j e c t ) o ) . g e t D e l e g a t e d S u b ( ) g e t P r i v a t e C r e d e n t i a l s å ( ) . i t e r a t o r ( ) ; i t 2 hasNext ( ) ; ) { // O b j e c t o2 = i t 2 . n e x t ( ) ; // System . o u t p r i n t l n (" o2 g e t C l a s s ( ) getName ( ) : " + o2 g e t C l a s s ( ) getName ( ) ) ; 65 66 67 68 69 70 71 72 73 74 75 72 Security A Java biztonsági rendszere - Kerberos

alapú SSO 76 // 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 t h i s . c r e d e n t i a l = new G S S C r e d e n t i a l I m p l ( ( GSSManagerImpl )MANAGER, (å G S S C r e d e n t i a l S p i ) o2 ) ; // } } } } i f ( t h i s . c r e d e n t i a l == n u l l ) { throw new E x c e p t i o n ( "No␣ G S S C r e d e n t i a l ␣ found ␣ f o r ␣ t h e ␣ c u r r e n t ␣ s u b j e c t . ␣The␣ s u b j e c t ␣å might ␣ not ␣ have ␣ been ␣ a u t h e n t i c a t e d ␣ with ␣SPNEGO␣ K e r b e r o s . " ) ; } private void a s s e r t C o n n e c t e d ( ) { i f ( ! this . connected ) { throw new I l l e g a l S t a t e E x c e p t i o n ( "Not␣ c o n n e c t e d . " ) ; } } private void a s s e r t N o t C o n n e c t e d ( ) { i f ( this . connected ) {

throw new I l l e g a l S t a t e E x c e p t i o n ( " A l r e a d y ␣ c o n n e c t e d . " ) ; } } public HttpURLConnection c o n n e c t ( f i n a l URL u r l ) throws GSSException , P r i v i l e g e d A c t i o n E x c e p t i o n , IOException { } return t h i s . c o n n e c t ( u r l , n u l l ) ; public HttpURLConnection c o n n e c t ( f i n a l URL u r l , f i n a l ByteArrayOutputStream dooutput ) throws GSSException , P r i v i l e g e d A c t i o n E x c e p t i o n , IOException { assertNotConnected ( ) ; GSSContext c o n t e x t = n u l l ; try { byte [ ] data = n u l l ; SpnegoHttpURLConnection .LOCK l o c k ( ) ; try { // work−around t o GSSContext /AD timestamp v s s e q u e n c e f i e l d r e p l a y bug try { Thread . s l e e p ( 3 1 ) ; } catch ( I n t e r r u p t e d E x c e p t i o n e ) { a s s e r t true ; } c o n t e x t = t h i s . getGSSContext ( u r l ) ; c o n t e x t . requestMutualAuth ( true ) ; c o n t e x t . r e q u e s t C o n f (

true ) ; c o n t e x t . r e q u e s t I n t e g ( true ) ; c o n t e x t . r e q u e s t R e p l a y D e t ( true ) ; c o n t e x t . r e q u e s t S e q u e n c e D e t ( true ) ; c o n t e x t . r e q u e s t C r e d D e l e g ( true ) ; data = c o n t e x t . i n i t S e c C o n t e x t (EMPTY BYTE, 0 , 0 ) ; } finally { SpnegoHttpURLConnection .LOCK u n l o c k ( ) ; } t h i s . conn = ( HttpURLConnection ) u r l openCo nnectio n ( ) ; t h i s . c o n n e c t e d = true ; f i n a l Set<S t r i n g > k e y s = t h i s . r e q u e s t P r o p e r t i e s k e y S e t ( ) ; f o r ( f i n a l S t r i n g key : k e y s ) { f o r ( S t r i n g v a l u e : t h i s . r e q u e s t P r o p e r t i e s g e t ( key ) ) { t h i s . conn a d d R e q u e s t P r o p e r t y ( key , v a l u e ) ; } 73 Security 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 A Java biztonsági rendszere - Kerberos alapú SSO } // TODO : re−f a c t o r t o s u p p o r t ( 3 0 2 ) r e d i r e c t s t h i s . conn s e t I n s t a n c e F o l l o w R e d i r e c t s ( f a l s e ) ; t h i s . conn setRequestMethod ( t h i s requestMethod ) ; t h i s . conn s e t R e q u e s t P r o p e r t y ( C o n s t a n t s AUTHZ HEADER , C o n s t a n t s .NEGOTIATE HEADER + ’ ␣ ’ + Base64 en co de ( data ) ) ; i f ( n u l l != dooutput && dooutput . s i z e ( ) > 0 ) { t h i s . conn setDoOutput ( true ) ; dooutput . wri teTo ( t h i s conn getOutputStream ( ) ) ; } t h i s . conn c o n n e c t ( ) ; f i n a l SpnegoAuthScheme scheme = getAuthScheme ( t h i s . conn g e t H e a d e r F i e l d ( C o n s t a n t s AUTHN HEADER) ) ; // app s e r v e r s w i l l n o t r e t u r n a WWW −A u t h e n t i c a t e on 302 , ( and 30 x . ? ) i f ( n u l l ==

scheme ) { System . out p r i n t l n ( " getAuthScheme ( ) ␣ r e t u r n e d ␣ n u l l " ) ; } else { data = scheme . getToken ( ) ; i f ( C o n s t a n t s .NEGOTIATE HEADER e q u a l s I g n o r e C a s e ( scheme getScheme ( ) ) ) { SpnegoHttpURLConnection .LOCK l o c k ( ) ; try { data = c o n t e x t . i n i t S e c C o n t e x t ( data , 0 , data l e n g t h ) ; } finally { SpnegoHttpURLConnection .LOCK u n l o c k ( ) ; } // TODO : s u p p o r t c o n t e x t l o o p s where i >1 i f ( n u l l != data ) { System . out p r i n t l n ( " S e r v e r ␣ r e q u e s t e d ␣ c o n t e x t ␣ l o o p : ␣ " + data l e n g t h ) ; } } else { throw new U n s u p p o r t e d O p e r a t i o n E x c e p t i o n ( " Scheme ␣NOT␣ Supported : ␣ " + scheme . getScheme ( ) ) ; } this . cntxtEstablished = context i s E s t a b l i s h e d ( ) ; } } finally { this . dispose ( context ) ; } } return t h i s . conn ; private void d i s

p o s e ( f i n a l GSSContext c o n t e x t ) { i f ( n u l l != c o n t e x t ) { try { SpnegoHttpURLConnection .LOCK l o c k ( ) ; try { context . dispose () ; } finally { SpnegoHttpURLConnection .LOCK u n l o c k ( ) ; } } catch ( GSSException g s s e ) { System . out p r i n t l n ( " c a l l ␣ t o ␣ d i s p o s e ␣ c o n t e x t ␣ f a i l e d " + g s s e ) ; gsse . printStackTrace () ; 74 Security 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 } A Java biztonsági rendszere - Kerberos alapú SSO } // i f ( n u l l != t h i s . c r e d e n t i a l ) { // try { // t h i s . credential dispose () ; // } c a t c h ( f i n a l GSSException g s s e ) { // System . o u t p r i n t l n (" c a l l t o d i s p o s e c r e d e n t i a l f a i l e

d " + g s s e ) ; // gsse . printStackTrace () ; // } // } } public void d i s c o n n e c t ( ) { this . d i s p o s e ( null ) ; this . r e q u e s t P r o p e r t i e s c l e a r ( ) ; this . connected = false ; i f ( n u l l != t h i s . conn ) { t h i s . conn d i s c o n n e c t ( ) ; } } public boolean i s C o n t e x t E s t a b l i s h e d ( ) { return t h i s . c n t x t E s t a b l i s h e d ; } private void a s s e r t K e y V a l u e ( f i n a l S t r i n g key , f i n a l S t r i n g v a l u e ) { i f ( n u l l == key | | key . isEmpty ( ) ) { throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " key ␣ p a r a m e t e r ␣ i s ␣ n u l l ␣ o r ␣empty" ) ; } i f ( n u l l == v a l u e ) { throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " v a l u e ␣ p a r a m e t e r ␣ i s ␣ n u l l " ) ; } } public void a d d R e q u e s t P r o p e r t y ( f i n a l S t r i n g key , f i n a l S t r i n g v a l u e ) {

assertNotConnected ( ) ; a s s e r t K e y V a l u e ( key , v a l u e ) ; } i f ( t h i s . r e q u e s t P r o p e r t i e s c o n t a i n s K e y ( key ) ) { f i n a l L i s t <S t r i n g > v a l = t h i s . r e q u e s t P r o p e r t i e s g e t ( key ) ; v a l . add ( v a l u e ) ; t h i s . r e q u e s t P r o p e r t i e s put ( key , v a l ) ; } else { s e t R e q u e s t P r o p e r t y ( key , v a l u e ) ; } public void s e t R e q u e s t P r o p e r t y ( f i n a l S t r i n g key , f i n a l S t r i n g v a l u e ) { assertNotConnected ( ) ; a s s e r t K e y V a l u e ( key , v a l u e ) ; } t h i s . r e q u e s t P r o p e r t i e s put ( key , Arrays a s L i s t ( v a l u e ) ) ; private GSSContext getGSSContext ( f i n a l URL u r l ) throws GSSException , PrivilegedActionException { } return getGSSContext ( t h i s . c r e d e n t i a l , u r l ) ; public InputStream g e t E r r o r S t r e a m ( ) throws IOException { assertConnected () ; 75

Security 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 } A Java biztonsági rendszere - Kerberos alapú SSO return t h i s . conn g e t I n p u t S t r e a m ( ) ; public S t r i n g g e t H e a d e r F i e l d ( f i n a l i n t i n d e x ) { assertConnected () ; } return t h i s . conn g e t H e a d e r F i e l d ( i n d e x ) ; public S t r i n g g e t H e a d e r F i e l d ( f i n a l S t r i n g name ) { assertConnected () ; } return t h i s . conn g e t H e a d e r F i e l d ( name ) ; public S t r i n g g e t H e a d e r F i e l d K e y ( f i n a l i n t i n d e x ) { assertConnected () ; } return t h i s . conn g e t H e a d e r F i e l d K e y ( i n d e x ) ; public InputStream g e t I n p u t S t r e a m ( ) throws IOException { assertConnected () ; }

return t h i s . conn g e t I n p u t S t r e a m ( ) ; public OutputStream getOutputStream ( ) throws IOException { assertConnected () ; } return t h i s . conn getOutputStream ( ) ; public i n t getResponseCode ( ) throws IOException { assertConnected () ; } return t h i s . conn getResponseCode ( ) ; public S t r i n g g e t R e s p o n s e M e s s a g e ( ) throws IOException { assertConnected () ; } return t h i s . conn g e t R e s p o n s e M e s s a g e ( ) ; public void r e q u e s t C r e d D e l e g ( f i n a l boolean r e q u e s t D e l e g a t i o n ) { this . assertNotConnected ( ) ; } this . reqCredDeleg = r e q u e s t D e l e g a t i o n ; public void setRequestMethod ( f i n a l S t r i n g method ) { assertNotConnected ( ) ; } t h i s . requestMethod = method ; public SpnegoAuthScheme getAuthScheme ( f i n a l S t r i n g h e a d e r ) { i f ( n u l l == h e a d e r | | h e a d e r . isEmpty ( ) ) { System . out p r i n t l n ( " a u t h o r i z

a t i o n ␣ h e a d e r ␣ was ␣ m i s s i n g / n u l l " ) ; return n u l l ; } e l s e i f ( h e a d e r . s t a r t s W i t h ( C o n s t a n t s NEGOTIATE HEADER) ) { f i n a l S t r i n g t o k e n = h e a d e r . s u b s t r i n g ( C o n s t a n t s NEGOTIATE HEADER l e n g t h ( ) + 1 ) ; 76 Security 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 1 2 3 4 5 6 7 8 9 A Java biztonsági rendszere - Kerberos alapú SSO return new SpnegoAuthScheme ( C o n s t a n t s .NEGOTIATE HEADER, t o k e n ) ; } e l s e i f ( h e a d e r . s t a r t s W i t h ( C o n s t a n t s BASIC HEADER) ) { f i n a l S t r i n g t o k e n = h e a d e r . s u b s t r i n g ( C o n s t a n t s BASIC HEADER l e n g t h ( ) + 1 ) ; return new SpnegoAuthScheme ( C o n s t a n t s .BASIC HEADER, t o k e n ) ; } // // // }

else { throw new U n s u p p o r t e d O p e r a t i o n E x c e p t i o n ( " N e g o t i a t e ␣ o r ␣ B a s i c ␣ Only : " + h e a d e r ) ; } private GSSName getServerName ( f i n a l URL r e t u r n MANAGER. createName ("HTTP@" GSSName .NT HOSTBASED SERVICE, return MANAGER. createName ( "HTTP/ " } u r l ) throws GSSException { + url . getHost () , getOid () ) ; + u r l . g e t H o s t ( ) , GSSName NT USER NAME) ; f i n a l GSSContext c l i e n t C o n t e x t = dmanager . c r e a t e C o n t e x t ( gssServerName å c a n o n i c a l i z e ( spnegoMechOid ) , spnegoMechOid , g c r e d , GSSContext .DEFAULT LIFETIME) ; private Oid getOid ( ) { Oid o i d = n u l l ; try { o i d = new Oid ( " 1 . 3 6 1 5 5 2 " ) ; // o i d = new Oid ( " 1 . 2 8 4 0 1 1 3 5 5 4 1 2 2 " ) ; } catch ( GSSException g s s e ) { System . out p r i n t l n ( " Unable ␣ t o ␣ c r e a t e ␣OID␣ 1 3 6 1 5 5 2 ␣ !

" + g s s e ) ; gsse . printStackTrace () ; //LOGGER. l o g ( L e v e l SEVERE, " Unable t o c r e a t e OID 1 2 8 4 0 1 1 3 5 5 4 1 2 2 ! " , g s s e ) ; } return o i d ; } public GSSContext getGSSContext ( f i n a l G S S C r e d e n t i a l c r e d s , f i n a l URL u r l ) throws GSSException { } return MANAGER. c r e a t e C o n t e x t ( getServerName ( u r l ) c a n o n i c a l i z e ( g et Oi d ( ) ) , getOid ( ) , creds , GSSContext . DEFAULT LIFETIME) ; public GSSContext getGSSContextWithDefaultOID ( f i n a l G S S C r e d e n t i a l c r e d s , f i n a l URL u r l ) throws GSSException { } return MANAGER. c r e a t e C o n t e x t ( getServerName ( u r l ) c a n o n i c a l i z e ( n u l l ) , null , creds , GSSContext . DEFAULT LIFETIME) ; } // 4−8. p r o g r a m l i s t a : package hu . a l e r a n t spnego ; import import import import import j a v a . i o ByteArrayOutputStream ; j a v a . i o IOException ; j a v a . n e t

MalformedURLException ; j a v a . n e t URL; java . s e c u r i t y PrivilegedActionException ; 77 Security 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 import import import import import import import A Java biztonsági rendszere - Kerberos alapú SSO j a v a x . s e c u r i t y auth l o g i n L o g i n E x c e p t i o n ; j a v a x . xml soap MessageFactory ; j a v a x . xml soap MimeHeaders ; j a v a x . xml soap SOAPConnection ; j a v a x . xml soap SOAPConstants ; j a v a x . xml soap SOAPException ; j a v a x . xml soap SOAPMessage ; import o r g . i e t f j g s s G S S C r e d e n t i a l ; import o r g . i e t f j g s s GSSException ; public c l a s s SpnegoSOAPConnection extends SOAPConnection { private f i n a l transient SpnegoHttpURLConnection conn ; public SpnegoSOAPConnection ( ) throws E x c e p

t i o n { super ( ) ; t h i s . conn = new SpnegoHttpURLConnection ( ) ; } @Override public f i n a l SOAPMessage c a l l ( f i n a l SOAPMessage r e q u e s t , f i n a l O b j e c t e n d p o i n t ) throws SOAPException { SOAPMessage message = n u l l ; f i n a l ByteArrayOutputStream bos = new ByteArrayOutputStream ( ) ; try { f i n a l MimeHeaders h e a d e r s = r e q u e s t . getMimeHeaders ( ) ; f i n a l S t r i n g [ ] contentType = h e a d e r s . g e t H e a d e r ( " Content−Type" ) ; f i n a l S t r i n g [ ] s o a p A c t i o n = h e a d e r s . g e t H e a d e r ( "SOAPAction" ) ; // b u i l d t h e Content−Type HTTP h e a d e r parameter i f n o t d e f i n e d i f ( n u l l == contentType ) { f i n a l S t r i n g B u i l d e r h e a d e r = new S t r i n g B u i l d e r ( ) ; i f ( n u l l == s o a p A c t i o n ) { h e a d e r . append ( " a p p l i c a t i o n / soap+xml ; ␣ c h a r s e t=UTF−8; " ) ; } else { h e a d e r .

append ( " t e x t /xml ; ␣ c h a r s e t=UTF−8; " ) ; } // n o t d e f i n e d as a MIME h e a d e r b u t we need i t as an HTTP h e a d e r parameter t h i s . conn a d d R e q u e s t P r o p e r t y ( " Content−Type" , h e a d e r t o S t r i n g ( ) ) ; } else { i f ( contentType . l e n g t h > 1 ) { throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " Content−Type␣ d e f i n e d ␣ more ␣ than ␣ once . " ) ; } } // u s e r s p e c i f i e d as a MIME h e a d e r so add i t as an HTTP h e a d e r parameter t h i s . conn a d d R e q u e s t P r o p e r t y ( " Content−Type" , contentType [ 0 ] ) ; // s p e c i f y SOAPAction as an HTTP h e a d e r parameter i f ( n u l l != s o a p A c t i o n ) { i f ( soapAction . length > 1) { throw new I l l e g a l A r g u m e n t E x c e p t i o n ( "SOAPAction␣ d e f i n e d ␣ more ␣ than ␣ once . " ) ; } t h i s . conn a d d R e q u e s t P r

o p e r t y ( "SOAPAction" , s o a p A c t i o n [ 0 ] ) ; } r e q u e s t . wr iteTo ( bos ) ; t h i s . conn c o n n e c t (new URL( e n d p o i n t t o S t r i n g ( ) ) , bos ) ; 78 Security 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 A Java biztonsági rendszere - Kerberos alapú SSO f i n a l MessageFactory f a c t o r y = Me ssa ge Fac to ry . n e w I n s t a n c e ( SOAPConstants .SOAP 1 2 PROTOCOL) ; try { message = f a c t o r y . c r e a t e M e s s a g e ( null , t h i s conn g e t I n p u t S t r e a m ( ) ) ; } catch ( IOException e ) { message = f a c t o r y . c r e a t e M e s s a g e ( null , t h i s conn g e t E r r o r S t r e a m ( ) ) ; } } catch ( MalformedURLException e ) { throw new SOAPException ( e ) ; } catch ( IOException e ) { throw new SOAPException ( e ) ; } catch ( GSSException e ) { throw new SOAPException ( e ) ; } catch ( P r i v i l e g e d A c t i

o n E x c e p t i o n e ) { throw new SOAPException ( e ) ; } finally { try { bos . c l o s e ( ) ; } catch ( IOException i o e ) { a s s e r t true ; } this . c l o s e ( ) ; } } } return message ; @Override public f i n a l void c l o s e ( ) { i f ( n u l l != t h i s . conn ) { t h i s . conn d i s c o n n e c t ( ) ; } } Amennyiben a Kerberos alapú hitelesítés nem sikerült, úgy a web.xml deklarációja szerint FORM alapú autentikációra teszünk kísérletet a login.jsp lap használatával: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 4 −9. p r o g r a m l i s t a : l o g i n j s p < !DOCTYPE HTML PUBLIC "−//W3C/DTD␣HTML␣ 4 . 0 ␣ T r a n s i t i o n a l //EN"> <html> <META HTTP−EQUIV = "Pragma" CONTENT="no−c a c h e "> < t i t l e> Login Page </ t i t l e> <body> <h2>Form Login</h2> <form method=POST action=" j s e c u r i t y c h e c k ">

<p> <font s i z e=" 2 "> <strong> Enter u s e r ID and password : </ strong></ font> <BR> <strong> User ID</ strong> <input type=" t e x t " s i z e=" 20 " name=" j username "> <strong> Password </ strong> <input type=" password " s i z e=" 20 " name=" j password "> <BR> <BR> <font s i z e=" 2 "> <strong> And then c l i c k t h i s b ut to n : </ strong></ font> <input type=" submit " name=" l o g i n " value=" Login "> </p> </form> </body> </html> 79 Security 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 A Java biztonsági rendszere - Kerberos alapú SSO // 4 −10. p r o g r a m l i s t a : e r r o r j s p < !DOCTYPE HTML PUBLIC "−//W3C/DTD␣HTML␣ 4 . 0 ␣ T r a n s i t i o n a l //EN"> <html>

<head>< t i t l e>A Form l o g i n a u t h e n t i c a t i o n f a i l u r e o c c u r r e d</head></ t i t l e> <body> <H1><B>A Form l o g i n a u t h e n t i c a t i o n f a i l u r e o c c u r r e d</H1></B> <P>A u t h e n t i c a t i o n might f a i l f o r one o f many r e a s o n s . Some p o s s i b i l i t i e s i n c l u d e : <OL> <LI>The u s e r ID o r password might have been e n t e r e d i n c o r r e c t l y ; e i t h e r m i s s p e l l e d o r t h e wrong c a s e was used . <LI>The u s e r ID o r password d o e s not e x i s t , has e x p i r e d , o r has been d i s a b l e d . </OL> </P> <%new E x c e p t i o n ( ) . p r i n t S t a c k T r a c e ( ) ;%>E x c e p t i o n </body> </html> Összefoglalás és Irodalomjegyzék Ebben a cikkben az AD-t tartalmazó Windows szerver környezetet, Weblogic szervert és a Kerberost használtuk együtt. A cél az volt,

hogy az elterjedten használt Windows Integrated hitelesítést (amikor a Windows desktoppal bejelentkeztünk egy Windows domain-be) használhassuk. Erre sokáig az NTLM-et használtuk, de manapság inkább a Kerberos használata javasolt. Remélhetőleg ez az összefoglaló segít bennünket elindulni ezen az úton Segítségül szeretnénk megosztani néhány további érdekes olvasnivalót • Configuring JBoss for Windows Integrated Authentication: http://spnego. sourceforge.net/spnego jbosshtml • SPNEGO SSO administrators guide for JBoss • Integrated Windows Authentication in Java: http://spnego.sourceforge net/ dows: http://tools.ietforg/html/ rfc4559 • HTTP-Based Cross-Platform Authentication by Using the Negotiate Protocol: http://msdn.microsoftcom/ en-us/library/ms995330.aspx • Apache Kerberos támogatás: http:// modauthkerb.sourceforgenet/ • Java Generic Security Services (Java GSS) and Kerberos: http: //docs.oraclecom/javase/6/docs/

technotes/guides/security/jgss/ jgss-features.html • EJB3 Authentication With SPNEGO: https://community.jbossorg/wiki/ EJB3AuthenticationWithSPNEGO * Befejezésül szeretnénk megköszönni Darmai Gábor (Alerant Informatikai Zrt. - technológiai • SPNEGO-based Kerberos and NTLM igazgató) támogatását, aki nélkül ez a cikk nem HTTP Authentication in Microsoft Win- készülhetett volna el. 80 Security 5. A SPNEGO SSO beállítása JBoss környezetben A SPNEGO SSO beállítása JBoss környezetben A előző cikkben az Oracle Weblogic környezetre tekintettük át a Windows Integrated SPNEGO SSO használatát, ott sok mindent megtanultunk. Most a népszerű JBoss alkalmazásszerverre tekintjük át ugyanezt, hiszen ez egy komoly és rendkívül megbízható alternatívája a fizetős JEE környezeteknek. különben az automatikus SPNEGO hitelesítés nem tud működni. Erről az adott Windows gépen könnyen meggyőzödhetünk, ha megnyitjuk a System Properties ablakot

(StartSettingsControl Panel), ahol 2 fontos információt találunk: 1. A számítógép teljes neve (FQDN, példa: gepem.ceghu) és 2. A domain neve 5.1 ábra JBoss, AD, Kerberos és SPNEGO Az 5.1 ábra a már ismerős Kerberos kommunikációs sémát mutatja, ezért induljunk ki ismét ebből az alapműködésből és nézzük meg röviden, hogy a JBoss szerveren milyen beállítási lépések szükségesek. Tekintsünk egy ilyen urlt: http://jbserverceghu Ekkor gépünk a ceg.hu domainban van, a gép neve pedig jbserver (az jboss szerver), amire a kerberos beállításokat el fogjuk végezni. http://sourceforge.net/projects/ spnego/files/ Az előfeltételek áttekintése Lényeges az is, hogy a gépet éppen használó user a domain-ba legyen bejelentkezve. Windows környezet és domain account Erről a Weblogicról szóló cikk Windows Server oldali konfiguráció részében részletesen írtunk, az ott olvashatóak természetesen itt is érvényesek. Esetünkben a Windows domain

adminisztrátorral közösen elkészített SPN ilyen alakú lesz: h t t p / j b s e r v e r . c e g hu@CEGHU s e t s p n . e x e −a HTTP/ j b s e r v e r c e g hu@CEGHU å jbossuser setspn . exe −l j b o s s u s e r k t p a s s −p r i n c HTTP/ j b s e r v e r . c e g hu@CEGHU −å p a s s ∗ −mapuser CEG j b o s s u s e r −out c : j b o s s u s e r . h t t p k ey ta b ktab −k c : j b o s s u s e r . h t t p ke yt ab −a å Az előző cikkből sok mindent megtanultunk, de jbossuser@CEG .HU ennek ellenére egy gyors ellenőrző lista hasznos lehet. A Firefox beállítása Az előző írásban az IE beállítása szerepelt, itt most emiatt csak a Firefox szükséges beállítását A böngészőt futtató munkaállomásnak egy adjuk meg. Az ismert about:config URL segítsémegfelelő Windows domain-ban kell lennie, gével a következő 2 beállítás szükséges: A kliens gépek ellenőrzése 81 Security • network.negotiate-authtrusted-uris http://,https://

A SPNEGO SSO beállítása JBoss környezetben sorokat kell elhelyezni: • network.negotiate-authdelegation-uris http://,https:// A spnego.jar telepítése A Weblogic 11g gyárilag tartalmazott minden szükséges komponenst a SPNEGO Kerberos kialakításához, azonban JBOSS esetén töltsük le a spnego.jar file-t innen: http://sourceforge net/projects/spnego/files/ (itt a cikk írásakor a spnego-r7.jar és a spnego-r5jar volt elérhető) Mi az r7 változatot használtuk, amit ide kell másolni, mert mi a default domaint használjuk: JBOSS HOME/server/default/lib. A krb5.conf file beállítása . <a p p l i c a t i o n −p o l i c y name=" spnego−c l i e n t "> <a u t h e n t i c a t i o n> <l o g i n −module code="com . sun s e c u r i t y å auth . module Krb5LoginModule " f l a g=" r e q u i r e d " /> </ a u t h e n t i c a t i o n> </ a p p l i c a t i o n −p o l i c y> . <a p p l i c a t i o n −p

o l i c y name=" spnego−s e r v e r "> <a u t h e n t i c a t i o n> <l o g i n −module code="com . sun s e c u r i t y å auth . module Krb5LoginModule " f l a g=" r e q u i r e d "> <module−o p t i o n name=" s t o r e K e y ">t r u e<å / module−o p t i o n> </ l o g i n −module> </ a u t h e n t i c a t i o n> </ a p p l i c a t i o n −p o l i c y> Tesztelés Állítsuk le a JBOSS szervert! A kerbe- A tesztelést ezzek a kis jsp lappal végezhetjük ros krb5.conf beállításának lépései megegyez- el, ami kiírja a SPNEGO token alapján elérhető nek a Weblogic-nál írtakkal, azonban azt most hitelesített felhasználó nevét. a JBOSS HOME/bin könyvtárba kell elhelyez<html> nünk. Fontos, hogy a JRE képes legyen ezt a <head> < t i t l e>H e l l o SPNEGO Example</ t i t l e> file-t megtalálni. A login-config.xml file beállítása A JBOSS

HOME/server/default/conf könyvtárban lévő login-config.xml file-ba a következő </ head> <body> H e l l o <%= r e q u e s t . getRemoteUser ( ) %> ! </ body> </ html> A használt web.xml file: // web . xml < f i l t e r> < f i l t e r −name>S p n e g o H t t p F i l t e r</ f i l t e r −name> < f i l t e r −c l a s s>n e t . s o u r c e f o r g e spnego S p n e g o H t t p F i l t e r</ f i l t e r −c l a s s> <i n i t −param> <param−name>spnego . a l l o w b a s i c</param−name> <param−v a l u e>t r u e</param−v a l u e> </ i n i t −param> <i n i t −param> <param−name>spnego . a l l o w l o c a l h o s t</param−name> <param−v a l u e>t r u e</param−v a l u e> </ i n i t −param> <i n i t −param> 82 Security A SPNEGO SSO beállítása JBoss környezetben <param−name>spnego . a l l o w u n s

e c u r e b a s i c</param−name> <param−v a l u e>t r u e</param−v a l u e> </ i n i t −param> <i n i t −param> <param−name>spnego . l o g i n c l i e n t module</param−name> <param−v a l u e>spnego−c l i e n t</param−v a l u e> </ i n i t −param> <i n i t −param> <param−name>spnego . krb5 c o n f</param−name> <param−v a l u e>krb5 . c o n f</param−v a l u e> </ i n i t −param> <i n i t −param> <param−name>spnego . l o g i n c o n f</param−name> <param−v a l u e>l o g i n . c o n f</param−v a l u e> </ i n i t −param> <i n i t −param> <param−name>spnego . p r e a u t h username</param−name> <param−v a l u e>Zeus</param−v a l u e> </ i n i t −param> <i n i t −param> <param−name>spnego . p r e a u t h password</param−name>

<param−v a l u e>Z3usP@55</param−v a l u e> </ i n i t −param> <i n i t −param> <param−name>spnego . l o g i n s e r v e r module</param−name> <param−v a l u e>spnego−s e r v e r</param−v a l u e> </ i n i t −param> <i n i t −param> <param−name>spnego . prompt ntlm</param−name> <param−v a l u e>t r u e</param−v a l u e> </ i n i t −param> <i n i t −param> <param−name>spnego . l o g g e r l e v e l</param−name> <param−v a l u e>1</param−v a l u e> </ i n i t −param> </ f i l t e r> < f i l t e r −mapping> < f i l t e r −name>S p n e g o H t t p F i l t e r</ f i l t e r −name> <u r l −p a t t e r n>∗ . j s p</ u r l −p a t t e r n> </ f i l t e r −mapping> Amennyiben valaki a gyakorlatban is vé- ezt a webhelyet: http://spnego.sourceforge gig szeretné

próbálni a leírtakat, tanulmányozza net/spnego jboss.html 83 Bonita Tippek és Trükkök 6. A JBOSS – Bonita páros produktív használata A JBOSS – Bonita páros produktív használata Ez a cikk röviden ismerteti a Bonita BPM motor JBOSS Java szerver környezetre való konfigurációjának lépéseit. Ez nagyrészt az adatbázis perzisztencia réteg beállítását jelenti, ezért itt most ezt mutatjuk be Oracle adatbázis-kezelőt használva. Konfigurálás Oracle adatbázishoz A Bonita környezettel előre összecsomagolt letölthető JBOSS verziót (jelen esetben 5.10 GA, letölthető innen: http://www.bonitasoft com/products/download/jboss-5.10-9) egy server.companyorg (továbbiakban: server ) nevű gép /opt/BOS-5.52-JBoss-510GA könyvtárába (továbbiakban erre jboss home néven fogunk hivatkozni) csomagoltuk ki és a JBOSS default domain-ját használjuk. Feltételezzük, hogy a Java 16 már működik az operációs rendszeren A JBOSS működőképességét

próbáljuk ki úgy, hogy elindítjuk (A -b 0.000 azt jelenti, hogy más számítógépekről is elérhető lesz az alkalmazásszerver, ugyanis nem ez az alapértelmezés): ./< jboss home>/run sh −b 0 0 0 0 & remove hsqldb −ds . xml from <jboss home >/ s e r v e r å /<domain>/d e p l o y / remove hsqldb −p e r s i s t e n c e −s e r v i c e . xml from <å jboss home >/ s e r v e r /<domain>/d e p l o y /å messaging / Az Oracle használata Több Oracle thin driver létezik, de ebben a leírásban az ojdbc6.jar használata mellett döntöttünk, amit a http://www.oraclecom/ technetwork/indexes/downloads/index. html helyről lehet hivatalosan letölteni. Ezután ezt a 3 lépést kell végrehajtani: ( 1 ) copy o j d b c 6 . j a r t o <jboss home >/ s e r v e r /<å domain>/ l i b / ( 2 ) c r e a t e J B o s s s d a t a s o u r c e f i l e ( o r a c l e −ds . å xml ) ( 3 ) copy o r a c l e −ds . xml t o <jboss home >/

s e r v e r å /<domain>/d e p l o y / Ha szükséges az éppen használt Oracle verzió A JBOSS szerver leállítása (a -s után a JNDI gyors lekérdezése, akkor ezt az ismert Naming Service-re hivatkozó URL van): s e l e c t ∗ from v $ v e r s i o n ./< jboss home>/shutdown sh −s jnp : / / å localh ost :1099 A következő feladat a JBOSS átkapcsolása Oracle adatbázisra, aminek lépéseit mutatjuk meg az alábbiakban. A beépített HSQDB visszamozgatása A következő 4 művelet kitörli a hsqdb-vel kapcsolatos file-okat a megfelelő JBOSS könyvtárakból: remove h s q l d b . j a r from <jboss home >/common/ l i b å / remove hsqldb −p l u g i n . j a r from <jboss home >/å common/ l i b / 84 paranccsal tehetjük meg. A fent hivatkozott oracle-ds.xml az ismert datasource létrehozási módszer szerinti tartalommal rendelkező XML, amit a 6-1. Programlista mutat A oraservercompanyorg az adatbázis szerver neve, a test pedig az adatbázis.

Amikor az XML-t telepítjük az JBOSS szerverre, esetünkben létrejön egy DefaultDS nevű JEE Datasource. Ez az a JNDI név, amit a programozó is használ, illetve a konfigurációkban is kulcszerepe van, hiszen ezen keresztül tudunk adatbázis connection-okat szerezni. Bonita Tippek és Trükkök A JBOSS – Bonita páros produktív használata // 6 −1. P r o g r a m l i s t a <?xml v e r s i o n ="1.0" e n c o d i n g="UTF−8"?> <d a t a s o u r c e s > <l o c a l −tx−d a t a s o u r c e > <!−− The j n d i name o f t h e DataSource , i t <j n d i −name>DefaultDS </j n d i −name> i s p r e f i x e d with j a v a : / −−> <c o n n e c t i o n −u r l >j d b c : o r a c l e : t h i n : @ o r a s e r v e r . company o r g : 1 5 2 1 : t e s t </c o n n e c t i o n −u r l > <!−− The d r i v e r c l a s s −−> <d r i v e r −c l a s s >o r a c l e . j d b c d r i v e r

O r a c l e D r i v e r </ d r i v e r −c l a s s > <!−− The l o g i n and password −−> <u s e r −name>b o n i t a h i s t o r y </u s e r −name> <password>BoHis 1315</password> <e x c e p t i o n −s o r t e r −c l a s s −name>o r g . j b o s s r e s o u r c e a d a p t e r j d b c vendor O r a c l e E x c e p t i o n S o r t e r </å e x c e p t i o n −s o r t e r −c l a s s −name> <!−− t h i s w i l l be run b e f o r e a managed c o n n e c t i o n i s removed from t h e p o o l f o r u s e by aå c l i e n t −−> <check−v a l i d −c o n n e c t i o n −s q l >s e l e c t 1 from dual </check−v a l i d −c o n n e c t i o n −s q l > <!−− The minimum c o n n e c t i o n s i n a p o o l / sub−p o o l . P o o l s a r e l a z i l y c o n s t r u c t e d on f i r s t u s e å −−> <min−po ol −s i z e >5</min−po ol −s i z e > <!−− The

maximum c o n n e c t i o n s i n a p o o l / sub−p o o l −−> <max−po ol −s i z e >20</max−po ol −s i z e > <!−− The time b e f o r e an unused c o n n e c t i o n i s d e s t r o y e d −−> <!−− NOTE: This i s t h e c h e c k p e r i o d . With <i d l e −timeout−minutes> you can i n d i c a t e t h e å maximum time a c o n n e c t i o n may be i d l e b e f o r e b e i n g c l o s e d and r e t u r n e d t o t h e p o o l . I f not s p e c i f i e d i t ’ s 15 å minutes . −−> <i d l e −timeout−minutes >5</ i d l e −timeout−minutes> <!−− s q l t o c a l l when c o n n e c t i o n i s c r e a t e d −−> <new−c o n n e c t i o n −s q l >s e l e c t 1 from dual </new−c o n n e c t i o n −s q l > <!−− s q l t o c a l l on an e x i s t i n g p o o l e d c o n n e c t i o n when i t i s o b t a i n e d from p o o l −−> <check−v a l i d −c o n n e c t

i o n −s q l >s e l e c t 1 from dual </check−v a l i d −c o n n e c t i o n −s q l > <!−− example o f how t o s p e c i f y a c l a s s t h a t d e t e r m i n e s a c o n n e c t i o n i s v a l i d b e f o r e i t handed out from t h e p o o l <v a l i d −c o n n e c t i o n −c h e c k e r −c l a s s −name>o r g . j b o s s r e s o u r c e a d a p t e r j d b c vendor å DummyValidConnectionChecker </ v a l i d −c o n n e c t i o n −c h e c k e r −c l a s s −name> −−> is å <!−− Whether t o c h e c k a l l s t a t e m e n t s a r e c l o s e d when t h e c o n n e c t i o n i s r e t u r n e d t o t h e å po ol , t h i s i s a d e b u g g i n g f e a t u r e t h a t s h o u l d be t u r n e d o f f i n p r o d u c t i o n −−> <t r a c k −s t a t e m e n t s >f a l s e </t r a c k −s t a t e m e n t s > <!−− Use t h e g e t C o n n e c t i o n ( u s e r , pw) f o r l o g i n s

<a p p l i c a t i o n −managed−s e c u r i t y /> −−> <!−− Use t h e s e c u r i t y domain d e f i n e d i n c o n f / l o g i n −c o n f i g . xml −−> <s e c u r i t y −domain>OracleDbRealm</ s e c u r i t y −domain> <!−− Use t h e s e c u r i t y domain d e f i n e d i n c o n f / l o g i n −c o n f i g . xml o r t h e 85 Bonita Tippek és Trükkök A JBOSS – Bonita páros produktív használata g e t C o n n e c t i o n ( u s e r , pw) f o r l o g i n s . The s e c u r i t y domain t a k e s p r e c e d e n c e −−> <s e c u r i t y −domain−and−a p p l i c a t i o n >OracleDbRealm</ s e c u r i t y −domain−and−a p p l i c a t i o n > <!−− This e l e m e n t s p e c i f i e s t h e number o f p r e p a r e d s t a t e m e n t s p e r c o n n e c t i o n i n an LRU å cache , which i s keyed by t h e SQL query . S e t t i n g t h i s t o z e r o d i s a b l e s t h e c a c h e

−−> <p r e p a r e d −s t a t e m e n t −cache−s i z e >32</p r e p a r e d −s t a t e m e n t −cache−s i z e > <metadata> <type−mapping>O r a c l e 9 i </type−mapping> </metadata> </ l o c a l −tx−d a t a s o u r c e > </ d a t a s o u r c e s > A login-config.xml beállítása A perzisztencia beállítása A JBOSS login beállítás a A JBOSS több mintafájlt tartalmaz, mi innen vegyük el a következőt: <jboss home >/ s e r v e r /<domain>/c o n f / l o g i n −c o n f i g å . xml file-ban található. Az itt lévő application policy neve alapértelmezésben a HsqlDbRealm, amit át kell állítani OracleDbRealm-re, a 6-2. Programlistán mutatott módon <jboss home >d o c s examples jms o r a c l e −å p e r s i s t e n c e −s e r v i c e . xml és másoljuk ide: <jboss home >/ s e r v e r /<domain>/d e p l o y / m e s s a g i n g / // 6 −2. P r o g r a m l

i s t a <a p p l i c a t i o n −p o l i c y name="OracleDbRealm"> <a u t h e n t i c a t i o n > <l o g i n −module code="o r g . j b o s s r e s o u r c e s e c u r i t y C o n f i g u r e d I d e n t i t y L o g i n M o d u l e " å f l a g =" r e q u i r e d "> <module−o p t i o n name=" p r i n c i p a l "> t e s t </module−o p t i o n > <module−o p t i o n name="userName"> b o n i t a h i s t o r y </module−o p t i o n > <module−o p t i o n name="password">BoHis 1315</module−o p t i o n > <module−o p t i o n name="managedConnectionFactoryName"> j b o s s . j c a : s e r v i c e=å LocalTxCM , name=DefaultDS </module−o p t i o n > </ l o g i n −module> </ a u t h e n t i c a t i o n > </ a p p l i c a t i o n −p o l i c y > Indítsuk el a JBOSS-t és nézzük meg a lege- history célra szolgál. Ennek

megfelelően a sémánerált új táblákat! ink a már ismert adatbázisban a következőek: A Bonita BPM Oracle beállításai Eddig áttekintettük azt a JBOSS Oracle perzisztenciájának kialakítását, most ugyanebbe az adatbázisba végezzük el a Bonitával összefüggő konfigurációt. A Bonita ajánlása az, hogy 2 sémát használjunk Az egyik a journal, a másik 86 host : sid : user user o r a s e r v e r . company o r g ( p o r t : 1 5 2 1 ) test 1 : b o n i t a j o u r n a l / BonJour 1316 2 : b o n i t a h i s t o r y / BoHis 1315 Ezután állítsuk be a bonita-journal.properties és bonita-history.properties file-okat az alábbiak szerint (látható, hogy a BPM perzisztencia kezeléshez a Bonita a Hibernate könyvtárat használja): Bonita Tippek és Trükkök A JBOSS – Bonita páros produktív használata <bonita home>/b o n i t a / s e r v e r / d e f a u l t / c o n f / b o n i t a −j o u r n a l . p r o p e r t i e s s h o u l d be l i k e

: #h i b e r n a t e . hbm2ddl auto update hibernate . dialect org . hibernate d i a l e c t Oracle10gDialect hibernate . connection driver class o r a c l e . jdbc OracleDriver hibernate . connection url j d b c : o r a c l e : t h i n : @ o r a s e r v e r . company o r g : 1 5 2 1 : å test h i b e r n a t e . c o n n e c t i o n username bonita journal h i b e r n a t e . c o n n e c t i o n password BonJour 1316 h i b e r n a t e . c o n n e c t i o n shutdown true h i b e r n a t e . cache use second level cache false h i b e r n a t e . c a c h e use query cache false h i b e r n a t e . show sql false hibernate . format sql false h i b e r n a t e . use sql comments false bonita . search use true h i b er n a t e . search d e f a u l t indexBase $ {BONITA HOME}/ s e r v e r / d e f a u l t / work / i n d e x e s /å journal <bonita home>/b o n i t a / s e r v e r / d e f a u l t / c o n f / b o n i t a −h i s t o r y . p r o p e r t i e s s h o u l d be l

i k e : #h i b e r n a t e . hbm2ddl auto update hibernate . dialect org . hibernate d i a l e c t Oracle10gDialect hibernate . connection driver class o r a c l e . jdbc OracleDriver hibernate . connection url j d b c : o r a c l e : t h i n : @ o r a s e r v e r . company o r g : 1 5 2 1 : å test h i b e r n a t e . c o n n e c t i o n username bonita history h i b e r n a t e . c o n n e c t i o n password BoHis 1315 h i b e r n a t e . c o n n e c t i o n shutdown true h i b e r n a t e . cache use second level cache false h i b e r n a t e . c a c h e use query cache false h i b e r n a t e . show sql false hibernate . format sql false h i b e r n a t e . use sql comments false bonita . search use true h i b er n a t e . search d e f a u l t indexBase $ {BONITA HOME}/ s e r v e r / d e f a u l t / work / i n d e x e s /å history Ezután a Bonita webhelyéről töltsük le a A következőkben a script ezeket megkérdezi BOS-5.72-deployzip file-t (ez a legfrissebb

mielőtt elkezdi az adatbázisban létrehozni a Boverzió a cikk írásakor) innen: http://www nita releváns objektumokat: bonitasoft.com/products/BPM downloads Which domain do you want t o u s e ( p r e s s e n t e r å without nothing to use d e f a u l t ) ? Csomagoljuk ki egy alkalmas könyvtárba, ami[ PRESS ENTER] nek a gyökerét BOS-5.72-deploy-nak nevezzük a továbbiakban. A most kialakított 2 properties Where i s your BONITA HOME f o l d e r ? <BOS−5.62 − deploy >/c o n f / b o n i t a / file-t másoljuk ide: <BOS−5.72 − deploy >c o n f b o n i t a s e r v e r d e f a u l t å conf Ezután futtassuk az initDatabase.sh scriptet, ami itt található: <BOS−5.72 − deploy >/b o n i t a e x e c u t i o n e n g i n e /å database / Which h i b e r n a t e c o n f i g u r a t i o n t o u s e t o å generate database ( p r e s s enter without å nothing to use d e f a u l t ) ? [ PRESS ENTER] A futás után a használt Bonita verzióban 69

darab új tábla jött létre a fenti 2 sémában. 87 Design Patterns 7. Az Abstract Factory minta Az Abstract Factory minta Képzeljük el, hogy van egy komponenskészletünk, amiben az egyes komponenseket jól meghatározott interface-en keresztül érjük el. Amikor egy alkalmazást készítünk ezekkel a komponensekkel, akkor azok beépítésekor csak az interface-eik által nyújtott szolgáltatásokat használjuk ki. Emiatt készíthetünk több hasonló, de másik komponens készletet is, aminek a használatára csak egy egyszerű átkapcsolással szeretnénk áttérni. Ilyenkor mindenképpen javasoljuk az Abstract Factory tervezési minta szerinti felépítést. 7.1 ábra Az Abstract Factory minta UML diagramja Áttekintés Nézzünk egy példát, amikor tipikusan az Abstract Factory minta használatos! Van egy GUI komponenskészletünk, ami ilyesmi komponensekből áll: Button, RadioButton, CheckBox, List, Text, MultiText, stb. Mindegyik elemet egy számára

szabványosított felületen éri el a kliens, azaz az őt használó program (Ő egy másik osztály). Felépítünk egy GUI felületet ezekből az elemekből és minden tökéletesen működik Később másfajta keretrendszerek is meg88 jelennek, újfajta gombokkal, listákkal és egyéb elemekkel. Az egyszerűség kedvéért tegyük fel, hogy ezek interface is hasonló, illetve némi munkával hasonlóvá tehető. Szeretnénk, ha a programunk használni tudná ezt az új, esetleg webes vagy más futtató környezetben is jól működő keretrendszert. A gond az, hogy a kliens programunkban mindenütt „beégetve” használtuk az először megjelent komponenskészlet vezérlőinek típusneveit? Milyen jó lenne, ha a kliens GUI csak interface-en keresztül függne a konkrét vezérlőktől és emiatt gyorsan át tudnánk kapcsolni Design Patterns Az Abstract Factory minta az egyes ablakozó felületek között. Nézzünk csak a 7.1 ábrára, gondolkodjunk el és

remélhetőleg felismertük, hogy ebben az Abstract Factory minta tud segíteni Figyeljük meg mi a kapcsolat az ábra és a mi mostani konkrét feladatunk között! Ami az ábrán az AbstractProductA, AbstractProductB, az a mi esetünkben a Button, RadioButton, CheckBox, List, Text, MultiText, . Amikor egy Motif, Java Swing, KDE, GNOME, Windows Forms típusú megvalósításokat használunk, akkor például MotifBut- ton, MotifRadioButton, MotifCheckBox, MotifList, MotifText, MotifMultiText vagy KDEButton, KDERadioButton, KDECheckBox, KDEList, KDEText, KDEMultiText a konkrét product. Minden komponens család egy AbstractFactory felületű factory objektum segítségével gyártható le úgy, hogy először a család konkrét factory objektumát szerezzük meg. A minta előnye, hogy a kliens nem is tudja, hogy milyen konkrét keretrendszer dolgozik a háttérben. 7.2 ábra Az EAI komponensek Példa - EAI komponensek Az EAI3 megoldások feladata, hogy az egyes alkalmazások között

kiépített interface-ekkel biztosítsuk a közöttük való együttműködés lehetőségét, amivel azután munkafolyamatok is kiépíthetőek. Tipikus feladat, hogy az egyes alkalmazásoktól megszerezzük az adatokat, átalakítjuk egy szabványos (például XML) formátumra, 3 majd elküldjük az üzenetkezelő rendszer részére. Ilyenkor a kliens alkalmazás egy input adapter, ami az alkalmazást kifelé irányban összeköti a világgal. Egy ilyen feladatban persze nem grafikus vezérlők vannak, hanem adat megszerzők, XML alakra való átalakítók és elküldő komponensek. A problémakör modellezését a 7.2 UML ábra mutatja. Képzeljük el, hogy van 3 termékünk: Megszerző, Átalakító és Elküldő. Ezek több dol- EAI=Enterprise Application Integration 89 Design Patterns got is csinálhatnak, de most a példánkban mindegyiknek csak 1 darab metódusa lesz. Egy valódi feladatban az a tipikus, hogy több van, hiszen megszerezni is több módon lehet, annak a

körülményeit (forrás kódlap, stb.) be kell állítani A példánkban most nem absztrakt osztályokat, hanem interface-t használunk ezek leírására, nézzük meg mindhármat: Az Abstract Factory minta package c s . t e s t dp a b s t r a c t f a c t o r y ; public c l a s s ASPENEAIFactory implements å EAIFactory { public Megszerzo c r e a t e M e g s z e r z o ( ) { return new ASPENMegszerzo ( ) ; } public E l k u l d o c r e a t e E l k u l d o ( ) { return new ASPENElkuldo ( ) ; } package c s . t e s t dp a b s t r a c t f a c t o r y ; public i n t e r f a c e Megszerzo { public S t r i n g m e g s z e r e z ( ) ; } public A t a l a k i t o c r e a t e A t a l a k i t o ( ) { return new ASPENAtalakito ( ) ; } package c s . t e s t dp a b s t r a c t f a c t o r y ; } public i n t e r f a c e A t a l a k i t o { public S t r i n g t r a n s f o r m ( ) ; } package c s . t e s t dp a b s t r a c t f a c t o r y ; package c s . t e s t dp a b s t r a c t f a

c t o r y ; public i n t e r f a c e E l k u l d o { public S t r i n g e l k u l d ( ) ; } public c l a s s COTASEAIFactory implements å EAIFactory { public Megszerzo c r e a t e M e g s z e r z o ( ) { return new COTASMegszerzo ( ) ; } public E l k u l d o c r e a t e E l k u l d o ( ) { return new COTASElkuldo ( ) ; } A kérdés az, hogy ki gyártja le nekünk azokat az objektumokat, amik ilyen interface-szel rendelkeznek. Ennek a tervezési mintának az public A t a l a k i t o c r e a t e A t a l a k i t o ( ) { a jellegzetessége, hogy első körben ezt is elvont return new COTASAtalakito ( ) ; } módon tesszük, azaz az EAIFactory is csak egy interface, az olyan factory objektumok felülete, } Van tehát 2 termékcsaládunk, de a legyáramik képesek nekünk Megszerzo, Atalakito vagy tásukhoz kell egy-egy gyár. Amikor gyárat Elkuldo példányokat gyártani. alapítunk, akkor az EAIFactoryMaker classpackage c s . t e s t dp a b s t r a c t f a c t o r y ; t hívhatjuk

segítségül. Például egy EAIFacpublic i n t e r f a c e EAIFactory tory.getEAIFactory(„ASPEN”) hívással egy AS{ public Megszerzo c r e a t e M e g s z e r z o ( ) ; PEN termékcsaládot gyártó gyárhoz jutunk, public E l k u l d o c r e a t e E l k u l d o ( ) ; aminek persze olyan a felülete, mint minden public A t a l a k i t o c r e a t e A t a l a k i t o ( ) ; } gyárnak (EAIFactory): A termékeinknek egyelőre 2 családját csináljuk meg, az egyes családok nevei: ASPEN és COTAS. A későbbiekben mindkét család konkrét termékeit implementálni fogjuk, de itt csak az egyes EAIFactory felületeket mutatjuk egyelőre: 90 package c s . t e s t dp a b s t r a c t f a c t o r y ; public c l a s s EAIFactoryMaker { public s t a t i c EAIFactory getEAIFactory ( å String interfaceTopic ) { i f ( i n t e r f a c e T o p i c . e q u a l s ( "COTAS" ) ) { Design Patterns } } return new COTASEAIFactory ( ) ; } e l s e i f ( i n t e r f a c e T o p i c .

e q u a l s ( "ASPEN"å )) { return new ASPENEAIFactory ( ) ; } return n u l l ; Egy gyár dolgozik, előre megtervezett termékek készülnek, így a mintánk implementálása során ezek terve sem maradhat kidolgozatlan. A következő 6 class a 3 ASPEN és 3 COTAS termék gyártási terve. Lehetnének sokkal összetettebbek is, működhetnének bonyolultabban, de talán akkor sem mutatnák be jobban ennek a mintának a lényegét: package c s . t e s t dp a b s t r a c t f a c t o r y ; public c l a s s ASPENMegszerzo implements å Megszerzo { public S t r i n g m e g s z e r e z ( ) { return "ASPEN␣ g e t t e r " ; } } package c s . t e s t dp a b s t r a c t f a c t o r y ; public c l a s s ASPENAtalakito implements å Atalakito { public S t r i n g t r a n s f o r m ( ) { return "ASPEN␣TR" ; } } package c s . t e s t dp a b s t r a c t f a c t o r y ; public c l a s s ASPENElkuldo implements E l k u l d o { } public S t r i n g e l k u l

d ( ) { return "ASPEN␣ s e n d e r " ; } package c s . t e s t dp a b s t r a c t f a c t o r y ; public c l a s s COTASMegszerzo implements å Megszerzo { Az Abstract Factory minta } public S t r i n g m e g s z e r e z ( ) { return "COTAS␣ g e t t e r " ; } package c s . t e s t dp a b s t r a c t f a c t o r y ; public c l a s s COTASAtalakito implements å Atalakito { public S t r i n g t r a n s f o r m ( ) { return "COTAS␣TR" ; } } package c s . t e s t dp a b s t r a c t f a c t o r y ; public c l a s s COTASElkuldo implements E l k u l d o { } public S t r i n g e l k u l d ( ) { return "COTAS␣ s e n d e r " ; } Elkészült minden. Rendelkezünk 2 gyárral és 6 termékkel. Itt van az a pont, amikor őket egy alkalmazásban bevethetjük. Ezt a helyet hívjuk más szóval kliens osztálynak (vagy egyszerűen kliensnek), ami a lenti példában a Main Most éppen a COTAS család termékeit használjuk, ezért az

EAIFactoryMaker osztályt egy ilyen gyár létrehozására kérjük meg. A gyár neve: fact lett, ami mindegyik termékből most 1-1 darabot gyárt csak, de ezekre sincs korlát, csinálhatna sokat, de esetenként nullát is, ahogy a pillanatnyi igény hozza. A lenti példakliensünk kicsit egyszerűen ugyan, de elkezdi használni a legyártott termékeket, majd mindegyiket megkéri arra, hogy csinálják azt, amihez értenek: megszerez(), elkuld(), transform(). package c s . t e s t dp a b s t r a c t f a c t o r y ; public c l a s s Main { public s t a t i c void main ( S t r i n g [ ] a r g s ) { System . out p r i n t l n ( " T e s z t ␣ A b s t r a c t ␣å Factory " ) ; 91 Design Patterns Az Abstract Factory minta EAIFactory f a c t = EAIFactoryMaker . å getEAIFactory ( "COTAS" ) ; // EAIFactory f a c t = EAIFactoryMaker . å getEAIFactory ("ASPEN") ; Megszerzo m e g s z e r z o = f a c t . å createMegszerzo () ; Elkuldo elkuldo = fact

.å createElkuldo () ; Atalakito atalakito = fact .å createAtalakito () ; System . out p r i n t l n ( m e g s z e r z o å megszerez ( ) ) ; } } System . out p r i n t l n ( e l k u l d o e l k u l d ( ) ) å ; System . out p r i n t l n ( a t a l a k i t o å transform () ) ; Befejezésül a BME egyik segédletéből kölcsönzött példát ajánljuk tanulmányozásra, amit a 7.3 ábra mutat Gondolkodjunk el rajta! A különféle biológiai környezetek szimulálása egy ilyen osztály felépítéssel remekül megoldható. 7.3 ábra Egy biológiai program UML modellje 92