Priprava okolja za izvajanje programerskih testov (TDD - Test Driven Development ) (verzija 2)
Celje, marec 2008
Marjan Gubensek
Interno delovno gradivo
1
KAZALO VSEBINE 1. UVOD 2. SPLOŠNI ORIS Osnovni značilnosti ob prehdu iz okolja Designer v okolje jDeveloper Program ne deluje pravilno Vrste testov Kaj je TDD in zakaj ? TDD – miti in realnosti ( Scott W. Ambler ) Scenarij razvijanja programja v ciljnem sistemu Kje so začetki ? 3. PROGRAMERSKI ORIS Vstop v jDeveloper Delovno okolje izvajanja naloge Dilema pristopa Osnove JUnit JUnit za poslovne komponnente Obvladovanje podatkov v TDD Vzpostava baze DbUnit Metode razširitve Poženi »build« Poglej oziroma dobi rezultat build-a 4. ZAKLJUČEK – PROGRAMERSKI 5. ZAKLJUČEK - SPLOŠEN 6. VIRI
Marjan Gubensek
Interno delovno gradivo
2
1. UVOD
Gradivo » Priprava okolja za izvajanje programerskih testov« (TDD - Test Dri ven Development ) je namenjeno interni obravnavi problematike testiranja v smeri TDD z integracijo v jDeveloper. Osnovna izhodišča in pričakovanja so bila zapisana ob odprtju naloge. Opredeljene so bile naslenje zahteve: Okolje za testiranje mora biti int egrirano v jDeveloper. Podpirati mora vsaj trenuten razvoj, v katerem se uporabljajo naslednje tehnologije in ogrodja: BC4J, EJB, ADF, Struts, JSF in JClient. Podatki uporabljeni za testiranje, oz. nad katerimi se bodo izvajali testi, naj bodo shranjeni v bazi (v tabelah aplikacije) Testi morajo biti ponovljivi Testi se morajo izvajati tako posamezno kot v nekem sekvenčnem zaporedju Teste naj bo možno izvajati tako ročno kot avtomatično Vsebino naloge je lahko razumeti kot izziv. Če pa to vsebino položimo v izvajalni in širši lokalni kontekst, jo lahko razumemo tudi drugače – kot nekaj, kar moramo doseči vsaj v prvem koraku tako, da ne izgubimo orientacije smeri, ki bi zagotavljala strokovni stik s globalnimi dogajanji na področju našega delovanja. In to je potreben pogoj za vsa ostala nadaljevanja. Gradivo pod aja oris na dveh nivojih: Splošen oris Programerski oris Poglobitev v smeri splošnosti je obisk strani s teoretičnimi osnovami (nove metodologija, TDD). P oglobit ev v programerski smeri je obisk tehnoloških strani. Poglobitev v smeri same naloge je ogle d gradiv predstavitve iz septembra 2007. Poglobitev v izvedbe naloge je ogled primerov skozi jDeveloper. Podrobnejši seznam je naveden v VIRIH.
Marjan Gubensek
Interno delovno gradivo
3
2. SPLOŠNI ORIS Osnovni značilnosti ob prehdu iz okolja Designer v okolje jDeveloper manj transfor macij in naslonitev na ogrodja
Marjan Gubensek
Interno delovno gradivo
4
Program ne deluje pravilno
Na spodnji skici si lahko na preprostem geometrijskem primeru preigramo razne variantne, na katere naletimo v klasični praksi v trikotniku uporabnik-analitik-programer. Imamo različne vzroke, ki pripeljejo do nepravilnega delovanja programa. Prav tako je prikazana shema povečevanja kompleksnosti glede na namen in uporabo programske rešitve (večnamensko, večuporabniško, problem uporabnika ).
Problem je najbolj čisto zastavljen v primeru 3. Ta vsebuje tudi primer uporabe s pričakovanim rezultatom 1,41. Pričakovani rezultat moramo doseči tako v času priprave, kakor tudi v poljubnih ponovitvah. Ponovitve podvržemo avtomatizmom in če se kaj zalomi, moramo dobiti ustrezno opozorilo. Tako lahko pravočasno ukrepamo in preprečimo mnogovrstna jezna opozorila, da stvar zopet ne deluje. Vemo da se to v praksi zgodi velikokrat ob raznih hitrih dopolnitvah in spremembah.
Marjan Gubensek
Interno delovno gradivo
5
Vrste testov test enote inte gracijski test funkcionalni test performančni test test sprejemljivosti instalacijski test
Kaj je TDD in zakaj ?
TFD --------------- Test First Development TDD --------------- Test Driven Development MDD -------------- Model Driven Development Naše dosedanje izkušnje se navezujejo predvsem na MDD, saj se prvenstveno srečujemo z podatkovno orientiranim delom. Pri tem gre tudi za večjo vizualno obliko razmislekov. V TDD je večji povdarek na kodi. TDD izhaja iz kroga »lahkih metodologij« (agilnih). Glavna značilnost le teh je, da so usmerjene bolj »adaptivno« kot »prediktivno« in da so bolj »people-oriented«, kot process-oriented« (Fowler). Metodološke razlike prav tako nazorno kaže spodnja stara skica iz literature. To moramo poznati in razumeti kako ne varni so lahko ekstremi v eno ali drugo smer, saj lahko pripeljejo do izgube potrebnih in zadostnih pogojev za uspeh.
Marjan Gubensek
Interno delovno gradivo
6
Osnovni namen TDD-ja je povečanje kakovosti kode. V kolikor nam to uspe imamo poleg same kakovostnejše kode tudi ugodne vplive v smislu pridobivanja večje kompetentnosti in zaupanja vpletenih v razreševanje problema. Test-driven development (TDD) je evolutivni pristop pri razvoju programja. V njem kombiniramo test-first razvoj in refaktorizacijo. ( Refactoring is the restructuring of soft ware by applying a series of internal changes that do not afect its observable behavior ( Fowler )). TDD = TFD + REFAKTORIZACIJA Najprej napišemo test in nato ravno dovolj kode (čim manj), da lahko poženemo test Po uspelem prvem koraku napravimo refaktor izacijo. Refaktorizacija ni dodajanje novih funkcionalnosti ampak izboljšava notranje strukture kode. Kadar dodajamo nove funkcionalnosti, dodajamo novo kodo. Torej refaktorizacija in dodajanje nove funkcionalnosti sta dva različni, toda komplementarni opravili (črna in bela škatla). V naslednji skici je podana poenostavljena ponazoritev novih razmer: iterativnost, inkrementalnost, integrativnost ( CI continuous i ntegration ); enota, celota. napiši test, napiši rešitev, napravi test
Marjan Gubensek
Interno delovno gradivo
7
Na spodnji sliki je primer rezultata, ki podaja stanje v nekem trenutku dela v izbranem načinu delovanja. Rezultat je podan na : celoto, pakete, razrede metode. Številke nedvoumno kažejo na to kako smo uspešni pri delanju rešitve. Nedvoumno je dobro imeti v vsakem trenutku na razpolago nekaj takega. Drugo je seveda vprašanje kako se do tega dokopati.
Marjan Gubensek
Interno delovno gradivo
8
Skica »TDD ritem« ponazarja osnovni potek dela pri TDD. Vsa opravila naj bi potekala hitro (merjeno v minutah). Več časa posvetimo refaktorizaciji.
Marjan Gubensek
Interno delovno gradivo
9
Kje se kaj dogaja
Razmerje med pogostostjo spreminjanja in stopnjo formalnih kontrol se spreminja tekom napredovanja pri iskanju rešitve. Tako je v začetnih fazah velika stopnja pogostosti spreminjanja in manjša formalna kontrola. Na koncu ( v produkciji ) imamo manjšo stopnjo pogostosti spreminjanja in večjo formalno kontrolo. Na skici je primer nekaj instanc ( »peskovnikov«), ki so namenjeni določenim specifičnim opravilom. Določeno avtomatizirano testiranje ( npr TDD ) vršimo v okviru razvojne in projektno integracijske instance.
Marjan Gubensek
Interno delovno gradivo
10
TDD – miti in realnosti ( Scott W. Ambler )
Za poglobljenejši vstop v problematiko TDD sem izbral Scott-ov pregled mitov in realnosti. V tem so tudi povezave na razne vidike obravnave TDD-a You create a 100% regression test suite Although this sounds like a good goal, and it is, it unfortunately isn't realistic for several reasons: I may have some reusable components/frameworks/... which I've downloaded or purchased which do not come with a test suite, nor perhaps even with source code. Although I can, and often do, create black-box tests which validate the interface of the component these tests won't completely validate the component. The user interface is really hard to test. Although user interface testing tools do in fact exist, not everyone owns them and sometimes they are difficult to use. A common strategy is to not automate user interface testing but instead to hope that user testing efforts cover this important aspect of your system. Not an ideal approach, but still a common one. Some developers on the team may not have adequate testing skills. Database regression testing is a fairly new concept and not yet well supported by tools. I may be working on a legacy system and may not yet have gotten around to writing the tests for some of the legacy functionality. The unit tests form 100% of your design specification People new to agile software development, or people claiming to be agile but who really aren't, or perhaps people who have never been involved with an actual agile project, will sometimes say this. The reality is that the unit test form a fair bit of the design specification, similarly acceptance tests form a fair bit of your requirements specification, but there's more to it than this. As Figure 3 indicates, agilists do in fact model (and document for that matter), it's just that we're very smart about how we do it. Because you think about the production code before you write it, you effectively perform detailed design as I highly suggest reading my Single Source Information: An Agile Practice for Effective Documentation article. You only need to unit test For all but the simplest systems this is completely false. The agile community is very clear about the need for acceptance testing, user testing, system integration testing, and a host of other testing techniques.
Marjan Gubensek
Interno delovno gradivo
11
TDD doesn't scale This is partly true, although easy to overcome. Scalability issues include: 1.
Your test suite takes too long to run .
This is a common problem with a equally common solutions. First, separate your test suite into two components. One test suite contains the tests for the new functionality that you're currently working on, the other test suite contains all tests. You run the first test suite regularly, migrating older tests for mature portions of your production code to the overall test suite as appropriate. The overall test suite is run in the background, often on a separate machine(s), and/or at night. Second, throw some hardware at the problem. 2.
Not all developers know how to test .
That's often true, so get them some appropriate training and get them pairing with people with unit testing skills. Anybody who complains about this issue more often than not seems to be looking for an excuse not to adopt TDD. 3.
Everyone might not be taking a TDD approach .
Taking a TDD approach to development is something that everyone on the team needs to agree to do. If some people aren't doing so, then in order of preference: they either need to start, they need to be motivated to leave the team, or your team should give up on TDD.
Marjan Gubensek
Interno delovno gradivo
12
Scenarij razvijanja programja v ciljnem sistemu Razvijanje programa v novih razmerah se v odnosu do starega sistema (Designer/Repozitorij) ne spreminja le v tehnološkem vid iku, ampak tudi z drugih vidikov. Predvsem je sistem interakcij med udeleženimi spremenjen tako, da se sprožajo nove komunikacijske poti ne samo v lokalu, ampak tudi v globalu. Vse ključne stvari okoli razvijanja programja so danes postale globalne in potekajo skozi specializirane skupnosti. Če smo morali po Designer iti s torbo, imamo danes na mizi celi svet. In tega je seveda ogromno, tako da brez primernih filtrov in leč, ki ostrijo pogled, hitro zaidemo v nepregledno stanje Programer ima danes svojo mašino (prenosnik), na katerem je osnovno programje in orodjarska podpora. V našem primeru je to predvsem XE baza in jDeveloper. S svojim delom je vključen v različne projekte in skupnosti.Pri vključitvi v določen projekt prevzema »naloge«, ki jih poskuša opraviti čim hitreje in učinkovitejše tako, da rezultati ustrezajo zahtevanim kriterijem kakovosti. Pri včlanitvi v razne skupnosti, se povezuje z dogajanji po svetu in ostri svoje kompetenčne zmožnosti (pretok znanj,……). Potek programerjevega dela je v gro bem naslednji : Iz projektnega sistema verzij vzame kopijo enote kode na kateri bo opravljal naloge, ali odpravljal morebitne napake. Kodo dopolnjuje in modificira lokalno v svojem privatnem delovnem prostoru. Občasno osvežuje svojo enoto dela s spremembami iz sistemu verzij. Preveri oziroma testira kakovost napisanega . V projektni sistem verzij vrne rezultat svojega dela. Kritična je tretja točka , kjer lahko pride do konflikta različnih interpretacij iste zadeve. Konflikt je potrebno razrešiti skozi poglobljeno komunikacijo in se dogovoriti ali naj bo stvar »črna« ali »bela«. Na ravni projektnega sistema verzij se vrši integracija prispelih delov rešitve in preverja kakovost celote glede na podana pričakovanja rezultatov. To poteka bodisi: Ročno. Avtomatsko v časovnih intervalih. Avtomatsko ob novi spremembi, ki jo ugotavljamo v krajšem časovnem intervalu.
Marjan Gubensek
Interno delovno gradivo
13
Posamezne enote se nahajajo v dveh statusih: Dobro. Slabo. Za enote, ki spremenijo status bodisi: Dobro - Slabo Slabo
- Dobro
mora mo dobiti opozorila, tako da obvladujemo dinamiko celote. V smislu obvladovanja celote so opozorila o ohranjanju nekega statusa (dobro-dobro ali slabo-sabo) nepotrebna oziroma nadležna. Tovrstno informacijo moramo pridobiti le na posebno zahtevo. Celoten sistem predpostavlja najmanj dnevno dinamiko zato, da ne pridemo do prevelikega števila konfliktnih situacij in da imamo pravočasno potrditev pravilnosti delovanja celote. Povratne informacije o spremembah so ključni element. Tako nekako je logična slika. Za tak način dela so na razpolago različna orodja in ogrodja, ki podpirajo delovanje v opisani smeri. Predvsem so to sistemi za verziranje in sistemi za neprestano integracijo. ( CC, Continnum, CVS, Subversion ) Na http://www.ibm.com/developerworks/java/library/j-ap11297/index.html lahko preberemo tudi najnovejša (december 2007) spoznanja, kako se stvari odvijajo, ko se ne uresničujejo predpostavke, da lahko vse podvržemo nekim avtomatizmom in da vsi prvenstveno čakamo na opozorila, ko je nekaj v avtomatizmih zaškripalo.
Marjan Gubensek
Interno delovno gradivo
14
Kje so začetki ?
Osnovna zamisel je stara in je v tem, da je program sposoben marsičesa – torej lahko z njem tudi preizkušamo pravilnost delovanja drugega programa. V tem preizkusu konfrontiramo pričakovan rezultat z dobljenim rezultatom iz programa. Java je to oživela in z ogrodjem JUnit generičnim imenom xUnit.
sprožila plaz prizadevanj v tej smeri pod
Samoumevna je trditev, da celota ne more delovati pravilno, če ne delujejo pravilno vse enote, ki nastopajo v celoti. Gremo torej od najmanjše enote proti celoti. V praksi ne moremo pričakovati 100%, vendar več kot storimo, bolj se povečuje verjetnost pravilnega delovanje celote. Najenostavnejši možni primer : import junit.framework.TestCase; public class TestString extends TestCase { public void testKončanjeZ() { assertTrue(“abcabc”.endsWith(“abc”)); } public void testKončanjePrazniNiz() { assertTrue(“Ne konča se z praznim nizom!”, “abcabc”.endsWith(“”)); }
Razred TestCase je začetek in vstopna točka v to problematiko. V nadaljevanju se pričakuje: poznavanje principov lahkih metodologij (agilnih), poznavanje principov objektnega pristopa, poznavanje principov relacijskega pristopa, osnovno poznavanje jave, osnovno poznavanje jDeveloperja, osnovno poznavanje tehnologij v jDeveloperju, osnovno poznavanje fenomena odprto kodnih rešitev, osnovno poznavanje Oraclovih smeri, osnovno poznavanje večplastne arhitekture, poznavanje podatkovnih obravnav in modeliranja, poznavanje baze, poznavanje poslovnih komponent za javo, poznavanje ADF sklada tehnologij.
Marjan Gubensek
Interno delovno gradivo
15
3. PROGRAMERSKI ORIS
Vstop v jDeveloper
Ko zapustimo klasična okolja razvoja programja in preidemo v javanski IDE, začne hitro naraščati širina potrebnih znanj, veščin in možnosti. To ima za posledico tudi večjo različnost interpretacij o možnih smereh nadaljevanja. V sklopu jDeveloperja je množica novih tehnologij z različ nim izvorom: Oraclove stare tehnologije v novi preobleki za delo z bazo, Oraclove nove javanske tehnologije BC4J in ADF ter TopLink, javanske tehnologije (EJB…..), tehnologije iz odprtokodne skupnosti, inkorporirane s strani Oracla, razširitve z lastnim izborom odprtokodnih rešitev. Uporaba jDeveloperja je lahko zelo različna: lahko z njim obdelujejo le nek parcialni problem (npr.: procedura, diagram), lahko napravimo neko rešitev od začetka do konca. V splošnem izberemo neko skladovnico tehnologij, ki najbolj ustreza problemu reš itve ter našim preferencam (npr BC4J-ADF-JSF). Do določene globine lahko probleme rešujemo s tistim, kar nam IDE ponuja v avtomatizmih. Ko trčimo na prag zmožnosti le teh, moramo preiti na »ročno« obravnavo in se resneje zakopati v ozadja. Za praktično delovanje potrebujemo metode dela . Pri tem velja, da je metoda dela preizkušen sklop opravil, orodij in ogrodij (tehnologij), ki pripelje do uporabnega rezultata. Zaradi hitrega razvoja se tudi krajša »rok veljavnosti« metodam dela, oziroma te doživljajo manjše število uporab. Razmerje med rutinskim in nerutinskim delom se spreminja. Prav tako se povečuje razkorak med potrebnim vedenjem in uporabljivostjo vedenega.
Marjan Gubensek
Interno delovno gradivo
16
Delovno okolje izvajanja naloge
Delovno okolje za izvajanje naloge sem vzpostavil na mojem PC-ju. Infrastrukturno osnovo predstavlja xe baza s apex-om in jDeveloper, ter različna druga ogrodja. (…. in JUnit, JUnit integracija, ANT, DbUnit ). Iz nekako standardnega nabora izstopa DbUnit. Test123DB je delovni prostor, ki vsebuje različne pro jekte ( terminologija jDev ): Baza123 vsebuje definicije baznih elementov ( build.sql ) FrameworkExtension vsebuje razširitve ogrodja poslovnih komponent v smislu Oracle priporočila mojDBunit vsebuje razširitve JUnit in DbUnit bcProjektDb in tocketest
pri meri modulov
TestiranjeEnot testni programi in zagoni ( build.xml ) Knjižnica libgubi
Naveden primer ni navodilo ali priporočilo ampak le navedba dejstva, zato da se lahko spustimo v morebitne večje podrobnosti. Vsebina je nastajala postopno in je v spr eminjanju. V okviru tega delovnega prostora lahko vršim prikaze (ne)delovanja zastavljene rešitve sistema dela.
Marjan Gubensek
Interno delovno gradivo
17
Dilema pristopa Vse so objekti in tam nekje v ozadju je baza. Vse je baza in tam v ospredju je uporabniški vmesnik. Skica grobo ponazarja osnovne gradnike v primarni Oraclovi orientaciji temelječi na ADF skladovnici tehnologij. UI … uporabniški vmesnik AM …aplikacijski modul EO… entitetni objekt VO….view objekt I ..proc. za insert, D..proc. za delete, R..proc. za update, F..C/S aplik acija (npr.: forms)
Marjan Gubensek
Interno delovno gradivo
18
Osnovna orientacija: podatkovna logika na bazo, poslovna logika na srednjo plast. Iščemo kompromis med številom plasti (kompleksnostjo) in sklopljenostjo. V poslovnih aplikacijah in še posebno v naših zatečenih razmerah, bo v ospredju verjetno bazni oziroma podatkovni poudarek. Spodaj je podan podatkovni model s svojim tabelnim in xml delom. Tabele: tip problemov, problemi, problemi-problemi, uporabniki, uporabniki-problemi iz sklopa neke aplikacije in tabeli povezave in toc ketest kot nepovezani tabeli.
Ta model sem uporabil pri prikazih možnosti, predvsem v delu problema obvladovanja podatkov pri testiranju.
Marjan Gubensek
Interno delovno gradivo
19
Osnove JUnit Neštetokrat zapisano in naj bo še enkrat – v malo bolj zgoščeni obliki. TestCase zagotavlja skupino pomožnih metod, ki pomagajo pri izvajanju serije testov. Metodi setup in teardown kreirata ozadje vsakega testa. Enostaven primer: public class MojTestCase extends TestCase { /** * klic super konstruktorja. * @param testIme ime metod e, ki jo je potrebno pognati */ public MojTestCase(String testIme){ super(testIme); } /** * klicano pred vsakim testom */ protected void setUp() throws Exception { initNekaj(); } /** * klicano po vsakem testu */ protected void tearDown() throws Exception { zaključiNekaj(); /** * metoda, ki testira da....... */ public void testNekajTest1(){ ... } /** * metoda, ki testira da...... */ public void testNekajTest2 (){ ……….. } }
Marjan Gubensek
Interno delovno gradivo
}
20
TestSuite je sest avljen iz nekaj TestCase s ali drugih TestSuite objektov. Testi dodani v TestSuite potekajo zaporedno . Primer: public class MojTestSuite extends TestSuite { public static Test suite() { TestSuite suite = new TestSuite("Test suite za problem..."); // dodamo prvi test MojTestCase mtc = new MojTestCase("testNekajTest1"); suite.addTest(mtc); // dodamo drugi test MojTestCase mtc2 = new MojTestCase("testNekajTest2"); suite.addTest(mtc2); return suite; } }
Javanski IDE imajo vključen J Unit . Testiranje samih razredov in metod je tako možno na enostaven način, tudi če v nadalje vanju ne gremo v kakšne višje metodološke oblike ( TDD ). V kolikor nam TestCas ne zadostuje, napravino svojo razširitev. public abstract class GubiDbTestCase extends TestCase { …………… In tega potem uporabljamo pri delu. public class MojTestCase extends GubiDb TestCase {………
Marjan Gubensek
Interno delovno gradivo
21
JUnit za poslovne komponente
V primeru uporabe poslovnih komponent moramo namestiti Oraclovo integracijo za JUnit. V naših razmerah bo to pogostokrat. Delo z AM (aplikacijski modul) je osrednjega pomena. Pri delu sledimo Oraclovi d okumentaciji in navodilom ter priporočilom. Kaj bomo upoštevali oziroma uporabili od priporočanega, je odvisno od konkretne projektne situacije. Tako je npr. priporočilo, da se vzpostavi dodaten sloj za poslovne komponente in s tem pripravi orožje za anticipacijo kasnejših problemov. To zahteva nekaj dodatnega dela in povečano kompleksnost obravnave. Ali upoštevamo to ali ne, je torej stvar »kalkulacije« in odločitve o tem, na katerem nivoju bomo držali skupni imenovalec. public class GubiEntityImpl exten ds EntityImpl {…….. public class GubiViewObjectImpl extends ViewObjectImpl {….. public class GubiApplicationModuleImpl extends ApplicationModuleImpl {…
… package irc.gubi.bcprojektdb.test; import oracle.jbo.ApplicationModule; import oracle.jbo.client.Configuration; public class BcprojektdbGubiConnectFixture { private ApplicationModule _am; public BcprojektdbGubiConnectFixture() { } public void setUp() { _am = Configuration.createRootApplicationModule("irc.gubi.bcprojektdb.AppModuleGubi","AppModul } public void tearDown() { // getApplicationModule().getTransaction().commit(); Configuration.releaseRootApplicationModule(_am, true); } public ApplicationModule getApplicationModule() { return _am; } }
AM je transakcijski objekt AM lokalna konfiguracija
Marjan Gubensek
Interno delovno gradivo
22
Ilustracija v uporabi s TestCase
package irc.gubi.bcprojektdb.test; import junit.framework.TestCase; import oracle.jbo.ViewObject; public class BcprojektdbProPogledTestCase extends TestCase BcprojektdbGubiConnectFixture fixture1 = new BcprojektdbGubiConnectFixture(); public BcprojektdbProPogledTestCase(String sTestName) { super(sTestName); } public void testObstojaProtipViewG_drugic() { ViewObject view = fixture1.getApplicationModule().findViewObject("ProViewG"); assertNotNull(view); } public void setUp() throws Exception { fixture1.setUp(); } public void tearDown() throws Exception { fixture1.tearDown(); }
{
}
Ilustracija s GubiDbCase razširitvijo package irc.gubi.bcprojektdb.test; import irc.gubi.tdd.dbogrodja.GubiDbTestCase; …… public class BcprojektdbSestavljenPogledTestCase extends GubiDbTestCase BcprojektdbGubiConnectFixture fixture1 = new BcprojektdbGubiConnectFixture(); public BcprojektdbSestavljenPogledTestCase(String sTestName) { super(sTestName); } public void testSestaveBrezAM() throws Exception { izpisParametrovOkolja(); napraviMostVarovanje (new String[] { "upor", "protip", "pro", "propro","proupor"}); } public void testSestaveZam() throws Exception { // nekaj kar tako……. izpisParametrovOkolja(); ……. // za "glavo" ViewObject viewProtip = fixture1.getApplicationModule().findViewObject("ProtipViewG"); assertNotNull(viewProtip); Row vrsticaProtip = viewProtip.createRow(); vrsticaProtip.setAttribute("Iid", 4000); vrstica Protip.setAttribute("Naziv", "To je neki Protip"); …. assertEquals(vrsticaProtip.getAttribute("Naziv"),"To je neki Protip"); // commit odločitve // viewProtip.getApplicationModule().getTransaction().commit(); Marjan Gubensek
Interno delovno gradivo
{
23
// za "pozicijo" ViewObject viewPro = fixture1.getApplicationModule().findViewObject("ProViewG"); assertNotNull(viewPro); Row vrsticaPro = viewPro.createRow(); vrsticaPro.setAttribute("Iid", -1); …… vrsticaPro.setAttribute("ProtipIid", vrsticaProtip.getAttribute("Iid")); // igra refleksije Class x = vrsticaPro.getClass(); assertEquals("class irc.gubi.bcprojektdb.ProViewRowImpl",x.toString()); // viewPro.insertRow(vrsticaPro); assertEquals(vrsticaPro.getAttribute("Naziv"),"To je nek Pro"); // itd ….. } public void setUp() throws Exception { super.setUp(); fixture1.setUp(); ApplicationModule am = fixture1.getApplicationModule(); Connection c1 = getJDBCConnection( am.getTransaction().getConnectionMetadata().getJdbcURL(), am.getTransaction().getConnectionMetadata().getUserName(), (String)getConnectionProperty("connection.password")) ; dbconnection = getDbConnection(c1); } public void tearDown() throws Exception { super.tearDown(); fixture1.tearDown(); } }
Verjetno je kar nekaj napak v kodi. Namen je le konkretneje prikazati, kako dobimo oblast nad AM v JUnit integraciji ter izpostaviti transakcijski značaj AM.
Marjan Gubensek
Interno delovno gradivo
24
Obvladovanje podatkov v TDD Sedaj imamo trk dveh pristopov – relacijskega in objektnega. Ko se konča enostavnost testiranja razreda in se srečamo dejansko s komponento in z objektnemu svetu tujimi elementi, se zadeve zaostrijo. Testi potekajo hitro, morajo biti ponovljivi, pripraviti je potrebno testne podatke, pripraviti je potrebno tudi podatke pričakovanih rezultatov itd. Zaradi novosti in hitrosti se lahko marsikaj zavozla – potrebne so hit re obnove. Ta problematika ni samo pri testiranju enot, ampak je prisotna tudi v drugih vrstah testov ali predstavitev. V kolikor nimamo ustrezno rešenega tega problema, se lahko v realnih situacijah poslovimo od TDD-ja. V člankih so tudi opozorila na miselne trke z klasičnimi DBA pogledi. Problem obvladovanja podatkov (pri testiranju) bom v okviru tega gradiva podrobneje podal. Za to je več razlogov: navezuje se tudi na sedanje oblike delovanja, predlagana rešitev v prototipu nakazuje možne smeri, predl og lahko vsebuje kakšno resno neodkrito pomanjkljivost, predlog je lahko enostavno nesprejemljiv (strategije) ali neizvedljiv, predlog ne sme biti sprejemljiv le zato, ker nakazuje neko rešitev. predlog mogoče premalo upošteva naše realnosti. Skica prikazuje različnost baznih posegov
Marjan Gubensek
Interno delovno gradivo
25
Vzpostava baze
Za vzpostavo baze imamo skupino sql skript od definicijskih do podatkovnih (inserti začetnih podatkov v bazo). To je klasičen, dobro znan postopek. Skripta morajo biti napisana v strukturirani obliki tako, da lahko v drugi situaciji uporabimo neko funkcionalno enoto (npr.: pobriši podatke, napolni začetne podatke). Začetni podatki imajo značaj testnih podatkov.
Na skici je prikazano vključevanje sql skript na treh nivojih.
Marjan Gubensek
Interno delovno gradivo
26
Znani so zapleti v odnosu sekvence in PK-ji ter FK-ji. Da postopki potekajo čim bolj gladko, je možno napisati sprožilec po vzoru:
CREATE SEQUENCE USERS_SEQ INCREMENT BY 1 START WITH 400 MAXVALUE 99999999 MINVALUE 1 CACHE 20 ; CREATE OR REPLACE TRIGGER ASSIGN_USER_ID BEFORE INSERT ON USERS FOR EACH ROW BEGIN IF :NEW.USER_ID IS NULL OR :NEW.USER_ID < 0 THEN SELECT USERS_SEQ.NEXTVAL INTO :NEW.USER_ID FROM DUAL; END IF; END;
Torej sprožanje dodeljevanja PK na osnovi sekvence prevzame baza. Tako napisano sedaj omogoča ponovljivost insertiranja začetnih podatkov iz skripte in razrešitev problema referenčne integritete. Tako napisano tudi daje potrebno gibkost za različne situacije iz dveh zornih kotov: podatke pripravimo »ročno« vključno s integriteto odnosov (FK), podatke pripravimo v aplikaciji in napravimo izvoz. V pogojih TDD se stvari odvijajo zelo hitro (v minutah) in ponovljivo (v minutah). Imamo različne kontekstne testne situacije in v vsakem trenutku moramo biti sposobni doseči znano stanje podatkov. Na spodnji skici so prikazani trije možni scenariji
Marjan Gubensek
Interno delovno gradivo
27
Marjan Gubensek
Interno delovno gradivo
28
DbUnit
DbUnit je odprtokodno ogrodje. Ogrodje olajšuje testiranje v primerih, ko imamo opravka z bazo. V teh primerih se srečamo z zunanjo odvisnostjo. Ogrodje lahko uporabimo programsko v okviru JUnit. V primerih ko za »build« uporabljamo ANT pa lahko koristimo ANT taske, ki jih prinaša DBunit. In kakor je običajno za ta področja, lahko v primeru posebnih zahtev posežemo tudi po razširitvah in dopolnitvah ogrodja. Kako globoko bomo posegli, je odvisno od potreb in zmožnosti. Tako se oborožimo za manipulacije (INSERT, DELETE, …) s podatki zapisanimi v tabelah in v xml datotekah. S primerno kombinacijo ANT taskov dosežemo prisotnost specifičnih testnih podatkov v bazi v času izvajanja nekega testa. Prav tako lahko napravimo različne scenarije ponovitve. Podatkovni model Za ponazoritve delovanja sem uporabil že prej podani podatkovni model. Nad tem modelom so vzpostavljene poslovne komponente z različnimi aplikativnimi primeri. Iz baznih zapisov se tvorijo različni datotečni zapisi za različne namene. Iz namenskih datotečnih zapisov se polnijo bazni zapisi. Za ponazoritev možnosti je na prototipnem nivoju napravljeno nekaj razširitev v abstraktni razred GubiDbTestCase . Te razši ritve se nanašajo na primere tipov, kakor je ilustrirano spodaj. dobiVrednostTKV(….) ---- za tabelo, kolono, vrstico dobiVrednostDKV(…) --- za datoteko, kolono, vrstico izprazniTabelo(…) --------- praznenje tabele napolniTabelo((..) ---- -----napolni tabelo napraviMostIzTabele() --- napravi most datoteke za tabele vrniMostIzTabele(..) ---- --vrne podatke iz datotek v tabele …….. preveriEnakostTabelnihPodatkov() ----- preverjanje enakosti
Marjan Gubensek
Interno delovno gradivo
29
Kombinacija ANT + DbUnit lahko izgleda nekaj takega :
<property file="build.properties"/> <property file="connection.properties"/>
………….
<echo message="Export tabel"/> <echo message="Uporabnik: ${connection.user}"/> <echo message="Podatki iz baze : ${connection.url}"/> <xdbunit driver="${connection.drive r.ant}" url="${connection.url.ant}" userid="${connection.user}" password="${connection.password}"> <export dest= "dbunitExportTabelPrimera.xml">
Kot rezultat dobimo vsebino vseh tabel v eni xml datoteki. Seveda se lahko odločimo tudi za kakšno drugo taktiko – npr vsaka tabela v svojo xml datoteko.
Marjan Gubensek
Interno delovno gradivo
30
V izbrani točki lahko sprožimo polnitev podatkov iz xml datotek v bazne tabele
<echo message="Polnitev tabel"/> <echo message="Uporabnik: ${connection.user}"/> <echo message="Podatki iz baze : ${connection.url}"/> <xdbunit driver="${connection.driver.ant}" url="${connection.url.ant}" userid="${connection.user}" password="${connection.password}">
ali le odstranitev podatkov iz tabel
<echo message="Delete tabel"/> <echo message="Uporabnik: ${connection.user}"/> <echo message="Podatki iz baze : ${connection.url}"/> <xdbunit driver="${connection.driver.ant}" url="${connection.url.ant}" userid="${connection.user}" password="${connection.password}">
Seveda mora biti sprožilec na bazi napisan pravilno !
Marjan Gubensek
Interno delovno gradivo
31
Metode razširitve
In kako napravimo nekaj podobnega v testnem programu z uporabo napisanih metod v razširitvah ?
package irc.gubi.tdd.test.testGubiDbTestCase; import irc.gubi.tdd.dbogrodja.GubiDbTestCase; import junit.swingui.TestRunner; public class VrniMostGubiDbTestCase extends GubiDbTestCase { public VrniMostGubiDbTestCase(String name) { super(name); } public void testvrniMost() throws Exception { napraviMostIzTabele(); // od obrobja proti sredidi - zraven je še varovanje …………….. vrniMostIzTabele(); // Od sredine proti obrobju - zraven je še varovanje } public static void main(String[] args) { String args2[] = { "-noloading", "irc.gubi.tdd.test.testGubiDbTestCase.VrniMostGubiDbTestCase"};TestRunner.main }}
Ker gre za prototipno obliko rešitve, jo spremlja varovanje podatkov z obširnim izpisom dogajanja pri poteku testov, ter dodatnem varovanju podatkov. Nekaj takega bi lahko napravili tudi neposredno z uporabo DbUnit-a. Takrat bi bilo občutno več vrstic kode (glej DbUnit domačo stran). To bi torej naj bila poenostavitev za nekatere ponovljive in v različnih situacijah nastopajoče primere.
Marjan Gubensek
Interno delovno gradivo
32
Poženi »build«
build.xml je privzeta vrednost imena za ANT zagon izgradnje (build-a) sistema. Kritični trenutek je, ko preidemo na »ročno« upravljanje build.xml zato, ker hočemo ali moramo postaviti svoj scenarij izgradnje sistema in nam ne zadostujejo nekateri poenostavljeni avtomatizmi. S tem vstopimo v novo globino kompleksnosti in seveda v nov nabor možnosti. V build.xml napravimo načrt opravil in medsebojnih odvisnosti, po katerih se izgradi sistem z zagonom ANT-a. Pri tem lahko vključimo tudi izvajanje testov. Build moramo biti sposobni pognati ročno ali pa ga vključiti v kakšne kasn ejše avtomatizme s tem, da ga ovijemo v višje postopke. Najradikalnejši je načrt, v katerem izgradimo sistem od »nule«, vključno z baznimi definicijami in do javanske dokumentacije. Dokler zadeve ne obvladujemo se zadovoljimo z bolj »per partes« prijemi. V primeru sem to napravil v naslednjem smislu:
<echo message=""/> <echo message="Test123DB za TDD"/> <echo message="-----------------------"/> <echo message=""/> <echo message="Cilji:"/> <echo message=""/> <echo message="Izbris datotek in direktorijev prevedenih programov in generirane dokumentacije"/> <echo message="Prevod programov za projekt Tocketest"/> <echo message="Prevod programov za projekt bcProjektDb"/> <echo message="Prevod programov za projekt mojDBunit"/> <echo message="Prevod vseh programov za izvajanje testov"/> <echo message="Zagon testa samo za projekt Tocketest.."/> <echo message="Zagon testa samo za projekt bcProjektDb"/> <echo message="Zagon vseh testov na drevesu iz izbranega vozlišča :${test123db.home}/TestiranjeEnot" /> <echo message="Generiranje javanske dokumentacije"/> <echo message=""/>
Marjan Gubensek
Interno delovno gradivo
33
Opisati moramo medsebojno odvisnost posa meznih ciljev, ki je v tem konkretnem primeru bila naslednja: ……….. <property file="build.properties"/> <property file="connection.properties"/> ……………..
classname="org.dbunit.ant.DbUnitTask"
Torej, če izberemo cilj »doc« se bo postopek začel v zadnjem odvisnem cilju, kar je »pozdrav«. Izbrani cilj bo dosežen v kolikor so uspešno doseženi vsi predhodni cilji.
Marjan Gubensek
Interno delovno gradivo
34
V skici je narisano v katerih točkah lahko poženemo test: za posamezen test pr imera za posamezne skupine testnih primerov prek build.xml z izbiro cilja - v primeru na skici je bil izbran »testZagonSkupaj« z vključitvijo build.xml v nek buil.bat in podobno connection.url=jdbc:oracle:thin:@localhost:1521:xe connection.user=cc conne ction.password=******* connection.bc.password=******* connection.shema=cc connection.service=xe connection.xml.sufixPricakovano = _pricakovano.xml connection.dirExtracti= u:/gubi/gubilog/tddextractiTabel/ connection.dirVarovanja= u:/gubi/gubilog/tddvarovanja/ connection.dirMostVarovanja= E:/jdevstudio10133/jdev/mywork/Test123DB/TestiranjeEnot/ connection.mostTabela= MOSTTABELE connection.mostTabelaKolona = IMETABELE connection.scriptLokacija=E:/jdevstudio10133/jdev/mywork/Test123DB/ScripteSQLplus/ -- za ANT -connection.url.ant=jdbc:oracle:thin:@//localhost:1521/xe connection.driver.ant=oracle.jdbc.driver.OracleDriver
Marjan Gubensek
Interno delovno gradivo
35
Poglej oziroma dobi rezultat build-a
Slika prikazuje primer, ko smo na neki celoti ( npr projektu ) uspešno odpravili vse napake ( Errors ) in vse padce ( Failures ) na vključenih testih. Na začetku v splošnem delu gradiva je slika z neuspešnim zagonom zaradi različnih napak.
Marjan Gubensek
Interno delovno gradivo
36
Spodnji primer kaže, kako vključimo vse napisane teste v neko izvajanje. S tem se lahko izognemo eksplicitnemu navajanju testov v TestSuite, oziroma to napravimo npr samo za ročni zagon. <echo message="Zagon vseh testov na drevesu iz izbranega vozlišča :${test123db.home}/TestiranjeEnot" /> <mkdir dir="${tests.reporting.dir}"/> <junit printsummary="false" errorproperty ="testiZagonSkupaj.failed" failureproperty ="testiZagonSkupaj.failed"> <sysproperty key="jbo.debugoutput" value="file"/> …………… <junitreport todir="${tests.reporting.dir}">
Vidimo, da je pomemben poimenovalni dogovor. Ta je lahko ohlapnejši npr. - ime mora imeti Test, ali trši npr. - ime se mora končati s Test. Ker želimo poročilo o testu in to predvsem kadar ta pade, je pomemben »if« v predzadnji vrstici. Iz primerov je razvidno dobro sodelovanja JUnit in ANT.
Marjan Gubensek
Interno delovno gradivo
37
4. ZAKLJUČEK – PROGRAMERSKI
V splošnem je namen testiranja ugotoviti ali se obnaša rešitev v skladu s pričakovanji ali ne. V kolikor testi niso programsko podprti, je pri ponavljanju le teh potrebno mnogo pripravljalnih del, v nekaterih primerih pa ponovitve niso niti mogoče. V takšni ali drugačni obliki nas torej čakajo progr amsko podprti testi in sicer: White box testi, s katerimi bomo preizkušali ali program daje pričakovane rezultate v nekem pozitivnem funkcionalnem scenariju delovanja (glej poglavje Program ne deluje pravilno); Black box testi, s katerimi bomo p reizkušali ali se program ne zruši v izjemnih situacijah in ali je interna struktura optimalna. Ostrejši kot bodo kriteriji po kakovosti delovanja programja, ostrejše bodo zahteve po testiranju. Točka zaokreta je nedvoumno situacija, ko ne moremo odpravljati napak po principu kurative v reaktivnem scenariju delovanja – to je, ko pomeni padec programa padec sistema kot celote s posledično mnogo večjo škodo, kot bi bil strošek proaktivne zagotovitve večje kakovosti. Problema se lotimo korakoma od manj zaht evnih do bolj zahtevnih uporab in priprav. 1. 2.
neposredna uporaba že vgrajenega v jDeveloper; dodatna uporaba Oracle inkorporiranih odprto kodnih ogrodij in Oraclovih razširitev le teh. (prek OTN, npr JUnit za poslovne komponente); 3. dodatna uporaba odprto kodn ih ogrodij (npr. DbUnit); 4. lastnih razširitev ogrodij; 5. lastnih ogrodij (teoretično). V praksi se sedaj naslonimo na različna ogrodja. Splošno priporočilo je, da napravimo svojo razširitev ogrodja, ki predstavlja novo plast v arhitekturi. V to plast potem umestimo skupne stvari za določen problemski krog tako, da imamo večjo stopnjo prilagodljivosti in ponovne uporabe skupnih rešitev – torej konkretno, da z eno potezo spremenimo obnašanje vsega kar se naslanja na določeno plast. To seveda ni nujno, včasih tudi mogoče ni racionalno, saj nam povečuje kompleksnost. Moramo pa razumeti čemu se odpovedujemo, v kolikor tega ne napravimo. Že v sedanjih relativno enostavnih razmerah imamo na dnevnem redu, da neka programska enota zaradi raznih odvisnosti in sklopljenosti začne delovati nepravilno, Marjan Gubensek
Interno delovno gradivo
38
ker je pač nekdo nekje nekaj spremenil, in to napačno. Teoretično tega naj nebi bilo več, praktično pa nas bo spremljalo še naprej. Zato je dobro zavrteti test v nekih časovnih intervalih. In če pride do napake v naši kodi, jo pač čim hitreje odpravimo, če je nekje drugje jo skušamo anticipirati skozi razširitveno plast navezave. Vprašanje je namreč, kdaj bo napaka odpravljena na izvornem mestu. Problem dodatno zaostruje vedno večja navezava na tuje komponente in storitve, med katerimi bodo lahko nekatere nezanesljive. V nekem ekstremu bi lahko pred vsako uporabo programa zahtevali možnost preverbe, ali ta deluje pravilno ( v skladu s pričakovanji). Začeti je potrebno in je tudi edino možno z enostavnejšimi primeri od 1 naprej.
5. ZAKLJUČEK - SPLOŠEN
Glede na naše izkušnje in prakso predstavlja obravnavana problematika radikalen zaokret - tako v miselnem, metodološkem in tehnološkem smislu. Zato je potrebno vedno imeti vsaj implicitne meje globin in širin uporabe novosti, da ne bi prihajalo do navzkrižnih negacij v konkurenčnosti različnih ciljev. Pri tem gre predvsem za razmerje med potrebnim vloženim delom za doseganje količine pokritosti nekih funkcionalnosti in odnos med vloženim delom za doseganje kakovosti rešitev v okviru razpoložljivih virov. Oblike testiranja kjer test vršimo prek testnega programa ima posledično v nekem ekstremu podvojitev obsega kode. Zato kritiki TDD-ja pravijo »še več kode – še več napak«. Vendar v kolikor je problem kakovosti zaostren do te mere, da ga ne moremo več obvladovati »očno in ročno«, je to edina možna pot. Zelo verjetno ne bomo izvajali kakšnega ortodoksnega testiraja po TDD-a evangelijih, ampak se bomo morali zadovoljiti z manjšimi koraki testiranja v smeri TDD-a. Zavedati se moramo, da je to white box nivo in je refaktorizacija sestavni del. V kolikor nas zanimajo zgolj funkcionalnosti v pozitivnih scenarijih zadostuje black box preizkušanje. Pri tem se bolj osredotočimo na količino in zavestno znižujemo kakovost. V to nas mnogokrat prisili diktat prakse. Nedvoumno moramo problematiko najprej obvezno poznati, da lahko razumemo dogajanja in da ostajamo komunikativni s skupnostmi iz področja našega delovanja. Dobro je, da imamo pri roki določeno mašinerijo (metode) in da jo uporabimo vsaj v situacijah, kjer je to nedvoumno koristno in utemeljeno. (»testiranje s namenom« ) Tako je smiselno, da če že izvajamo neko testiranje, da ga podvržemo pogoju ponovljivosti in obnovljivosti podatkov z različnimi scenariji testnih podatkov. S tem poskušamo zmanjšati preveliko nedoločenost pri raznih »probavanjih«. V tem smislu je ključna naslonitev na ogrodje DbUnit, ali na kakšno drugo ogrodje, ki daje podobno oporo.
Marjan Gubensek
Interno delovno gradivo
39
Obseg naloge, predvsem v prevedbi na enoto izvajanja, je rela tivno velik. Pri tem gre glede na naše razmere za niz novosti, skozi katere se je potrebno prebijati postopno. Osredotočil sem se na ključne elemente in se lotil raznih zaprek. Na koncu sem le nekako na mašini pognal najenostavnejše primere in tako zadostil radovednosti. Predvsem v delu, ki se navezuje na podatke, so tudi v prototipni obliki pokriti zahtevki iz naloge. Prav tako naj bi bil viden obris celote, ter gradirani možni začetki. Na koncu še stavek obarvan z osebnim pogledom : Obravnavana problematika je »le pika na i« v nekem novem kontekstu razvijanja programskih rešitev. Piko na i običajno pišemo tako, da najprej napišemo vertikalno črto v i in nato dodamo piko.
6. VIRI
jDeveloper JUnit ANT DbUnit Automation for the people: Remove the smell from your build scripts Automation for the people: Continuous testing Java Exteme Programming Cookbook - poglavjeJUnit U:\GUBIIRC\IRC-TDD-PREDSTAVITEV U:\GUBIIRC\Ogrodja v IRC okolju in smeri razvoja Test123DB primer v jDeveloperju Opomba – interna delovna gradiva na U:\GUBIIRC vsebujejo moje poglede na problematike obravnave, zato imajo le omejeno skladnost s pogledi iz drugih vidikov, oziroma le te dopolnjujejo. To so delovna gradiva.
Marjan Gubensek
Interno delovno gradivo
40