Bernard Atelšek TESTNO VODEN RAZVOJ PROGRAMSKIH REŠITEV Diplomsko delo Maribor, september 2011
Bernard Atelšek
TESTNO VODEN RAZVOJ PROGRAMSKIH REŠITEV
Diplomsko delo
Maribor, september 2011
I
Diplomsko delo univerzitetnega študijskega programa
TESTNO VODEN RAZVOJ PROGRAMSKIH REŠITEV
Študent: Bernard Atelšek
Študijski program: UN ŠP – računalništvo in informatika
Smer: Informatika
Mentor: red. prof. dr. Marjan Heričko
Lektorica: Irena Žunko, prof.
Maribor, september 2011
II
III
ZAHVALA
Zahvaljujem se mentorju – red. prof. dr.
Marjanu Heričku za pomoč in vodenje pri
pisanju diplomskega dela.
Posebna zahvala velja staršem, ki so mi
omogočili študij. Hvala tudi lektorici.
IV
TESTNO VODEN RAZVOJ PROGRAMSKIH REŠITEV
Ključne besede: informacijski sistemi, agilni razvoj, testno voden razvoj,
avtomatizirano testiranje, testiranje enot
UDK: 659.2:004(043.2)
Povzetek
Diplomsko delo predstavlja testno voden pristop k razvoju programskih rešitev (TDD).
Podali smo definicije in koncepte ter navedli razloge za nastanek te metode. Opisali
smo tudi agilne metode razvoja programske opreme ter predstavili preoblikovanje kode,
saj gre za pomemben del TDD. V nadaljevanju smo se posvetili testiranju – predvsem
avtomatiziranemu. Navedli smo izbrana ogrodja za testiranje enot (xUnit) in definirali
še druge vrste testiranj. Diplomsko delo pa podaja praktični primer razvoja programske
opreme po metodi TDD. Med samim razvojem smo merili posamezne aktivnosti in jih na
koncu tudi primerjali s podobnim eksperimentom.
V
TEST-DRIVEN SOFTWARE DEVELOPMENT
Key words: information systems, agile development, test driven development,
test automation, unit testing
UDK: 659.2:004(043.2)
Abstract
This thesis presents test-driven development (TDD). We describe concepts, definitions
and try to explain the reasons behind the formation of this development method. We
also describe agile software development methods with refactoring as one of the main
ingredients of TDD. Furthermore we focused mainly on testing, especially test
automation. We mentioned a few frameworks for unit testing (xUnit) and other testing
techniques. The accumulated theoretical knowledge was used to develop a prototype
project with TDD. During the process of development we measured separate activities
and compared the time distribution with a similar experiment.
VI
VSEBINA
1 UVOD ...................................................................................................................... 1
2 TESTNO VODEN RAZVOJ ................................................................................. 3
2.1 KRATKA ZGODOVINA RAZVOJA PROGRAMSKIH REŠITEV .................................... 3
2.2 AGILNI MANIFEST .............................................................................................. 4
2.3 PRINCIPI AGILNIH METOD .................................................................................. 4
2.4 EKSTREMNO PROGRAMIRANJE (XP) .................................................................. 5
2.4.1 Razširjeno ekstremno programiranje (XP2) ................................................ 7
2.5 DOMENSKO NAČRTOVANJE ................................................................................ 7
2.6 TESTNO VODEN RAZVOJ ..................................................................................... 9
2.6.1 Mejniki TDD ................................................................................................. 9
2.6.2 Definicija in umestitev TDD ....................................................................... 10
2.6.3 Razvojni cikel testno vodenega razvoja ...................................................... 13
2.6.3.1 Napiši test enote, ki pade .................................................................... 15
2.6.3.2 Poskrbi, da bo test uspešen ................................................................. 16
2.6.3.3 Preoblikuj ........................................................................................... 16
2.6.4 Razvoj voden s prevzemnimi testi ............................................................... 18
2.6.5 Vedenjsko voden razvoj .............................................................................. 20
2.6.6 Eksperimentalno voden razvoj.................................................................... 22
2.6.7 TDD in zastarela koda ................................................................................ 23
2.6.7.1 Analiziranje sprememb ....................................................................... 24
2.6.7.2 Priprave na spremembe ...................................................................... 24
2.6.7.3 Testno voden vnos sprememb ............................................................ 25
2.6.8 Čista koda ................................................................................................... 25
2.6.9 Prednosti in slabosti TDD .......................................................................... 26
2.6.10 Testi kot dokumentacija .......................................................................... 27
2.6.11 Zunanja in notranja kvaliteta ................................................................. 28
2.6.12 Kaj testirati? ........................................................................................... 29
2.6.13 Principi TDD .......................................................................................... 30
2.6.14 TDD-anti-vzorci ..................................................................................... 31
3 AVTOMATIZIRANO TESTIRANJE ................................................................ 33
VII
3.1 CILJI AVTOMATIZIRANEGA TESTIRANJA ........................................................... 34
3.1.1 Testi naj bi izboljšali kvaliteto .................................................................... 34
3.1.2 Testiranje naj bi pomagalo razumeti sistem, ki ga razvijamo .................... 35
3.1.3 Testi naj bi zmanjšali tveganje ................................................................... 35
3.1.4 Testi naj bi bili enostavni za izvajanje ....................................................... 36
3.1.5 Testi naj bi bili enostavni za pisanje in vzdrževanje .................................. 37
3.1.6 Testi naj bi zahtevali minimalno vzdrževanje ............................................. 37
3.2 MANIFEST AVTOMATIZIRANEGA TESTIRANJA .................................................. 38
3.3 PROBLEMI PRI AVTOMATIZACIJI ....................................................................... 38
3.4 KATERE TESTE AVTOMATIZIRATI? ................................................................... 41
3.5 TESTIRANJE ENOT ............................................................................................ 43
3.5.1 Testiranje in izolacija ................................................................................. 44
3.5.2 Ogrodja za testiranje .................................................................................. 45
3.5.3 Poimenovanje testov ................................................................................... 46
3.5.4 Struktura testa enote ................................................................................... 47
3.5.5 Pokritost kode s testi ................................................................................... 47
3.6 INTEGRACIJSKO TESTIRANJE ............................................................................ 48
3.7 SISTEMSKO TESTIRANJE ................................................................................... 49
3.8 PREVZEMNO TESTIRANJE ................................................................................. 50
3.9 VEDENJSKO TESTIRANJE .................................................................................. 52
3.10 A/B TESTIRANJE .............................................................................................. 53
4 SODOBNI KONCEPTI RAZVOJA PROGRAMSKIH REŠITEV ................ 55
4.1 VZOREC MVVM ............................................................................................. 55
4.2 OBRAT KONTROLE ........................................................................................... 56
4.3 VRIVANJE ODVISNOSTI .................................................................................... 56
4.4 STALNA INTEGRACIJA ...................................................................................... 56
4.5 NAČRTOVALSKI VZORCI .................................................................................. 57
4.6 PRINCIPI SOLID .............................................................................................. 57
4.7 PRAKTIČNI NASVETI ZA PROGRAMIRANJE IN TESTIRANJE ................................. 58
5 PRAKTIČNI PRIMER RAZVOJA PO METODOLOGIJI TDD .................. 59
5.1 PREDSTAVITEV IN IDEJA .................................................................................. 59
VIII
5.2 ITERACIJA NIČ ................................................................................................ 60
5.3 REŠITEV ........................................................................................................... 63
6 ANALIZA .............................................................................................................. 71
6.1 ZAKLJUČEK ..................................................................................................... 77
7 SKLEP ................................................................................................................... 79
8 VIRI ....................................................................................................................... 80
9 PRILOGE .............................................................................................................. 84
9.1 SEZNAM SLIK ................................................................................................... 84
9.2 SEZNAM TABEL ................................................................................................ 86
9.3 NASLOV ŠTUDENTA ......................................................................................... 86
9.4 KRATEK ŽIVLJENJEPIS...................................................................................... 87
IX
UPORABLJENE KRATICE
(D)VCS (Porazdeljen) Sistem za upravljanje konfiguracije (angl. (Distributed) Version
Control System)
AAA Sintaksa Pripravi-Izvedi-Preveri (angl. Arrange-Act- Assert)
API Programski vmesnik (angl. Application Programming Interface)
ATDD Razvoj voden s prevzemnimi testi (angl. Acceptance Test Driven Development)
BDD Vedenjsko voden razvoj (angl. Behavior Driven Development)
CI Stalna integracija (angl. Continuous integration)
DDD Domensko načrtovanje (angl. Domain-driven design)
DI Vrivanje odvisnosti (angl. Dependency Injection)
DIP Princip obrata odvisnosti (angl. Dependency Inversion Principle)
DSL Domensko specifičen jezik (angl. Domain-specific language)
EDD Eksperimentno voden razvoj (angl. Experiment Driven Development)
GUI Grafični vmesnik (angl. Graphical User Interface)
IDE Integrirano razvojno okolje (angl. Integrated Development Environment)
IOC Obrat kontrole (angl. Inversion of Control)
ISP Princip vmesniške ločitve (angl Interface Segregation Principle)
LSP Princip Liskove zamenjave (angl. Liskov Substitution Principle)
MVC Vzorec Model-Pogled-Upravljalec (angl. Model-View-Controller)
MVVM Vzorec Model-Pogled-Vmesnik (angl. Model-View-ViewModel)
OCP Princip odprtosti in zaprtosti (angl. Open Closed Principle)
SCM Sistem za upravljanje konfiguracije (angl. Software Configuration Management)
SOLID Kombinacija akronimov SRP, OCP, LSP, ISP, DIP
SRP Princip ene odgovornosti (angl. Single Responsibility Principle)
SUT Sistem, ki se testira (angl. System Under Test)
TDD Testno voden razvoj (angl. Test Driven Development)
TFP Programiranje po testu (angl. Test-First Programming)
TLD Testiranje po implementaciji (angl. Test-Last Development - TLD)
V&V Verifikacija in validacija (angl. Verification and Validation)
XP Ekstremno programiranje (angl. Extreme Programming)
XP2 Razširjeno ekstremno programiranje
XUNIT Ogrodje za testiranje enot (npr. JUnit, NUnit)
Testno voden razvoj programskih rešitev Stran 1
1 UVOD
Programska rešitev je rezultat nekega razvojnega procesa. Vsak projekt pa mora biti
ustrezno voden, saj se v nasprotnem primeru bodisi ne konča v roku ali pa preseže
načrtovane stroške. Tako kot celoten projekt mora biti planiran in ustrezno strukturiran
tudi sam razvojni proces. Gre za zaporedje aktivnosti, ki so rezultat preteklih izkušenj in
predstavljajo dobre prakse [17].
Bolj kot sama implementacija predstavlja problem odnos med razvijalci in naročniki.
Zbiranje in usklajevanje zahtev je še posebej zahtevno, saj obe strani razpolagata z
različnimi predznanji. Edina konstantna stvar v računalništvu pa so spremembe. Vse to pa
morajo razvijalci pričakovati in tudi biti sposobni se odzivati na spremembe. Tudi pisanje
dokumentacije zahteva ogromno režije, sploh pri spremembah. Dejstvo pa je, da nihče ne
bere te dokumentacije – vsak razvijalec raje preleti izvorno kodo ali pa dokumentacijo
samo na hitro prelista. Poleg tega pa je nihče ne piše rad in je samo dodatna obremenitev
za že tako zaposlene razvijalce. V današnjih časih pa je, roko na srce, glavni cilj hitrost.
Zahtevajo se boljše programske rešitve v krajšem času [16].
Zaradi teh izzivov so nastale t. i. agilne metode. Gre za čisto nasprotje togih procesov, ki
so se uporabljali v preteklosti. Njihov cilj je boljše odzivanje na spremembe, samo
najnujnejše pisanje dokumentacije ter testiranje. Vse to pa se izraža v hitrejšem razvoju,
boljši programski kodi in v znosnejših razmerah na delovnem mestu.
Najbolj znana agilna metoda je ekstremno programiranje (XP) [15]. Pokriva planiranje,
testiranje, vzdrževanje in namestitev programskih rešitev. Večji projekt razdeli na več
manjših, za vsako iteracijo pa se sproti določijo funkcionalnosti, ki bodo implementirane
[15].
Programiranje po testu (angl. Test First Programming – TFP) je koncept, kjer že pred
implementacijo napišemo test. To nam zagotavlja kvalitetnejšo implementacijo, saj imamo
na voljo več informacij in časa za načrtovanje. Količina testov se poveča z vsako dodano
funkcionalnostjo, vsi pa morajo uspeti.
Testno voden razvoj programskih rešitev Stran 2
Če to kombiniramo s preoblikovanjem kode, dobimo testno voden razvoj (angl. Test
Driven Development – TDD). Pri tej metodologiji razvoj narekujejo testi, ki so
avtomatizirani. Večinoma se testiranje kode izvaja na t. i. xUnit ogrodjih za testiranje.
Rezultat takšnega razvoja je običajno minimalna programska koda, ki zadosti vsem
zahtevam.
TDD sam po sebi sicer ničesar ne zagotavlja, promovira pa sodoben način razvoja
programskih rešitev [15].
Osnovni cilji diplomske naloge so: prvič – umestitev, definiranje in predstavitev testno
usmerjenega razvoja programske opreme, drugič – razložiti avtomatizirano testiranje ter
tretjič – razviti pilotski projekt s pomočjo vsega naštetega. Namen je podrobno definirati
TDD (koncept, zgodovino, prednosti in slabosti), testiranje ob razvoju (ogrodja, principi)
ter takšen razvojni cikel preslikati v prakso in tako razviti konkreten projekt. Diplomska
naloga bo promovirala agilnost, preoblikovanje in testiranje kode ter tako prispevala k
razvoju konkurenčnejših programskih rešitev.
Prvo poglavje diplomskega dela predstavlja uvod, v katerem so opisani cilji in splošno
področje raziskav. V drugem poglavju preko agilnih metod hitro pridemo do testno
usmerjenega razvoja – ga definiramo, umestimo in predstavimo. V nadaljevanju
predstavimo še preoblikovanje kode – definicije in dobre prakse. V naslednjem poglavju
obravnavamo testiranje. Poudarek je na avtomatiziranem testiranju in ogrodjih za
testiranje. V predzadnjem poglavju pa obravnavamo praktični primer – razvoj pilotskega
projekta po teoriji, predstavljeni v tej diplomi – od ideje, načrtovanja, vmesnikov do
implementacije. V zaključku povzamemo izkušnje in ugotovitve, zaključimo pa s
seznamom slik, tabel in uporabljenih virov.
Testno voden razvoj programskih rešitev Stran 3
2 TESTNO VODEN RAZVOJ
V tem poglavju bomo postopoma poskušali definirati testno usmerjen razvoj programske
opreme (TDD). Opisali bomo nastanek, definirali nekatere povezane pojme in opisali
razvojni cikel. Razložili bomo prednosti in slabosti ter primerjali z razvojnimi procesi, ki
so se uporabljali pred tem.
2.1 Kratka zgodovina razvoja programskih rešitev
Na začetku so bili vsi projekti brez ustreznega vodenja. Razvoj je običajno temeljil na
enem razvijalcu, ki je dobro poznal uporabljeno tehnologijo in sam problem. Jasno je, da
takšni projekti, ki niso bili ustrezno načrtovani, običajno niso bili ustrezni za stranko.
Logična posledica je bila, da se je začel razvoj programske opreme formalizirati [18].
Proces razvoja se je razdelil na posamezne korake in pripadajoče postopke. Različne
metodologije so se začele pojavljati med leti 1970 in 1980. Kmalu pa so postale tudi zelo
obsežne. Nekatere programske hiše so razvile svoje lastne metodologije in jih dolgo tudi
uspešno prodajale. Vse je bilo do potankosti razdelano in primerno za večje ekipe.
Potrebno je bilo voditi veliko dokumentacije, kar je zahtevalo ogromno režije. V razvojnih
ekipah pa je vsaka oseba nepogrešljiva, niti nihče ne piše in vzdržuje dokumentacije prav
rad. Uvesti je bilo potrebno nekaj »lažjega«, kar bo primerno za manjše skupine in bo bolj
prilagodljivo. Nastale so agilne metode [19].
Testno voden razvoj programskih rešitev Stran 4
2.2 Agilni manifest
Vse agilne metode temeljijo na t. i. agilnem manifestu. Njegova vsebina narekuje smernice
in podaja bistvo agilnih metod [1].
Posamezniki in sodelovanje med njimi je pomembnejše od procesa in orodij.
Delujoči sistem je pomembnejši kot dokumentacija.
Sodelovanje naročnika je pomembnejše od pogajanj glede pogodbe.
Odzivnost na spremembe namesto strogega upoštevanja planov.
Pred tem so bile v ospredju vrednote, ki so tukaj zapisane na desni strani alinej. Od sedaj
najprej pa je poudarek na vrednotah zapisanih na levi strani, kar pa ne pomeni, da desnih ni
več.
2.3 Principi agilnih metod
Agilnost lahko na daljše opišemo s pomočjo naslednjih principov [1].
Glavni cilj je zadovoljstvo stranke. To se doseže z zgodnjimi in pogostimi izdajami
programske opreme.
Spremembe so dobrodošle, celo pozno v razvojnem ciklu. Agilni proces izkorišča
spremembe za doseganje konkurenčne prednosti naročnika.
Pogoste izdaje z delujočimi funkcionalnostmi, vsakih nekaj tednov do vsakih nekaj
mesecev. Boljše je, če so izdaje bolj pogoste.
Naročniki in razvijalci morajo biti ves čas razvoja projekta v stiku.
V središču projekta naj bodo motivirani posamezniki. Nuditi jim je potrebno okolje
in podporo, ki jo potrebujejo, ter jim zaupati, da bodo delo opravili kvalitetno.
Najučinkovitejši način prenosa informacij v razvojni ekipi je neposreden pogovor.
Delujoča programska rešitev je največji pokazatelj napredka projekta.
Agilni proces zagovarja stalen razvoj. Naročniki, razvijalci in uporabniki morajo
vzdrževati stalen tempo dela.
Stalno je treba posvečati pozornost tehnični odličnosti in dobremu načrtovanju, saj
povečujeta agilnost.
Preprostost – sposobnost izločanja nepotrebnega dela – je poglavitna.
Testno voden razvoj programskih rešitev Stran 5
Najboljše arhitekture, zahteve in načrti izhajajo iz dobro organiziranih razvojnih
ekip.
Vsake toliko časa naj razvojna ekipa pregleda možnosti, kako bi bila še bolj
učinkovita. Glede na ugotovitve pa potem izpili in prilagodi način dela.
2.4 Ekstremno programiranje (XP)
Gre za eno bolj popularnih agilnih metodologij, ki se je že dokazala za zelo uspešno v
podjetjih po vsem svetu. Glavni razlog je to, da poudarja zadovoljstvo stranke. Namesto
enkratne predaje programske opreme v daljni prihodnosti, ekstremno programiranje stalno
dostavlja nove funkcionalnosti, ki so usklajene z zahtevami stranke. Kot agilna metoda se
je sposobna odzivati na spremembe tudi pozno v razvojnem ciklu [2].
Zagovarja skupinsko delo, kjer so razvijalci, stranke in vodstvo enakovredni člani ekipe.
Vsak pa je odgovoren za svoje področje. Zagotavlja okolje, v katerem so lahko vsi v ekipi
maksimalno produktivni. XP vpliva na razvoj programskih rešitev preko petih ključnih
vrednot [16]:
komunikacija,
preprostost,
odzivnost,
pogum in
spoštovanje.
Pomembno je, da so razvojne ekipe majhne (do 12 ljudi), saj se le tako izognemo dolgim
sestankom in ohranimo komuniciranje enostavno in jasno. Za XP je značilno tudi sprotno
določanje funkcionalnosti, ki se bodo implementirale kot naslednje. Določi jih stranka
glede na lastne potrebe.
Razvojni cikel je razdeljen na več kratkih iteracij, v katerih se implementirajo nove
funkcionalnosti. Na takšen način pa hitro dobivamo jasno sliko o napredku samega
projekta in tako lažje ukrepamo. Bistvo pa je seveda delujoča programska oprema, ki je v
skladu z željami stranke. To se pa zagotavlja s testiranjem enot in prevzemnimi testi. Vse
pa naj bo avtomatizirano. Seveda pa XP ni zacementiran tako kot je predlagan v literaturi.
Nasprotno – celo spodbuja se prilagajanje. Na takšen način lahko izberemo in uporabimo
Testno voden razvoj programskih rešitev Stran 6
dobre prakse iz vseh razvojnih metodologij in jih kombiniramo z XP. S tem dobimo
agilnost v pravem pomenu besede – na vseh točkah razvoja.
Razvojni proces je definiran s štirimi spremenljivkami, od katerih tri določi stranka, eno pa
razvijalec [16]. To so:
stroški,
čas,
kakovost,
obseg.
Pomembno je omeniti še priporočene prakse – in relacije med njimi (Slika 1).
Slika 1: Prakse ekstremnega programiranja [20]
Testno voden razvoj programskih rešitev Stran 7
2.4.1 Razširjeno ekstremno programiranje (XP2)
Tudi ekstremno programiranje se je skozi čas razvijalo in se prilagajalo. Leta 2004 je bilo
posodobljeno in dopolnjeno z nekaterimi dodatnimi praksami. To pa še ni vse, saj se sedaj
prakse delijo na primarne in napredne (Slika 2). Uvedba slednjih je zahtevna in nevarna, če
prej nismo osvojili povezanih primarnih praks. Seveda pa je najboljše, če preizkusimo in
potem po potrebi korigiramo. Število primarnih praks se je povečalo na 13, naprednih pa je
11. To nam pove, da je metodologija ustrezna in široko uporabljana [15].
Slika 2: Prakse pri XP2 [15]
2.5 Domensko načrtovanje
Programske rešitve se najpogosteje uporabljajo pri avtomatizaciji procesov, ki obstajajo v
realnem svetu. Prav tako lahko rešujejo konkretne poslovne probleme. Ti procesi in
problemi predstavljajo domeno teh programskih rešitev. Čeprav so aplikacije sestavljene iz
programske kode, je potrebno nanje gledati širše – ne samo kot na skupek objektov in
metod [45].
Za preproste probleme je to lahko ustrezna rešitev, za kompleksnejše pa je to nemogoče.
Za dobre programske rešitve je potrebno dobro poznavanje ozadja, ki ga aplikacija rešuje.
Potrebno je podrobno poznavanje domene problema, ki se rešuje.Domene pa seveda ne
poznajo računalniški arhitekti, analitiki, še manj pa razvijalci. Najboljše jo poznajo tisti, ki
Testno voden razvoj programskih rešitev Stran 8
so zaposleni v panogi, ki jo domena pokriva. Najboljše pa je takšne ljudi poiskati kar v
okolici naročnika. Tako dobimo dostop do virov, ki posedujejo znanje, podrobnosti, trike
in poznajo potencialne prepreke našega problema. Razviti je potrebno izdelek, ki se bo
brez težav vključil v okolje domene [45].
Najboljše je, da programska rešitev odraža domeno. Vsebuje naj glavne koncepte,
elemente domene in vse povezave med njimi. Rešitev naj modelira domeno. Če temu ni
tako, bo vzdrževanje zelo zahtevno.
Poznavanje domene pa ni mogoče kar tako preslikati v programsko kodo. V ta namen je
potrebno narediti abstrakcijo domene. To je model znanja, ki ga posedujejo strokovnjaki
domene. Predstavlja načrt, ki je na začetku precej pomankljiv, a ga sčasoma dopolnjujemo,
ko zadeve postajajo jasnejše tudi razvojni ekipi. To lahko naredimo s pomočjo diagramov,
skrbno napisane programske kode ali pa kar z vezanim besedilom.
Model predstavlja naše razumevanje ciljne domene. Nemogoče je kar naenkrat spoznati
vse. Informacije si je potrebno organizirati in sistematizirati. Celoto pa razdeliti na manjše
dele in jih povezati v logične module. Manjše enote lažje obvladamo, nekaj pa jih je
potrebno tudi izpustiti. Domena je namreč prevelika, da bi jo lahko celo vključili v model.
Pa tudi potrebe ni, da vključujemo enote, ki niso neposredno povezane z našim
problemom. Kaj vključimo in kaj ne pa je del načrtovanja oz. razvojnega procesa.
Dobljeni model je potrebno izraziti in deliti s člani razvojne ekipe. Znanje in informacije je
potrebno deliti. To lahko naredimo s pomočjo grafike ali besedila. Priporočljivo je
definirati tudi jezik domene, ki ga uporabljajo in razumejo vsi vpleteni [45].
Ko je model zadovoljiv, se prične delo na načrtovanju programske kode. Dobra koda je
pomembna sestavina končnega izdelka. Priporočljiva je uporaba načrtovalskih vzorcev.
Dobre tehnike kodiranja naredijo kodno osnovo čisto in lažjo za vzdrževanje.
Razvoj takšnih rešitev poteka s pomočjo tradicionalnih ali agilnih metodologij. Domensko
vodeno načrtovanje (angl. Domain-driven design – DDD) kombinira načrtovalske in
razvojne prakse, ki vodijo do boljših programskih rešitev.
Testno voden razvoj programskih rešitev Stran 9
2.6 Testno voden razvoj
Testno voden razvoj (angl. Test-driven development – TDD) je drugo ime za
programiranje po testu (angl. Test-First Programming – TFP). Še vedno pa marsikoga
zavede, da gre za testiranje. V osnovi gre za razvojno metodologijo, ki je zelo enostavna, a
hkrati tako radikalno drugačna. Ideja je, da se testi napišejo že pred dejansko
implementacijo. Ta praksa se večini zdi skregana z logiko, ampak po krajšem premisleku
vidimo, da temu ni tako [3], [4].
2.6.1 Mejniki TDD
Sam po sebi pa TDD ni nič neverjetnega ali revolucionarnega. Je samo kombinacija metod,
ki so znane že dolgo časa, a so bile mogoče različno uporabljane. Paralele lahko iščemo že
na začetku našega obstoja.
Tabela 1: Mejniki TDD [5], [6]
Letnica Pomembni dogodki
Pred našim
štetjem
Ožgan kos mesa kot prvi uspešen testni primerek. Jamski človek opisuje drugemu, kako
mu je uspelo zanetiti ogenj in kakšen je rezultat.
1815–1852 Ada Lovelace kot prva nevede pokaže na koncept TDD s preverjanjem rezultatov, pa
čeprav še ni imela na voljo računalnika.
1959–1963 Programerji Merkurjevega vesoljskega programa so uporabljali neko vrsto TDD za
programiranje luknjanih kartic.
1989 Gerry Weinberg in Gary Goldberg sta programirala po testu luknjane kartice.
Ward Cunningham je napisal prvo ogrodje za testiranje – Fit-like.
1994 Kent Beck napiše ogrodje za testiranje – Sunit.
1995–1999 Kent Beck demonstrira TDD Wardu Cunninghamu.
Koncept ekstremnega programiranja (XP) pride v javnost iz projekta C3.
Izid knjige o preoblikovanju kode – Refactoring: Improving the Design of
Existing Code.
Izid knjige o XP – Extreme Programming Explained.
Pojavi se prvi vodič za TDD (Xp Immersion One konferenca).
2000
Pojavi se spletna stran JUnit.org.
Začne se razvoj ogrodij NUnit in JUnit.
Prvi članek o oponašanju objektov.
2001
Nat Pryce napiše knjižnico za nadomeščanje objektov v Rubyu in jo predela še
za Javo. Nastane jMock.
Chris Stevenson predstavi poimenovanje za teste v obliki stavkov.
2002
TFP se preimenuje v TDD.
Izid prve knjige o TDD – Unit Tests mit Java – v nemščini.
Kent Beck napiše knjigo Test Driven Development By Example.
Razvojno okolje Eclipse dobi podporo za JUnit.
Testno voden razvoj programskih rešitev Stran 10
2003
Izid knjige Unit Testing In Java: How Tests Drive the Code.
Izid knjige Test Driven Development a Practical Guide.
2004 Izid knjige Extreme Programming Adventures.
Podpora za testiranje v Ruby On Rails.
Izid knjig Refactoring To Patterns, Junit Recipes, Working Effectively With
Legacy Code.
Martin Fowler objavi članek Mocks aren't stubs.
2005 Izid knjige Agile Java.
JUnit dobi podporo za trditve (angl. Asserts).
Microsoft po svoje definira TDD.
2007 Izid knjige Test Driven Practical TDD and Acceptance TDD for Java
Developers.
Predstavljen koncept BDD (angl. Behavior-driven development).
2008 Koncept razvoja vodenega s prevzemnimi testi (ATDD).
Prvi patent na temo TDD (Parameterized test driven development).
2009 Izid knjig Acceptance Test Engineering Guide, Agile Testing – A Practical
Guide for Testers and Agile Teams.
Microsoft izda ogrodje ASP.NET MVC.
Izid knjige ASP.NET MVC 1.0 Test Driven Development: Problem – Design –
Solution.
Izid knjige Clean Code: A Handbook of Agile Software Craftsmanship.
2010 Izid knjige Test-Driven Development: An Empirical Evaluation of Agile
Practice.
Izid knjige Domain-Specific Model-Driven Testing.
Izid knjige Growing Object-Oriented Software, Guided by Tests.
Microsoft izda ogrodji ASP.NET MVC 2 in MVC 3 BETA.
2.6.2 Definicija in umestitev TDD
Skoraj vsak programerski projekt predstavlja nekaj, kar še nihče izmed razvijalcev v ekipi
ni delal prej (na lokalni ali svetovni ravni). Običajno gre za kombinacijo vpletenih ljudi,
uporabljene tehnologije in problemske domene aplikacije. Ne glede na vložen trud vsi
projekti (razen rutinskih) vsebujejo elemente presenečenja. To se zelo pogosto zgodi ravno
pri projektih, ki bi morali prinesti največji dobiček [4].
Običajno se razvijalci poleg same problematike implementacije srečujejo tudi s problemi
uporabe tehnologije. Tudi če odlično poznajo tehnologijo, jih aplikacija, ki se razvija,
postavlja pred nove izzive, dileme in jih tako potiska na neznan teren. S podobnimi
problemi pa se srečujejo tudi stranke in uporabniki. Z razvojem projekta se morajo tudi
Testno voden razvoj programskih rešitev Stran 11
sami učiti in podrobno spremljati dogajanje. Potrebne so analize in formalizacije procesov,
ki jim do sedaj niso posvečali prevelike pozornosti.
Vsak, ki je vpleten pri programerskem projektu, se mora učiti skladno z napredkom
projekta. Vsi morajo delati skupaj, da lahko razumejo problematiko in rešujejo tekoče
probleme. Vsakemu je jasno, da bo prišlo do sprememb, nihče pa ne ve točno, kakšne bodo
te spremembe. Potrebni so mehanizmi, ki bodo pomagali spopadati se s spremembami –
pričakovati nepričakovano. Razvoj aplikacij je učni proces [4].
Najboljši pristop je, če ekipa za učenje in spoznavanje sistema uporablja povratne
informacije. Vse tako pridobljene izkušnje pa potem uporabi pri nadaljnjem razvoju. Ekipe
naj razvijajo aplikacije v ponavljajočih ciklih (Slika 3). V vsakem ciklu dodajo nekaj novih
funkcionalnosti, potem pa iz povratnih informacij dobijo sliko o napredku. Delo se razdeli
na časovne enote, v katerih se izvaja analiza, načrtovanje, implementacija in namestitev
novih funkcionalnosti.
Namestitev na novo dodanih funkcionalnosti ob koncu vsakega cikla je ključnega pomena.
Takrat imajo razvijalci priložnost, da vidijo, kako se sistem obnaša v realnih razmerah. To
je priložnost za ocenjevanje napredka, odpravljanje napak in za prilagajanje poteka razvoja
glede na pridobljene informacije. Brez namestitve povratna informacija ni popolna.
Razvoj v ciklih na vseh stopnjah razvoja omogoča lažje odkrivanje napak. Zunanji (večji)
razvojni cikli razkrivajo pomanjkljivosti, ki so se zgodile v notranjih (manjših) ciklih.
Povratne informacije teh ciklov pokrivajo različne dele razvijajočega se sistema in
razvojnega procesa. Notranji cikli so bolj osredotočeni na tehnične lastnosti sistema.
Zunanji pa bolj na razvojno ekipo in samo organiziranost [4].
Slika 3: Razvoj v ciklih in odvisnosti med njimi [9]
Testno voden razvoj programskih rešitev Stran 12
Načinu razvoja z gnezdenimi cikli pravimo inkrementalen in iterativen (Slika 4).
1. Inkrementalnost – sprotno dodajanje novih funkcionalnosti. Tako je vsaka
funkcionalnost vedno implementirana do konca, celoten sistem pa je vedno
pripravljen za namestitev.
2. Iterativnost – postopna implementacija funkcionalnosti glede na sprotne odzive
dokler stranka ni zadovoljna.
Slika 4: Inkrementalni in iterativni razvoj [21]
Prej ko lahko dobimo kakršnokoli povratno informacijo o sistemu, ki se gradi, boljše je.
Veliko razvojnih ekip izdaja nove različice tedensko ali celo dnevno. To jim zelo poveča
frekvenco kvalitetnih povratnih informacij od dejanskih uporabnikov.
Za zanesljiv in dinamičen razvoj programskih rešitev se moramo držati dveh tehničnih
pravil. Stalno testiranje omogoča dodajanje novih funkcionalnosti, brez da bi pokvarili že
dodane. Testiranje naj bo avtomatsko, saj je ročno prepočasno in nepraktično. Na takšen
način zmanjšamo stroške razvoja, namestitve in spreminjanja sistema.
Testno voden razvoj programskih rešitev Stran 13
Kot drugo pa naj bo izvorna koda kar se da enostavna in čista, da jo je lažje razumeti in
vzdrževati. Razvijalci običajno porabijo dosti več časa za branje izvorne kode kot pa za
dejansko implementacijo. V to je treba vlagati veliko truda, zato stalno preoblikujemo
izvorno kodo. Tako izboljšamo in poenostavimo arhitekturo, odstranimo podvajanje kode
in jasneje pokažemo, kaj ta koda počne. Testi pa skrbijo, da s preoblikovanjem ne
pokvarimo delujočih funkcionalnosti.
TDD pa stvari obrne na glavo. Najprej napišemo teste, potem pa šele izvorno kodo.
Testiranje tukaj nima več običajne vloge, saj postane testiranje del načrtovanja. S pomočjo
testov razvijalci definirajo, kaj naj bi izvorna koda delala. Na takšen način ločimo logično
od fizičnega načrtovanja. TDD daje stalne povratne informacije o kvaliteti naše
arhitekture, to pa običajno pomeni čistejšo izvorno kodo in bolj modularno arhitekturo.
Z avtomatiziranimi testi pa si tudi zagotovimo varovalo, ki nam daje samozavest za
spremembe. Vsakdo se strinja, da je zelo priporočljivo imeti napisane teste enot. Bolj kot
je programska koda pokrita s testi enot, bolj je prilagodljiva. Kajti velik nabor testov
zagotavlja nizko stopnjo napak v programski kodi. To pa še ni vse. Omogoča tudi lažje
dodajanje novih funkcionalnosti ter vnašanje sprememb v kodo. Običajna pokritost kode s
testi naj bi bila okoli 80 % [2].
Največ se TFP uporablja v agilnih programerskih ekipah, najdemo pa ga tudi drugod. Je
tudi osnovna praksa pri ekstremnem programiranju (XP), priporoča pa se uporaba tudi v
Crystal (družina agilnih metodologij, ki se osredotočajo na projektno varnost) in Scrum
(iterativna in inkrementalna metodologija za vodenje agilnih projektov) projektih.
2.6.3 Razvojni cikel testno vodenega razvoja
Razvojni cikel je v osnovi zelo enostaven (Slika 5, Slika 6) [4]:
1. Napišite test.
2. Poskrbite, da bo test uspešen.
3. Preoblikujte kodo.
Zlato pravilo TDD pa je, da ne dodajamo novih funkcionalnosti, ne da bi poprej napisali
test, ki seveda pade [7]. Podrobneje si bomo te točke pogledali v nadaljevanju.
Testno voden razvoj programskih rešitev Stran 14
Testno-razvojni cikel v praksi zgleda nekako takole [2]:
1. Napišite test.
2. Prevedite ga. Test se ne prevede, ker še niste implementirali kode, ki jo test kliče.
3. Implementirajte samo toliko, da se bo test lahko prevedel.
4. Poženite test in preverite, da test pade.
5. Implementirajte samo toliko, da bo test uspešen.
6. Poženite test in preverite, če je uspešen.
7. Preoblikujte kodo za večjo jasnost.
8. Ponavljajte vse te korake.
Programer začne s pisanjem testa, ki se prvič niti ne prevede. To je razumljivo, saj klicana
programska koda niti ne obstaja. Glede na napisan test potem postavi ogrodje – razrede in
metode, ki jih test kliče. V drugem poskusu bi se naj test prevedel, ampak mora pasti.
Tukaj šele prvič pridemo do implementacije tistega dela funkcionalnosti, za katerega smo
prej napisali test. Implementira se samo toliko, da je test uspešen. Programska koda naj bo
čista in preprosta. To olajša kasnejše preoblikovanje. Tokrat pa bi moral test biti uspešen.
Po potrebi preoblikujte kodo, da bo bolj jasna in lepša. Ta postopek ponavljajte, dokler ni
funkcionalnost implementirana in so vsi testi uspešni.
S TDD bomo lažje in hitreje pisali programsko kodo. Seveda bo ta koda tudi boljša – saj je
testirana – in enostavnejša – vsebuje samo to, kar potrebujemo. Boljša koda pa je
najpomembnejša, saj potrebuje malo vzdrževanja in je lažje prilagodljiva. Ob zagonu testa
takoj dobiš jasno povratno informacijo o uspešnosti. Takšen način razvoja postane hitro
samoumeven. Uspešni testi dajejo razvijalcem potrebno samozavest in spodbujajo
spremembe v kodi [4].
Slika 5: Primerjava klasičnega in TDD-cikla ter alternativ [7]
Testno voden razvoj programskih rešitev Stran 15
Slika 6: TDD v osnovi [4]
2.6.3.1 Napiši test enote, ki pade
V prvem koraku v resnici naredimo dosti več, kot pa samo napišemo test. Sprejmemo
načrtovalske odločitve. Načrtujemo programski vmesnik (angl. Application Programming
Interface – API) – vmesnik do funkcionalnosti, ki ga testiramo. Na takšen način se
prisilimo, da dobro premislimo, kako bo ta koda uporabljena. Analogija je podobna kot pri
sestavljanju sestavljanke. Tam šele glede na ostale kose sestavljanke lahko določimo,
kateri kos manjka. Programerji smo uporabniki lastne programske kode, zato je zelo
pomembno, kako jo oblikujemo. Najboljše lahko ocenimo zasnovo tako, da jo začnemo
uporabljati. S pisanjem testov dosežemo ravno to – dobimo želeno povratno informacijo.
Pozornost je treba posvečati tudi granularnosti naših testov. Testi naj bodo čim manjši –
napišemo jih v nekaj minutah. Sama implementacija pa tudi ne bi smela presegati teh
časovnih okvirov. Razvoj s pomočjo testov je zelo učinkovit in zagotavlja modularno
kodo, ki je testirana [7].
Načrtovanje pa ne pomeni samo strukturiranja. Pomeni tudi prilagajanje programske
opreme trenutnim potrebam. Stranka naj definira, katere funkcionalnosti potrebuje zdaj.
Ostale se naj beležijo posebej in bodo implementirane, ko jih bo stranka potrebovala.
Na takšen način vedno gradimo tisto, kar se potrebuje in tako nadzorujemo aplikacijo in
njeno arhitekturo. Testi nas varujejo pred zastoji v razvoju, hkrati pa vidimo, v katero smer
moramo iti.
Testno voden razvoj programskih rešitev Stran 16
2.6.3.2 Poskrbi, da bo test uspešen
V drugem koraku napišemo samo toliko izvorne kode, da bo test uspešno opravljen. Test,
ki pade, nam sporoča, da napisana koda ne počne tistega, kar se od nje pričakuje.
Implementacija običajno ne vzame več kot par minut in tako imamo kmalu spet delujoč
sistem.
Ena glavnih idej pri TDD je, da testi narekujejo, kaj je potrebno implementirati, da bomo
napredovali pri razvoju. Ne gre samo za pisanje izvorne kode. Potrebno je zadostiti
natančnim in nedvoumnim zahtevam, ki jih narekuje test. Uspešni testi so dokaz o
napredku pri razvoju.
Naš glavni cilj je, da naši testi kar se da hitro postanejo uspešni. To pogosto pomeni
izvorno kodo, ki je daleč od idealne. S tem se soočimo, ko smo dosegli želeno obnašanje in
nam tudi testi to potrjujejo [7].
2.6.3.3 Preoblikuj
Zadnji korak je, ko na našo zasnovo pogledamo nekoliko bolj od daleč in razmislimo, kako
bi jo izboljšali. Zaradi preoblikovanja nam TDD daje vzdrževano izvorno kodo. Brez tega
bi bil TDD samo dober način, kako ustvariti veliko kode, ki je sicer testirana, ampak zelo
grda. Takšna koda vpliva na našo produktivnost pri nadaljnjem delu z njo.. Napisani testi
dajejo zagotovilo, da s preoblikovanjem ne bomo česa pokvarili. Zato nikakor ne smemo
zanemarjati ali celo pozabiti na preoblikovanje [7].
Preoblikovanje je proces spreminjanja programske opreme na takšen način, da ne
spremenimo zunanjega obnašanja kode, ampak izboljšamo njeno notranjo strukturo. Je
priporočen način, kako očistimo kodo in zmanjšamo možnost za vnos novih napak. Pri
preoblikovanju izboljšamo arhitekturo kode, ki je že napisana [12].
Običajno se je najprej načrtovalo in potem kodiralo. Pri preoblikovanju pa delamo
nasprotno, lahko vzamemo slabo načrtovan oz. kaotičen sistem in ga spremenimo v dobro
oblikovan sistem. To naredimo z majhnimi koraki kot so: prestavimo kakšen atribut v drug
razred, izluščimo kakšno novo metodo ali prestavimo nekaj kode gor ali dol po razredni
hierarhiji. Skupen učinek teh malih sprememb lahko drastično izboljša obliko kode v
sistemu. Namen takšnega preoblikovanja je, da se izboljša razumljivost kode in olajša
nadaljnje delo z njo.
Testno voden razvoj programskih rešitev Stran 17
Preoblikovanje je odgovor na spremembe pri implementaciji. Načrtovanje poteka ves čas
razvoja. Med gradnjo sistema se učimo, kako izboljšati obliko sistema. Na takšen način
dobimo programsko opremo, katere struktura je ustrezna celoten čas razvoja. Med
razvojem vedno bodisi dodajamo nove funkcionalnosti ali pa preoblikujemo že obstoječo
kodo. Česa se lotimo prej, je odvisno od naše ocene, kaj nam bo lažje. Brez preoblikovanja
se struktura programa postopoma slabša. Samo z branjem kode postane vedno težje videti
celotno sliko, kako je aplikacija načrtovana. Preoblikovanje pa je kot čiščenje in urejanje
kode. To slabšanje strukture pa je kot verižna reakcija, ki nas slej kot prej pripelje do
brezizhodne situacije. Dodajanje novih funkcionalnosti zahteva ogromno dela predvsem
zaradi podvajanja kode. Podvojena koda ne vpliva bistveno na hitrost izvajanja, a drastično
vpliva na njeno razumevanje in spreminjanje. Z odstranitvijo podvajanja zagotovimo, da je
koda jasna in edinstvena, kar pa je tudi bistvo dobrega načrtovanja.
Razvijalci pri pisanju kode vedno pozabljamo na možnost, da bo to kodo poleg nas bral oz.
uporabljal še kdo drug. V tem primeru je bolj kot optimalna koda pomembno, da je koda
berljiva in kar se da jasna. V nasprotnem primeru vzdrževanje tretje osebe zahteva občutno
preveč časa in napora. S preoblikovanjem lahko kodo naredimo bolj berljivo in tako
jasneje izraža svoj namen. Uporabimo pa ga lahko tudi v drugo smer. S preoblikovanjem si
pomagamo pri neznani kodi in jo postopoma oblikujemo glede na naše razumevanje. Tako
postopoma spoznavamo podrobnosti načrtovanja in dosežemo višjo stopnjo razumevanja
kot bi jo pa drugače. Z razumevanjem pa opazimo tudi skrite hrošče v kodi. Preoblikovanje
izboljšuje kvaliteto, načrtovanje, berljivost in hrošče v kodi. Logična posledica tega pa je,
da je razvoj hitrejši.
Pomembno pa je seveda vedeti, kdaj uporabiti preoblikovanje. Najpogosteje to naredimo,
ko moramo dodati kakšno novo funkcionalnost. V tem primeru preoblikujemo zaradi
boljšega razumevanja obstoječe kode ali pa ker nam obstoječa koda ne omogoča
enostavnega dodajanja. Na podoben način ga uporabimo tudi pri iskanju hroščev.
Preoblikujemo pa tudi pri pregledu kode, v kolikor je dovolj enostavno. Pregledi so
koristni za vse, saj se seznanijo z obstoječim in lahko predlagajo tudi kakšne popravke.
Običajno se to izvaja v manjših skupinah ali pa se programira v parih [12].
Testno voden razvoj programskih rešitev Stran 18
2.6.4 Razvoj voden s prevzemnimi testi
Mamljivo je kar začeti pisati teste enot za našo aplikacijo, a takšnemu projektu manjka
ključna prednost TDD-metodologije. Kaj nam koristi odlična in testirana izvorna koda, če
je nikjer ne uporabimo ali pa se je ne da integrirati k ostalemu sistemu. Osnovno pravilo
TDD nam narekuje, da napišemo test, ki pade. Tako vemo, kje začeti oz. končati pisanje
izvorne kode.
Vsako novo implementacijo funkcionalnosti začnemo s pisanjem prevzemnega testa (Slika
7), ki odraža funkcionalnost, ki jo želimo dodati. Ko je test uspešen, smo končali. Takšna
praksa se imenuje – razvoj voden s prevzemnimi testi (angl. Acceptance Test Driven
development – ATDD). Prevzemni test nas vodi do situacije, ko bomo dejansko
potrebovali implementacijo, ki jo poskušamo dodati. Dodajamo samo tiste funkcionalnosti,
ki so dejansko potrebne. Znotraj prevzemnega testiranja pa imamo običajen TDD-razvojni
cikel. Zunanji cikel je prikaz dejanskega napredka, ki ga lahko pokažemo. Nabor testov pa
nas varuje pred napakami pri spreminjanju sistema. Običajno traja kar nekaj časa, da je
prevzemni test uspešen, zato običajno ločimo med prevzemnimi testi za funkcionalnosti, ki
jih šele razvijamo (niso vključeni v izdaje), in tistimi, ki so že končane (morajo vedno
delovati). Notranji cikel pa je namenjen razvijalcem. Pomaga vzdrževati kvaliteto kode.
Funkcionalnosti, katerih testi enot padejo, ne bi smelo biti v našem sistemu za upravljanje
konfiguracije [4].
Slika 7: Širši pogled na TDD oz. ATDD [4]
Testno voden razvoj programskih rešitev Stran 19
Kadar je možno, pa naj prevzemni test preveri še sistem kot celoto, brez direktnega
klicanja notranjih funkcionalnosti. Takšen test celote komunicira z našim sistemom od
zunaj: preko uporabniškega vmesnika, pošilja sporočila kot drugi sistemi, kliče spletne
storitve, obdeluje izpise ipd. Celotno obnašanje sistema zajema tudi komunikacijo z
zunanjim okoljem. To je običajno najtežji in najbolj tvegan korak, zato ga pogosto
zanemarjamo. Običajno se izogibamo prevzemnim testom, ki preverjajo notranje delovanje
sistema, če je le možno. Opravimo jih samo, če želimo doseči pohitritev in že imamo nabor
testov za celoto.
Za nas celota pomeni več kot samo testiranje sistema od zunaj (Slika 8). Raje imamo, da
testiranje celote preveri tako sistem kot sam proces gradnje in namestitve. Vsak prispevek
v sistem za upravljanje konfiguracije naj bi občasno prenesel zadnjo verzijo sistema,
prevedel in testiral enote programske kode (avtomatizirana gradnja). Potem bi zadevo
integriral in zapakiral ter namestil v okolje, podobno pravemu. Na koncu pa bi še preveril
celoten sistem preko zunanjih dostopnih točk. To je precej dela, ampak mora biti storjeno
večkrat v življenjskem ciklu programske opreme. Veliko korakov tega cikla zahteva
spretnost in je podvrženo napakam, zato je idealen za avtomatizacijo [4].
Slika 8: Celotno razvojno okolje [6]
Sistem lahko namestimo, ko so vsi prevzemni testi uspešni. To naj bi nam dalo vedeti, da
vse deluje kot mora. Tukaj pa imamo še zadnji korak, to je produkcija. Sploh v večjih
podjetjih je gradnja namestitvenega sistema samo začetek izdaje sistema. Preden je sistem
na voljo končnim uporabnikom, ga običajno še testirajo (Slika 9), predajo posameznim
oddelkom in koordinirajo z ostalimi izdajami. Lahko pride tudi do drugih stroškov, ki niso
ravno tehnične narave. Izobraževanje, trženje in druge pogodbene obveznosti. To otežuje
cikel izdaj, zato moramo razumeti naše celotno tehnično in organizacijsko okolje [4].
Testno voden razvoj programskih rešitev Stran 20
Slika 9: Preverjanje ustreznosti v agilnih moštvih [22]
2.6.5 Vedenjsko voden razvoj
Problem pri testiranju notranje strukture objekta je, da preverjamo, kaj objekt je, namesto,
da bi preverjali, kaj počne. Slednje je dosti bolj pomembno. Podobno velja tudi za
naročnike. Ne zanima jih, kako je kakšna stvar narejena, ampak da se končna aplikacija
obnaša tako, kot so pričakovali [39].
Vedenjsko voden razvoj (angl. Behavior driven development – BDD) se ne osredotoča na
strukturo, ampak na obnašanje. Takšen način pa se odraža na vseh stopnjah razvoja. Tako
se bolj osredotočamo na medsebojno delovanje: uporabnikov in sistema, objektov in na
koncu še med strukturami objektov. Komunikacija predstavlja enega večjih problemov v
razvojnih ekipah. BDD to rešuje s poenostavljanjem jezika, ki se uporablja za opisovanje
scenarijev (Slika 10), v katerih se bo aplikacija uporabljala. BDD spodbuja sodelovanje
med razvijalci, testerji in drugimi poslovnimi objekti. Osredotoča se na jasno zajemanje
zahtev o želenem obnašanju aplikacije, preko pogovora z naročniki. Testni primeri so
zapisani v naravnem jeziku, ki ga lahko razumejo vsi udeleženi. Gre za kombinacijo
naravnega jezika in jezika domene. Takšen zapis omogoča razvijalcem, da se osredotočijo
na razloge, zakaj se piše izvorna koda, ne pa na njene tehnične podrobnosti. Zmanjšuje
vrzel med jezikom domene in programskim jezikom, v katerem je aplikacija napisana [40].
Testno voden razvoj programskih rešitev Stran 21
Slika 10: BDD-cikel [39]
BDD še ni bil formalno definiran. Opisujejo ga kot agilno metodologijo druge generacije,
ki izhaja iz uporabniškega vmesnika. Temelji na zahtevah, ki jih poda množica ljudi, ki jo
sestavljajo tako uporabniki kot naročniki. Proces je visoko avtomatiziran, produkt pa zelo
razširljiv. Opisuje komunikacijski cikel z dobro definiranimi rezultati, ki prinaša delujočo
programsko opremo, kot je bila zamišljena [40].
BDD predstavlja TDD, kakršen bi le-ta moral biti od samega začetka. Ne gre zgolj za
evolucijo, ampak obstaja tudi nekaj razlik. BDD zajema zahteve celotne aplikacije, ne pa
samo posameznih enot. Zahteve se zapišejo v obliki uporabniških zgodb. Ključno pa je
sodelovanje vseh vpletenih strani [41].
Principi BDD [39]
Dovolj je dovolj. Implementirano naj bo vse, kar je bilo zahtevano na samem
začetku. Kar je več, predstavlja potrato časa. To se nanaša tudi na avtomatizacijo.
Ne poskušajmo avtomatizirati vsega.
Dostavi vrednost. Ukvarjajmo se samo s tistimi stvarmi, ki naročniku prinašajo
korist ali pa nam povečujejo produktivnost za takšen razvoj.
Vse je obnašanje. Pa naj bo to na nivoju kode, aplikacije ali sistema. Za opis
obnašanja lahko uporabimo iste konstrukte ne glede na nivo.
Testno voden razvoj programskih rešitev Stran 22
2.6.6 Eksperimentalno voden razvoj
Metodologiji TDD in BDD sta postali zelo razširjeni. Še vedno pa se lahko zgodi, da na
takšen način zamudimo kakšno poslovno priložnost ali celo, da kakšnega projekta ne
dokončamo. Ti dve metodologiji namreč ne moreta dati odgovora na sledeči vprašanji:
Kako se meri uporabnost aplikacije? Kako dobiti povratne informacije od uporabnikov?
Ankete niso pravi odgovor. Povratne informacije je potrebno dobiti na podoben način kot
ga ima TDD za kodo [42].
Eden glavnih problemov pri razvoju aplikacij je planiranje smernic nadaljnjega razvoja. Tu
imajo glavno vlogo različna subjektivna mnenja in nadrejeni. Takšne odločitve pa
zahtevajo dejstva, podprta s konkretnimi podatki. Vsako funkcionalnost predstavimo kot
hipotezo, zanjo napišemo eksperiment in jo pustimo, da zbira podatke v realnem okolju.
Dobljene informacije uporabimo za nadaljnji razvoj (Slika 11).
To pa je glavni cilj eksperimentalno vodenega razvoja (angl. Experiment driven
development – EDD). Gre za razvoj programske opreme, ki temelji na dejstvih. Vse se
začne pri idejah, na katerih se potem izvajajo meritve. Nič ni hipotetično, saj vse temelji na
dejanskih uporabnikih. Proces je iterativen. EDD pomaga določiti, katere funkcionalnosti
razviti in kam jih postaviti. Razkrije nam, kaj bo v prihodnosti postalo specifikacija. TDD
in EDD sta dobra naveza za kvalitetno in aktualno programsko opremo [42].
Slika 11: EDD-razvojni cikel [42]
Testno voden razvoj programskih rešitev Stran 23
2.6.7 TDD in zastarela koda
Zastarela koda pomeni izvorno kodo, ki jo je napisal nekdo drug. Takšna koda pa zaudarja,
pa ne zato, ker bi bila stara, ampak ker je težko berljiva in verjetno vsebuje napake. Niti pa
ni možno vedeti, če smo s kakšno spremembo pokvarili kaj drugega. Sinonim za zastarelo
kodo je koda brez testov [7].
Večina razvijalcev ne dela na novih projektih, ampak vzdržuje, razvija dalje ali integrira
zadeve z že obstoječimi sistemi, ki so stari že tudi po več let ali celo desetletij. To pomeni,
da moramo TDD uporabljati na obstoječi izvorni kodi (Slika 12).
Proces je podoben kot pri projektih, ki se ne razvijajo po testih. Začnemo s pisanjem testa
za tisti del, ki ga hočemo spremeniti. To bo običajno pomenilo, da razdremo nekaj
odvisnosti brez testov, ki bi nam ščitili hrbet. Zaradi večje pazljivosti in učenja bomo na
začetku napredovali zelo počasi. To pa nas ne sme ustaviti, saj se bo razdiranje odvisnosti
in pisanje testov obrestovalo prej, kot si mislimo [7].
Slika 12: TDD in zastarela koda [7]
Proces je sestavljen iz treh faz [7]:
analiziranje sprememb,
priprave na spremembe,
testno voden vnos sprememb.
Testno voden razvoj programskih rešitev Stran 24
Proces začnemo tako, da moramo v delujoč sistem vnesti neke spremembe. Ko poznamo
spremembo, gremo skozi vse tri faze procesa in tako dobimo nov delujoč sistem z
vnesenimi spremembami.
2.6.7.1 Analiziranje sprememb
Pri analizi sprememb moramo najprej določiti točke sprememb (angl. change points). To
so tiste točke, ki jih moramo spremeniti v izvorni kodi, da implementiramo želene
spremembe. Takšna analiza je v splošnem enaka, pa čeprav gre za zastarelo kodo ali pa ne.
Edina razlika je ta, da je zastarela koda težje razumljiva od tiste, ki je dokumentirana s
pomočjo testov enot.
Ko smo določili, kam bomo vnesli spremembe, moramo poiskati odvisne točke (angl.
inflection points). Pravimo jim tudi testne točke, to so takšne točke v naši kodi, kjer lahko
zaznamo spremembe v delovanju sistema, ko smo spreminjali kodo v točkah sprememb.
Običajno se odvisne točke nahajajo v bližnji okolici točk, kamor smo vnašali spremembe.
Seveda pa to ni nujno tako. Odvisne točke so lahko tudi daleč stran ali celo preko mreže v
drugem sistemu. Odločamo se med enostavnostjo pisanja testov in gotovostjo, ki nam jo ti
testi nudijo.
Bližnje odvisne točke predstavljajo lokalne kontrolne točke brez odvečnih motenj.
Oddaljene pa običajno ujamejo še kakšen stranski učinek, ki ga nismo zaznali z našo
analizo. V zameno pa je potrebno vložiti več truda v pisanje testov, ker nimamo takšnega
dostopa in natančnih informacij, kot pa bi jih imeli za bližnjo odvisno točko. Ko smo
določili točke sprememb in odvisnosti, vemo, kaj moramo spremeniti in kje lahko
testiramo obnašanje [7].
2.6.7.2 Priprave na spremembe
V tej fazi napišemo teste za odvisne točke in tako zabeležimo trenutno obnašanje, preden
vnesemo spremembe. To lahko vključuje tudi razbitje odvisnosti med objekti, ampak
moramo biti previdni in izpostaviti odvisnosti, da jih lahko kontroliramo v naših testih.
Teste, ki pokrivajo odvisne točke, tipično imenujemo karakterizacijski testi. To pomeni, da
preverjajo trenutno funkcionalnost takšno kot je, ne glede, če je obnašanje pravilno ali ne.
Uporabljajo pa se tudi za preverjanje domnev, ki smo jih postavljali pri identifikaciji točk
sprememb. S tem se odpravimo v zadnjo fazo procesa [7].
Testno voden razvoj programskih rešitev Stran 25
2.6.7.3 Testno voden vnos sprememb
Ko smo zadovoljni s pokritostjo kode s testi odvisnih točk, dodamo še test za novo
funkcionalnost. Med samo implementacijo pa karakterizacijski testi (angl. characterization
tests) povedo, če smo karkoli pokvarili na obstoječi kodi, novi testi pa povedo, če smo
implementirali tisto, kar smo hoteli. Ob koncu, ko smo končali z implementacijo in so vsi
testi uspešni, pa varno preoblikujemo našo kodo, saj nas varujejo testi.
Glavna razlika v primerjavi z običajnim TDD-ciklom je, da moramo najprej napisati teste
za obstoječe obnašanje, potem šele za novo funkcionalnost. Pogosto pa moramo razbiti
tudi odvisnosti med objekti, pri katerih nas ne varujejo testi. To zahteva bolj natančno in
pazljivo delo [7].
2.6.8 Čista koda
TDD narekuje čim enostavnejšo in čisto programsko kodo. To pa seveda velja tudi za vse
ostale razvojne metodologije, saj smo se že vsi prevečkrat srečali s slabo in nepregledno
kodo. Čista koda je pomembna predvsem programerjem, ki neposredno delajo z njo. Vse
prevečkrat pa se zgodi, da preveč hitimo z implementacijo, bodisi zaradi rokov ali
preproste naveličanosti. Takšna koda je zelo težka za vzdrževanje, kar podaljša razvojne
cikle. Dodajanje novih funkcionalnosti pa ustvari še večji nered. Produktivnost začne
strmo padati in slej ko prej je potrebno zadevo načrtovati od začetka. Skrb za čisto kodo ni
le stroškovno učinkovita, ampak nujna, če hočemo preživeti kot razvijalci [11].
Najprej pa se moramo sploh vprašati, kaj čista koda sploh je? Definicija je drugačna za
vsakega razvijalca. Vzdrževanje čiste kode zahteva disciplino in uporabo nekaterih tehnik.
Vse je lažje, če imamo smisel za čisto kodo, v nasprotnem primeru pa se moramo
disciplinirati.
Koda naj bo elegantna in učinkovita. Jasno naj izraža logiko tako, da ni prostora za skrite
hrošče. Odvisnosti naj bodo minimalne, da je olajšano vzdrževanje, lovljenje napak
skladno z našo strategijo, hitrost pa blizu optimalne, da ne bodo drugi pokvarili kode z
nepotrebnimi optimizacijami. Čista koda dela samo eno stvar in to dobro.
Koda naj bo jasna in neposredna. Bere se naj kot dobro napisana proza. Nikoli ne skriva
programerjevih namer, ampak je polna abstrakcije in izraža jasno kontrolo.
Testno voden razvoj programskih rešitev Stran 26
Čista koda se lepo bere in jo lahko izboljšajo tudi drugi razvijalci. Pokrita je s testi enot in
prevzemnimi testi. Vsebuje pomenska imena. Izraža samo en način dela za eno stvar. Ima
minimalne odvisnosti, ki so jasno določene, in ponuja čist in minimalen API.
Čista koda že na pogled daje jasno vedeti, da jo je napisal skrben razvijalec. Na njej ni nič
očitnega, kar bi klicalo po izboljšavi. Da delaš na čisti kodi, veš, ko se obnašanje vsake
prebrane rutine izkaže za pričakovano [11].
Vse skupaj pa bi lahko strnili v nekaj alinej [23]:
vsi testi so uspešni;
ni podvajanja, vse je potrebno izraziti dovolj jasno;
koda je izrazna – izraža vse načrtovalske ideje sistema; pomenska imena metod in
razredov, razbijanje razredov in metod na več delov;
minimalno število razredov in metod, hitra uvedba majhnih abstrakcij.
Vse to v kombinaciji s standardi kodiranja, formatiranjem in komentiranjem kode in
preoblikovanjem kode programerjem zelo olajša delo in pomaga k boljšemu končnemu
produktu v vseh pomenih besede.
2.6.9 Prednosti in slabosti TDD
Med samim razvojem sistema TDD daje povratne informacije o kvaliteti implementacije
(»Ali deluje?«) in kvaliteti arhitekture (»Je dobro načrtovano?«). Na takšen način imamo
dvojno korist.
Prednosti [4], [8], [9]:
večja produktivnost razvijalcev;
boljša kvaliteta kode;
test je izvršljiv opis – lahko ga poženemo – kaj programska koda dela in nam
narekuje ogrodje za implementacijo;
redka uporaba razhroščevalnika;
TDD je več kot samo vrednotenje sistema;
razvoj v majhnih korakih nudi boljši nadzor;
testiranje zagotavlja večjo samozavest razvijalcev;
testiranje nas opozori na napake zelo hitro, kar zmanjša čas razvoja in stroške;
Testno voden razvoj programskih rešitev Stran 27
TDD vodi do bolj modularne, prilagodljive in razširljive kode ter manjših razredov
– rezultat tega so šibka sklopljenost in jasnejši vmesniki;
omogoča varno spreminjanje in dodajanje funkcionalnosti;
spodbuja ponovno uporabo in preoblikovanje kode;
večja možnost za uporabo načrtovalskih vzorcev, saj razvijamo v malih korakih;
enostavni testi poenostavijo pisanje kompleksne kode;
testi so najboljša dokumentacija in so ažurni;
testiranje preprečuje prekomeren razvoj, saj nam pove kdaj je neka funkcionalnost
dokončana;
večji poudarek na načrtovanju.
Pomanjkljivosti TDD [4], [8], [9]:
ni enostaven za začetnike, saj zahteva dosti vaje;
težko uporaben v okoljih, ki ne omogočajo popolne pokritosti s testi (uporabniški
vmesniki, podatkovne baze, omrežja, mobilne naprave);
težko je pridobiti podporo vodstva za TDD in jih prepričati, da pisanje testov ni
izguba časa;
slabo spisani testi postanejo velik problem pri vzdrževanju aplikacije;
stopnja pokritosti s testi in testiranje je težko ponovljivo v vsakem razvojnem ciklu
TDD – testi morajo biti ločeno popravljeni;
zaradi nedoslednosti se lahko pojavijo v naši kodi vrzeli – dobimo lažen občutek
uspešnosti;
razvijalec piše tako teste kot implementacijo. Vsaka napaka v razumevanju se tako
pojavi na dveh mestih;
veliko število uspešnih testov nam daje preveliko samozavest in se zato ne
poglobimo toliko v problematiko in druge vrste testiranj.
2.6.10 Testi kot dokumentacija
Dobro napisani testi enot so najenostavnejša oblika dokumentacije. Pravzaprav gre za
najboljšo možno dokumentacijo razredov. Pri TDD so testi sestavni del programske kode.
Dokumentacija ni več tako pomembna, saj se testi razvijajo skupaj s programom. Pisanje
testov se več kot izplača, saj za nekaj več vloženega truda dobimo kvalitetnejšo kodo in
manjšo potrebo po klasični dokumentaciji. Pri kvalitetnejši kodi pa seveda porabimo manj
Testno voden razvoj programskih rešitev Stran 28
časa za iskanje hroščev. Testi enot so bolj natančni kot vsaka druga dokumentacija. Prav
tako so tudi bolj uporabni, saj nudijo informacije o tem, kako bi naj bi posamezen delček
kode deloval. Najboljše pri vsem tem pa je, da so ažurni. Če jih pišemo sproti, potem
odražajo vse spremembe, ki so bile narejene na programski kodi.
Seveda pa to ni nadomestilo za klasično dokumentacijo. Še vedno jo potrebujemo za naše
stranke ali zase. Testi pa so brez dvoma najboljša oblika sistemske dokumentacije, saj
najmanj motijo samo implementacijo. So praktični in kratki, saj se ne ubadajo s trivialnimi
stvarmi [16].
2.6.11 Zunanja in notranja kvaliteta
Na to, kaj nam testi povedo o sistemu, pa lahko gledamo tudi drugače. Ločimo med
notranjo in zunanjo kvaliteto (Slika 13). Zunanja kvaliteta pove, kako dobro sistem služi
naročniku in uporabnikom. Notranja kvaliteta pa nam pove, kako dobro sistem služi
razvijalcem (enostavnost in možnost spreminjanja). Prav tako kot zunanja pa je pomembna
notranja kvaliteta, a jo je težje doseči, saj nam ravno ta omogoča soočanje s stalnimi
spremembami. Zelo pomembno pa je, da jo ohranjamo, saj poenostavi spreminjanje
obstoječega sistema in zmanjša tveganje, da bodo spremembe zahtevale ogromno
prilagoditev [4].
Prevzemno testiranje celote daje jasnejšo sliko o zunanji kvaliteti sistema, pisanje teh
testov pa nam pove, kako dobro razvojna ekipa pozna problemsko domeno. Ne pove pa
nam ničesar o kvaliteti programske kode. Pisanje testov za enote zagotavlja veliko
povratnih informacij o kvaliteti same kode, njihov zagon pa nam pove, če smo pokvarili
kakšen razred. Ne povedo pa nič o ustreznosti celotnega sistema. Integracijsko testiranje pa
spada nekam vmes.
Da nam lahko testiranje enot pomaga pri notranji kvaliteti, morajo biti razredi dobro
strukturirani, da jih lahko testiramo neodvisno. Test enote mora ustvariti objekt in vse
njegove pomožne objekte, vse testirati in ugotoviti, če je obnašanje ustrezno. Odvisnosti
morajo biti eksplicitne, enostavno zamenljive, in metode jasne, da so enostavne za klicanje
in preverjanje. To pomeni, da mora biti koda šibko sklopljena in zelo vezljiva. To z
drugimi besedami pomeni, da je dobro načrtovana [4].
Testno voden razvoj programskih rešitev Stran 29
Slika 13: Povratne informacije od testov [4]
Če se torej zadeve lotimo napačno in ne upoštevamo zgoraj napisanega, potem je pisanje
testov enot zahtevno in nerazumljivo. Programiranje po testu daje dragocene in takojšnje
povratne informacije o naši arhitekturi. V teh primerih nam pisanje testov ne diši, a vseeno
poskušamo. To pa je dobra priložnost, da natančneje pregledamo in popravimo
problematično strukturirano aplikacijo. Temu pravimo poslušanje testov [4].
2.6.12 Kaj testirati?
Test naj se napiše za vsako stvar, pri kateri se lahko karkoli zalomi. Običajno ne pišemo
testov za metode, ki vračajo ali nastavljajo vrednost nekega polja. Lahko pa jih, da se
prepričamo, če delujejo, sploh če so kompleksni. Ne pišemo jih niti za metode, ki samo
kličejo kakšno drugo metodo, če seveda ta klicana metoda ima test. V splošnem pa test
napišemo vedno, ko smo v dvomih. Boljše, da jih je preveč kot pa premalo. Napišemo jih
tudi preden gremo odpravljati kakšno napako, pomanjkljivost. Problem predstavljajo tudi
uporabniški vmesniki. Nemogoče jih je neposredno testirati s testi enot. Kar pa lahko
naredimo pa je, da kar se da ločimo poslovno logiko od uporabniškega vmesnika. Enako
velja tudi za spletne aplikacije. Na takšen način dosežemo solidno pokritost s testi. V
preteklosti pa so bile tudi težave pri testiranju na mobilnih telefonih, saj je bil pomnilniški
prostor omejen, orodja pa niso bila ustrezna. Oboje pa se je z leti spremenilo tako, da to
več ne predstavlja problema [16].
Testno voden razvoj programskih rešitev Stran 30
2.6.13 Principi TDD
Predstavimo nekaj principov, pridobljenih z uporabo prakse TDD [6].
1. Profesionalni razvijalci testirajo svojo kodo.
2. Potrebno je ločiti med kaj (test) in kako (koda).
3. Z avtomatiziranimi testi potrdimo nove funkcionalnosti.
4. Gre za spremembo v kulturi.
5. Da program dela, ni dovolj.
6. Poslušajte, kaj vam govorijo testi.
7. Delujoč sistem daje povratne informacije.
8. Osredotočite se na namen.
9. Ko si v dvomih, se malo ustavi.
10. Bistvo ni samo testiranje.
11. Stara koda je koda brez testov.
12. Razumevanje principov, ki so za praksami.
Testno voden razvoj programskih rešitev Stran 31
2.6.14 TDD-anti-vzorci
Pravi anti-vzorci se od slabih navad, praks in idej ločijo po tem, da posnemajo akcije,
procese ali strukture, ki se zdijo na prvi pogled koristni, a na daljši rok naredijo več škode
kot koristi. Vse to mora biti dokumentirano, preizkušeno v praksi in ponovljivo.
Tabela 2: Seznam anti-vzorcev [10]
Naziv Opis
Lažnivec
(The Liar)
Celoten test je uspešen za vse testne primere in deluje kot ustrezen, a ko boljše
pogledamo, ugotovimo, da sploh ne testira prave stvari.
Prekomeren trud
(Excessive Setup)
Test, ki zahteva veliko nastavljanja, da sploh lahko začnemo testiranje. Včasih
je potrebnih več sto vrstic kode, da se pripravi okolje za en test. Pri tem
sodeluje več različnih objektov, zaradi česar je težko preveriti, kaj se testira,
saj je zadeva nepregledna.
Velikan
(The Giant)
Test enote, ki sicer ustrezno testira objekt, a obsega preko tisoč vrstic in
vsebuje ogromno testnih primerov. To je lahko znak, da je testirani objekt
ogromen – vsemogočni objekt.
Namestnik
(The Mockery)
Včasih je nadomeščanje dobro in pride prav. Včasih pa se lahko razvijalec
izgubi v želji, da bi nadomestil vse, kar se ne testira. V temu primeru test
vsebuje toliko nadomestkov, ogrodij in ponaredkov, da se sploh ne testira tisto,
kar bi se moralo. V bistvu ti nadomestki vračajo tisto, kar naj bi se testiralo.
Inšpektor
(The Inspector)
Test enote, ki krši pravila ograjevanja z namenom, da bi dosegel 100 %
pokritost kode. Hkrati pa je tako prepleten z dejanskim objektom, da bo vsak
poskus preoblikovanja pokvaril test, vsaka sprememba na objektu pa mora biti
izražena v testu.
Nehvaležni ostanki
(Generous Leftovers)
Nek test generira podatke, ki so nekje shranjeni, drugi test pa te podatke
uporablja. Če je generiranje izvedeno na koncu ali pa ga sploh ni, bodo vsi
odvisni testi padli.
Lokalni junak
(The Local Hero)
Test, ki je odvisen od neke zadeve v razvojnem okolju. Test je uspešen na
razvojnih računalnikih, povsod drugje pa pade.
Nergač
(The Nitpicker)
Test, ki obdela celoten izhod podatkov, a v bistvu preverja le manjši delček
tega. Tako vedno obdeluje podatke, ki bi bili drugače nepomembni.
Najpogosteje pri testiranju spletnih aplikacij.
Tihi lovec
(The Secret Catcher)
Test, ki na prvi pogled sploh ničesar ne testira, saj ne vsebuje nobene izjave.
Problem pa se skriva v podrobnostih. Test se v resnici zanaša na izjemo, ki se
pojavi, ko pride do napake in pričakuje, da bo ogrodje za testiranje ujelo
izjemo in jo javilo kot napako.
Izmikant
(The Dodger)
Test, ki ima veliko testnih metod za manj pomembne dele, a nikoli ne testira
glavnih funkcionalnosti. Včasih se to pojavi pri testih dostopa do podatkovne
baze, kjer je klicana metoda, potem pa test prebere zapise iz baze in na njih
preverja izjave.
Gobcač
(The Loudmouth)
Test ali skupek testov, ki zapolnijo konzolo z diagnostičnimi sporočili,
dnevniškimi obvestili in drugimi nepomembnostmi tudi, ko je test uspešen.
Včasih testi niso ustrezno očiščeni tiste kode, ki ni več trenutno aktualna.
Požiralec
(The Greedy Catcher)
Test, ki lovi izjeme, a prekrije sled sklada. Včasih pa jo nadomesti z manj
informativnim sporočilom o napaki ali pa samo zapiše poročilo o napaki in test
uspešno konča.
Zaporedje
(The Sequencer)
Test, ki je odvisen od elementov neurejenega seznama, ki so v enakem
vrstnem redu pojavljajo v izjavah.
Testno voden razvoj programskih rešitev Stran 32
Skrita odvisnost
(Hidden Dependency)
Podobno kot pri Lokalnem junaku test potrebuje pripravljene podatke, preden
se lahko izvede. V primeru, da teh podatkov ni, bo test padel in pustil malo
informacij o napaki, kar pomeni naporno branje kode in iskanje odvisnosti.
Števec
(The Enumerator)
Test, v katerem je vsaka testna metoda poimenovana z drugačno številko (npr.
test1, test2, test3). Namen teh metod ni jasen in zahteva dosti več truda, da se
seznanimo z njenim delovanjem.
OS promotor
(The Operating System
Evangelist)
Test, ki se zanaša na določen operacijski sistem, da lahko deluje. Dober primer
je Windows znak za novo vrstico, katerega test pade pod Linuxom.
Tujec
(The Stranger)
Testna metoda, ki sploh ne spada v test, v katerem se nahaja. V resnici testira
ločen objekt, običajno nek pomožni objekt. Metoda pa ga je testirala kot
samostojnega ne glede na njegovo odvisnost od glavnega objekta.
Srečko
(Success Against All
Odds)
Test, ki je bil napisan, da bi prvi uspel, ne pa prvi padel. Stranski učinek je ta,
da je vedno uspešen, tudi takrat, ko ne bi smel biti.
Šlepanje
(The Free Ride)
Namesto, da bi napisali novo testno metodo za novo funkcionalnost, raje kar v
obstoječo metodo dodamo par novih izjav.
Izbranec
(The One)
Kombinacija več vzorcev, običajno Velikan in Šlepanje, je test, ki vsebuje
samo eno testno metodo, ampak ta testira vse funkcionalnosti objekta.
Običajno se takšen test imenuje enako kot metoda, ki vsebuje več vrstic
nastavitev in izjav.
Nepovabljeni gost
(The Peeping Tom)
Test, ki zaradi deljenih virov lahko vidi rezultate drugega testa in tako
povzroči, da test pade, pa čeprav testirani sistem deluje čisto pravilno.
Običajno pri ogrodjih za testiranje, kjer se uporabljajo statične spremenljivke,
ki hranijo zbirke podatkov, in niso ustrezno počiščene.
Počasnež
(The Slow Poke)
Test, ki je izjemno počasen. Razvijalci imajo med izvajanjem čas za
vsakodnevne aktivnosti ali pa ga poženejo samo takrat, ko zaključijo delo.
Testno voden razvoj programskih rešitev Stran 33
3 AVTOMATIZIRANO TESTIRANJE
Programska oprema mora biti testirana, da se prepričamo, da bo delovala tako kot mora v
svojem okolju. Testiranje mora biti učinkovito pri iskanju napak, testi pa kar se da hitri in
enostavni za izvajanje. Avtomatizacija testiranja lahko korenito zmanjša trud, ki je
potreben za ustrezno testiranje oz. poveča obseg testiranja v dani časovni enoti. Testiranje,
ki drugače traja več ur, se izvede v par minutah. Avtomatizirano testiranje lahko prihrani
veliko denarja (tudi do 80 %) ali pa omogoči hitrejši razvoj kvalitetnejše programske
opreme. Moderni testni režim omogoča testiranje s pritiskom na gumb, testi pa se izvajajo
ponoči, ko so računalniki običajno neizkoriščeni. Avtomatizirano testiranje je ponovljivo
in omogoča vedno enake vhodne podatke v enakem zaporedju. Tega ne moremo zagotoviti
z ročnim testiranjem. Z minimalnim naporom lahko izvedemo celotno testiranje. Bolj kot
je testiranje nadležno in dolgočasno, večja je potreba po ustreznih orodjih [13].
Samo testiranje je veščina, ki jo moramo osvojiti. Za vsak sistem obstaja ogromno število
testnih primerov, a imamo v omejenem času možnost pognati samo nekaj osnovnih. Teh
nekaj pa naj bi našlo kar se da veliko napak v programski opremi. Torej je izbira ustreznih
testnih primerov poglavitna. Izkušnje in eksperimenti so pokazali, da njihovo naključno
izbiranje ne obrodi sadov. Za dobre testne primere je potreben bolj temeljit pristop. Dober
testni primer določajo štirje faktorji (Slika 14). Verjetno najpomembnejši je učinkovitost
iskanja napak. Testni primer naj bi bil tudi primerno obsežen. Ostala dva faktorja pa sta
ekonomska. Koliko stane izvajanje, analiza in iskanje napak in kako je testni primer
razširljiv. To pomeni, koliko truda je potrebnega, da vnesemo nove spremembe.
Avtomatizacija testa je dražja kot pa enkratno ročno testiranje. Da bi z avtomatizacijo
pridobili, morajo biti testi previdno izbrani in implementirani. Kvaliteta avtomatizacije je
neodvisna od kvalitete testa. Avtomatizacija vpliva samo na stroške in razširljivost. Boljši
kot imamo pristop k avtomatizaciji, cenejša bo implementacija na dolgi rok. Večkrat ko bo
test pognan, več prihranimo v primerjavi z ročnim testiranjem. Za ustrezen in učinkovit
nabor testov je potrebno pazljivo izbrati in načrtovati teste, ki bodo preverjali
najpomembnejše stvari. Postopek izdelave in vzdrževanja pa naj bo kar se da enostaven in
z malo stroški. Znanje osebe, ki testira, določa kvaliteto testiranja [13].
Testno voden razvoj programskih rešitev Stran 34
Slika 14: Faktorji avtomatiziranega testiranja [13]
3.1 Cilji avtomatiziranega testiranja
Vsi se lotimo avtomatiziranega testiranja z neko idejo, kaj bomo z njim pridobili. Poglejmo
si nekaj glavnih pridobitev. Prve tri predstavljajo koristi testiranja, zadnje tri pa se
osredotočijo na karakteristike samih testov [14].
3.1.1 Testi naj bi izboljšali kvaliteto
Običajno kvaliteto delimo v dve kategoriji, ki ju naslavljata vprašanji: Je programska
oprema grajena pravilno in ali smo razvili zahtevano programsko opremo? Temu pravimo
tudi verifikacija in validacija (angl. Verification and Validation – V&V). Če izvajamo
TDD, potem nam testi služijo kot specifikacije. Na ta način zapišemo, kaj mora sistem, ki
ga razvijamo, delati, še preden ga začnemo razvijati. Specifikacije zapišemo v izvršljivi
obliki in jih potem po želji poganjamo. Da se prepričamo, če razvijamo to, kar je
zahtevano, morajo naši testi natančno izražati, kako se bo sistem uporabljal. To lahko
dosežemo s skicami uporabniških vmesnikov, ki dajejo ravno dovolj informacij o izgledu
in obnašanju aplikacije, da lažje napišemo teste. Takšen način omogoča, da vse scenarije
premislimo in poiščemo področja, kjer so zahteve nejasne ali pa so si nasprotne. Boljše
specifikacije pa pomenijo boljši končni produkt [14].
Testno voden razvoj programskih rešitev Stran 35
Testiranje je sicer iskanje napak, ampak to ni bistvo avtomatiziranega testiranja. Bistvo je,
da se že prej prepreči vnos napak. Takšno testiranje nas varuje pred novim vnosom napak v
programsko kodo, ki je bila pred spremembo ustrezna. Če ustrezno izvajamo regresijsko
testiranje, ne bi smelo priti do napak, saj jih bodo odkrili testi.
Sčasoma vedno pride do hroščev v programski opremi. Nekatere takšne napake je precej
težje preprečiti, kot pa popraviti. Če so testi ustrezno strukturirani, potem lažje določimo,
kje se nahaja napaka. To je ena glavnih prednosti testiranja enot pred prevzemnimi testi.
Prevzemno testiranje nam pove, da delovanje aplikacije ni v skladu z zahtevami naročnika,
testi enot pa nam povedo, zakaj je tako. Temu pravimo lociranje odpovedi. Vsega tega pa
ne moremo doseči, če ne pišemo testov za vse možne scenarije, ki naj bi jih neka enota
pokrivala. Podobno je tudi, če sami testi vsebujejo napake. Jasno je, da morajo naši testi
biti kar se da preprosti, da napake hitro opazimo [14].
3.1.2 Testiranje naj bi pomagalo razumeti sistem, ki ga razvijamo
Preprečevanje vnosa napak pa ni edina korist od testov. Branje testov nam tudi pove, kako
naj bi programska koda sploh delovala. Testi za črno škatlo so v bistvu specifikacije zahtev
za te komponente, ki jih testi pokrivajo. Na takšen način testi služijo kot dokumentacija.
Povedo namreč, kakšni bi morali biti rezultati. V primeru, ko pa hočemo videti postopek,
ki nas pripelje do teh rezultatov, pa lahko poženemo razhroščevalnik in teste in tako
postopoma pregledujemo kodo [14].
3.1.3 Testi naj bi zmanjšali tveganje
Poleg vnosa napak obstajajo še druga tveganja. Preveriti je potrebno tudi delovanje v
izrednih razmerah, do katerih ne more priti pri običajnem prevzemnem testiranju. Zelo
koristno je, če obdelamo vse možna tveganja in premislimo, katera tveganja lahko
ublažimo s pomočjo popolne avtomatizacije testov.
Pri delu z zastarelo kodo nimamo na voljo nabora avtomatiziranih testov, zato nikoli ne
vemo točno, kaj nam je vnesena sprememba pokvarila. To je tudi razlog, da delamo počasi
in skrbno. V primeru, ko pa takšna koda ima ustrezen nabor avtomatiziranih testov, lahko
delamo dosti hitreje. Dovoli nam tudi, da s kodo eksperimentiramo in tako hitreje
razumemo napisano kodo. Nabor avtomatiziranih testov služi kot varovalna mreža.
Učinkovitost te mreže je odvisna od tega, kako natančno testi pokrivajo obnašanje sistema.
Manjkajoči testi so kot luknje v tej mreži, nepopolne izjave pa kot natrgane vrvice.
Testno voden razvoj programskih rešitev Stran 36
Vsaka takšna vrzel dovoli vnos napak, ki so različno nevarne. Mrežo dopolnjujejo tudi
aplikacije za upravljanje konfiguracije, ki omogočajo, da se vrnemo nazaj na različico, ki
je delovala. Za kratkoročne spremembe pa so na voljo orodja znotraj integriranega
razvojnega okolja (angl. Integrated Development Environment – IDE). Omogočajo
razveljavljanje zadnjih sprememb, vodijo nam pa tudi lokalno zgodovino datotek. Tako se
lahko vedno vračamo nazaj na želeno različico, ki je še ni v sistemu za upravljanje
konfiguracije (angl. Software Configuration Management – SCM) repozitoriju.
Seveda pa obstaja tudi možnost, da z avtomatiziranim testiranjem povečamo tveganje.
Pazljivi moramo biti, da si s tem ne nakopljemo novih problemov. Držati se je potrebno
načela, da logiko za testiranje ne mešamo s kodo, ki je za produkcijo. Na ta način se
izognemo vnosu zank, povezanih s testiranjem, v sistem, ki ga razvijamo. Vse kar je
povezano s testiranjem, naj se uporablja samo v okolju za testiranje. Druga nevarnost pa je,
da se zanašamo na kodo, ki naj bi bila temeljito testirana, a pravzaprav sploh ni bila. To se
zgodi, ko spreminjamo sistem, ki ga testiramo, tako da vnašamo logiko, ki se razlikuje
glede na test [14].
3.1.4 Testi naj bi bili enostavni za izvajanje
Pri razvoju aplikacij je testiranje kot nujno zlo. Razvijalci bodo teste poganjali dovolj
pogosto samo, če je to res enostavno opravilo. Test, ki ga lahko poženemo brez vsakega
ročnega vpletanja, je popolnoma avtomatiziran. Testi naj se sami preverjajo, to pomeni, da
preverijo, če je rezultat pričakovan. Večina ogrodij za testiranje ponuja grafične vmesnike,
ki nas z zeleno in rdečo barvo obveščajo o uspešnosti testiranja. Ponovljivost testov je
ključnega pomena. Pomeni, da bo vsak zagon testa vrnil čisto enak rezultat.
Neponovljivost je zelo nezaželena, saj zahteva obilo posredovanja razvijalcev. Enako
moteči pa so tudi nedoločeni testi (angl. Nondeterministic Tests), ki vedno vračajo različne
rezultate. To drastično zmanjša informativno vrednost rdeče črte. Namesto, da bi se takoj
lotili iskanja napake, jo ignoriramo, ko pa izgine, smo izgubili dragocene informacije o
napaki.
Testi, ki se izvajajo samo v pomnilniku in uporabljajo samo lokalne spremenljivke, so
običajno ponovljivi. Do problemov prihaja pri deljenih podatkih. V teh primerih moramo
poskrbeti, da testi počistijo za sabo. Najbolje je uporabiti kar splošen mehanizem za
uničevanje objektov, ki ga ponujajo ogrodja [14].
Testno voden razvoj programskih rešitev Stran 37
3.1.5 Testi naj bi bili enostavni za pisanje in vzdrževanje
Pri pisanju programske kode moramo biti pozorni na celo kopico stvari. Pri pisanju testov
pa je bolje, če se osredotočimo na samo testiranje. To pomeni, da so testi enostavni za
pisanje in branje. To je zelo pomembno, saj je testiranje avtomatiziranih testov zapleteno
opravilo. Ker tega običajno ne delamo, je pomembno, da lahko napake hitro opazimo s
prostim očesom.
Seveda pa s spreminjanjem sistema vplivamo tudi na tiste teste, ki so povezani s
spremenjenim delom. To odvisnost želimo čim bolj zmanjšati. Določen del kode naj
pokriva samo en test, saj podvajanje testov nima pozitivnega učinka. Test naj bo majhen,
testira pa naj samo eno stvar naenkrat. To je še posebej pomembno za TDD, kjer vsak nov
test pokriva samo delček obnašanja celotnega sistema. Vse logične poti v kodi pa
pokrijemo z različnimi testnimi metodami. Izjema so testne metode, ki demonstrirajo
dejansko uporabo aplikacije. V primeru, da uporabnik naredi veliko korakov pri uporabi,
naj bo to vidno tudi v testni metodi. Vsak test mora jasno izražati svoj namen. Podobno kot
pri programski kodi se je tudi tukaj potrebno izogibati podvajanju.
Ločevanje odgovornosti (angl. Separation of Concerns) se izvaja na dva načina. Prvič,
testna koda naj bo ločena od končnega sistema, in drugič, da ima vsak test samo eno
odgovornost. Primer dvojne odgovornosti bi bil, če bi testirali poslovno logiko in grafični
vmesnik (angl. Graphical User Interface – GUI) v istih testih [14].
3.1.6 Testi naj bi zahtevali minimalno vzdrževanje
Spremembe so sestavni del razvoja aplikacij. Strmimo k temu, da bi nam testi omogočali
lažje spreminjanje programske kode in ne obratno. Zato naj bodo testi napisani kar se da
robustno, da ena sprememba vpliva na čim manj testov. To pomeni, da moramo zmanjšati
prekrivanje testov. Moramo pa se tudi prepričati, da sprememba v testnem okolju ne vpliva
na teste. To dosežemo z izolacijo testiranega sistema od testnega okolja [14].
Testno voden razvoj programskih rešitev Stran 38
3.2 Manifest avtomatiziranega testiranja
Manifest izraža principe (kvalitete), ki naj bi jih testi imeli. Večina jih velja tako za
prevzemne test kot za teste enot [14]:
programiraj po testu (Write the Tests First),
načrtuj za testiranje (Design for Testability),
uporaba glavnih poti (Use the Front Door First),
izražaj namen (Communicate Intent),
izoliraj sistem, ki se testira (Isolate the System Under Test),
ne spreminjaj sistema, ki se testira (Don’t Modify the System Under Test),
testi naj bodo neodvisni (Keep Tests Independent),
minimiziraj prekrivanje testov (Minimize Test Overlap),
minimiziraj kodo brez testov (Minimize Untestable Code),
loči testno logiko od končnega sistema (Keep Test Logic Out of Production Code),
test naj preverja samo en pogoj (Verify One Condition per Test),
odgovornosti testiraj ločeno (Test Concerns Separately),
enači trud in odgovornost (Ensure Commensurate Effort and Responsibility).
3.3 Problemi pri avtomatizaciji
Pri avtomatizaciji pa se lahko pojavijo tudi problemi. Seveda je dobro, če jih poskušamo
malo napovedati, saj so čista presenečenja zelo nezaželena. To nam pomaga pri izdelavi
lastnega režima avtomatizacije. Seveda ne smemo imeti nerealnih pričakovanj, da bodo
orodja za testiranje rešila vse težave. Zavedati se je potrebno, da je še vedno treba vložiti
veliko truda, orodja so tu samo za pomoč. Sploh, če so pričakovanja vodstva res visoka,
takrat jih ne more uresničiti še tako dobro orodje. To še posebej velja, če nimamo ustrezno
urejenih praks testiranja. Najprej je potrebno izboljšati organizacijo in kvaliteto testov ter
dokumentacije. V tem primeru je boljše počakati z avtomatizacijo in najprej urediti
problematične zadeve [13].
Testno voden razvoj programskih rešitev Stran 39
Ne smemo pričakovati, da bo zaradi avtomatizacije odkritih občutno več napak (Slika 15).
Gre samo za ponavljanje testov, napake se lahko najdejo samo s spremembo kode ali
spremenjenega okolja. Uspešni testi pa lahko tudi zavajajo oz. dajejo lažen občutek
varnosti. To lahko pomeni, da so testi nepopolni ali celo vsebujejo napake.
Slika 15: Stroški in povratne informacije napak pri različnih tehnikah testiranja [25]
Seveda je ves nabor testov potrebno tudi vzdrževati. Ob spremembah v sistemu je potrebno
spremeniti tudi nekatere teste, ki so postali neustrezni. Kadar je potrebno več truda za
spreminjanje testov, kot pa bi ga bilo potrebno za ročno poganjanje testov, bo
avtomatizacija opuščena. Treba je paziti, da avtomatizacija ne bo žrtev visokih stroškov
vzdrževanja.
Potrebno pa se je zavedati, da so orodja za avtomatizirano testiranje samo programi. Kot
takšni pa imajo lahko napake, zato vedno prihajajo nove različice teh orodij. Upoštevati je
potrebno tudi združljivost z razvojnimi orodji in aplikacijami, ki jih uporabljamo. Na voljo
je nešteto okolij, ki jih ta orodja morda ne podpirajo. Za maksimalno učinkovitost pa je
potrebno orodja obvladati. Tukaj govorimo o dodatnih izobraževanjih in učenju. Predpogoj
za uporabo teh orodij pa je, da je sistem, ki ga razvijamo, primeren za avtomatizirano
testiranje [13].
Testno voden razvoj programskih rešitev Stran 40
Takšno avtomatizacijo pa seveda mora podpreti vodstvo, samo tako lahko postane sestavni
del organizacije. Čas je potrebno nameniti tudi izbiri orodij, izobraževanju,
eksperimentiranju in tudi učenju ter promociji uporabe teh orodij znotraj same
organizacije. Najboljše je, da obstaja znotraj organizacije nekdo, ki zelo dobro pozna
izbrano orodje. Takšna oseba potem širi znanje, promovira orodje in je na voljo za pomoč
ter ima glavno besedo pri izbiri orodja.
Avtomatizirano testiranje mora postati del razvojne infrastrukture, občasna uporaba ne
upraviči stroškov uvedbe. Pogosto se zgodi, da se avtomatizacija ne uporabi takrat, ko bi
bila najbolj koristna. To je pri spremembah in nadgradnjah aplikacij. Potrebno je poenotiti
testiranje znotraj celotne organizacije. V nasprotnem primeru pride do težav pri prenosu
testov med razvojnimi skupinami. Uporabo pa lahko zavirajo tudi administrativne zadeve
npr. premalo licenc za orodje. V primeru, da se testi izvajajo čez noč, je potrebno nameniti
nekaj delovnega časa pregledu rezultatov – ločena aktivnost. Pri ročnem testiranju je bil ta
čas skrit v samem poganjanju testov [13].
Naštejmo običajne razloge za neuspešnost pri avtomatizaciji testiranja [24]:
avtomatizacije ne moremo izvajati v prostem času, potrebna je večja
osredotočenost;
manjkajo jasno zastavljeni cilji;
manjkajo potrebne izkušnje;
pride do izgube izkušenj, ki smo jih pridobili pri ročnem testiranju;
k avtomatizaciji se zatekamo, ko smo obupani – rešitev naših težav je samo
pobožna želja in nima dosti skupnega z realnostjo;
napačno je mišljenje, da je poglavitna avtomatizacija in ne testiranje;
reševanje tehnoloških problemov lahko odvrne pozornost od testiranja;
programerji ne jemljejo avtomatizacije resno, še posebno takrat, ko imajo ločene
ekipe za testiranje;
zahtevno spoznavanje in učenje avtomatizacije testiranja ter pripadajočih orodij;
začetna investicija se ne povrne takoj. Tudi rezultati niso takoj vidni;
spremenljivost programske kode;
avtomatizacija testiranja zastarele kode;
strah zaradi neizkušenosti med programerji in tistimi, ki testirajo;
starih navad se je težko znebiti.
Testno voden razvoj programskih rešitev Stran 41
3.4 Katere teste avtomatizirati?
V grobem lahko teste razdelimo v dve kategoriji (Slika 16) [14]:
funkcionalni testi (angl. functional tests) – preverjanje obnašanja sistema pri
različnih interakcijah;
celostni testi (angl. cross-functional tests) – preverjanje različnih vidikov sistema,
ki zajemajo več funkcionalnosti.
Slika 16: Vrste testov in način izvajanja [14]
Testiranje funkcionalnosti preverja konkretno obnašanje delčka programa. Funkcionalnost
je lahko povezana s poslovno logiko ali pa z zahtevami za delovanje (vzdrževanje sistema,
scenariji, odporni na napake). Večina teh zahtev je lahko izražena kot primer uporabe,
funkcionalnost, uporabniška zgodba ali testni scenarij. Testi funkcionalnosti se lahko ločijo
na poslovne (ali uporabniške) in pa na velikost sistema, ki mu pripadajo (Slika 17).
Prevzemni testi (angl. Acceptance, Customer, End-user tests) preverjajo obnašanje sistema
kot celote. Običajno ustrezajo primerom uporabe, funkcionalnostim ali uporabniškim
zgodbam. Razvijalci jih lahko avtomatizirajo, bistvo pa je, da lahko končni uporabnik
prepozna obnašanje tudi, če ne zna brati testov.
Testi enot (angl. Unit tests) preverjajo obnašanje posameznega razreda ali metode, ki je
bila načrtovana. Takšno obnašanje običajno ni direktno povezano z zahtevami, izjemoma
Testno voden razvoj programskih rešitev Stran 42
pa tudi, če razred ali metoda vsebuje ključno logiko. Te teste pišejo razvijalci za lastne
potrebe – ostalim razvijalcem opišejo obnašanje v obliki testa.
Integracijski testi (angl. Component, Subsystem tests) preverjajo komponente, ki jih
sestavljajo razredi, ki skupaj tvorijo neko celoto. Po namenu sodijo med teste enot in
prevzemne teste.
Sejanje napak (angl. Fault insertion tests) poteka na vseh treh nivojih funkcionalnega
testiranja. Na vsakem nivoju se vstavijo različne vrste napak. Iz vidika avtomatizacije
testov je to samo še en skupek njihovih enot in integracije. Stvari postanejo bolj zapletene
pri aplikaciji kot celoti. Tukaj postane avtomatizacija zahtevna, kajti sejanje napak je
težavno, brez da bi nadomestili kakšen del aplikacije [14].
Zmogljivostni testi (angl. Performance tests) preverjajo nefunkcionalne zahteve sistema.
Zahteve so različne in lahko obsegajo različne funkcionalnosti. Običajno se nanašajo na
arhitekturo. Testira se lahko odzivne čase, kapaciteto in obremenitev. Večina teh testov
mora biti vsaj delno avtomatizirana, saj bi bilo ročno testiranje zelo težko. Ogrodje xUnit
ni preveč primerno za testiranje zmogljivosti, pa čeprav lahko teste izvajamo večkrat.
Prednost agilnih metod je, da lahko te vrste testov začnemo izvajati že kmalu v razvojnem
ciklu. Dovolj je, da so glavne komponente grobo sestavljene in da je ogrodje
funkcionalnosti možno pognati. Isti testi se lahko potem poganjajo stalno, ko se dodajajo
nove funkcionalnosti.
Testi uporabnosti (angl. Usability tests) preverjajo ujemanje z namenom in kontrolirajo, ali
so uporabniki aplikacije izpolnili zadane naloge. Takšne teste je zelo težko avtomatizirati,
saj zahtevajo subjektivno oceno uporabnikov o enostavnosti uporabe sistema.
Preiskovalno testiranje (angl. Exploratory testing) nam pove, ali je sistem konsistenten.
Testira se uporaba produkta, opazuje se obnašanje, tvorijo se hipoteze in načrtujejo se testi,
ki bodo preverili te hipoteze. Zaradi narave testiranja ti testi ne morejo biti avtomatizirani.
Lahko pa se avtomatizira priprava sistema, na katerem se izvaja preiskovalno testiranje
[14].
Testno voden razvoj programskih rešitev Stran 43
Slika 17: Piramida testov (prirejeno po [38])
3.5 Testiranje enot
Testiranje enot je že dobro poznan in uveljavljen koncept pri razvoju programskih rešitev.
Poznan je že od začetkov programskega jezika SmallTalk okoli leta 1970. Od takrat pa se
vedno znova in znova dokazuje kot eden najboljših načinov, kako lahko razvijalec
izboljšuje kvaliteto kode in hkrati pridobiva znanje o funkcionalnih zahtevah razredov in
metod.
Test enote je delček kode (običajno je to metoda), ki pokliče neko drugo metodo in preveri
njeno delovanje v primerjavi s pričakovanim. V primeru, ko ni skladanja med dejanskim in
pričakovanim izhodom, test pade. Enoto predstavlja metoda ali funkcija. Delček kode, ki
se testira, se imenuje testiran sistem (angl. System Under Test – SUT). Delček kode, ki pa
testiranje izvaja, je testna metoda [27].
Poleg klasične definicije pa zapišimo še eno. Test enote je avtomatiziran del kode, ki kliče
različne metode in preveri pričakovane izhode logičnega obnašanja metode ali razreda, ki
se testira. Napisan je s pomočjo ogrodja za testiranje. Lahko se enostavno napiše in hitro
Testno voden razvoj programskih rešitev Stran 44
požene. Požene ga lahko vsakdo v razvojni ekipi, večkrat zaporedoma. Logiko obnašanja
sestavljajo delčki kode, ki vsebujejo kakšno vrsto logike. Bodisi vejitve, zanke, kalkulacije
ipd [27].
Tehnike pri testiranju enot lahko v grobem delimo na tri tipe [28]: funkcionalno, strukturno
in hevristično testiranje. Napake v programski opremi se v splošnem delijo na nejasnosti,
presenečenja in napake pri implementaciji (Slika 18). Nejasnosti so zahteve, ki manjkajo
pri implementaciji. Presenečenja predstavlja implementacija, ki jo ne najdemo med
zahtevami. Napačno implementacijo pa predstavlja neustrezna implementacija zahteve.
Slika 18: Tipi testiranja enot in napake [28]
3.5.1 Testiranje in izolacija
Enote je najboljše testirati v popolni neodvisnosti od ostalih delov. Slej ko prej pa to ni več
možno. V tem primeru je treba dele, ki nas ne zanimajo, nekako nadomestiti (Slika 19).
Poznamo več načinov – uporabimo tistega, ki nam bolj ustreza [29].
Slepi (angl. Dummy) objekti se prenašajo naokoli, a niso nikoli uporabljeni.
Umetni (angl. Fakes) objekti so sicer delujoči, a je uporabljenih veliko bližnjic.
Ogrodja (angl. Stubs) se odzivajo samo na klice pri testiranju. Uporabljajo se tudi v
času, ko dejanska implementacija še ni na voljo.
Vohljači (angl. Spies) so ostanki, ki beležijo klice.
Nadomestki (ang. Mocks) so vnaprej pripravljeni objekti, ki imajo definirano
obliko klicev, ki bodo prejeti. V primeru napačnega klica lahko vrnejo izjemo ali pa
preverjajo, če so dobili vse pričakovane klice.
Testno voden razvoj programskih rešitev Stran 45
Slika 19: Mreža objektov in testiranje z nadomestki [4]
3.5.2 Ogrodja za testiranje
Družina ogrodij xUnit za avtomatizirano testiranje je prilagojena uporabi pri avtomatizaciji
testiranja. Načrtovana so tako, da zadostijo naslednjim kriterijem [14]:
enostavno pisanje testov, brez da bi se bilo potrebno naučiti novega programskega
jezika;
enostavno testiranje posameznih razredov in objektov, preden imamo celotno
aplikacijo (omogoča testiranje od znotraj);
enostavno in hitro poganjanje testov;
minimizacija stroškov poganjanja testov.
Velika večina programskih jezikov ima tudi ogrodje za testiranje enot. V tabeli (Tabela 3)
so navedena nekatera ogrodja bolj znanih programskih jezikov. Veliko pa jih temelji na
ogrodju xUnit – v tabeli so označeni krepko. Testirati je mogoče objektne, imperativne,
logične, funkcijske, skriptne, označevalne, povpraševalne in paralelne jezike. Za testiranje
dostopa do baze pa se uporablja DbUnit. Testirati je možno tudi različne protokole.
Testno voden razvoj programskih rešitev Stran 46
Tabela 3: Ogrodja za testiranje enot [26]
3.5.3 Poimenovanje testov
Za lažje iskanje in razumevanje je ključnega pomena, da teste ustrezno poimenujemo. S
sistematičnim poimenovanjem testnih metod je pokritost s testi lažje določiti. V imenu naj
bo tudi jasno viden pogoj, ki ga metoda testira. S pomočjo imen paketov, razredov in
metod poskušamo zajeti čim več uporabnih informacij. Naziv razreda in metode, ki se
testira, kakšna informacija o vhodnih vrednostih in po potrebi še kaj pomembnega o stanju
testiranega sistema. Tako je že iz nazivov jasno, kaj se testira in kakšni so testni pogoji.
Priporočljivo je tudi dodati kakšno informacijo o pričakovanih rezultatih testa [14].
S takšnim poimenovanjem testi služijo kot dokumentacija, kar pa je tudi ena od značilnosti
TDD. Dolžina teh nazivov je tako precej dolga, a to ni pomembno, saj teh metod nikjer ne
kličemo. So le del testnega nabora in se izvajajo avtomatično. Najprej lahko metodo
poimenujemo in potem napišemo njeno vsebino ali pa obratno [4].
Programski jezik Naziv ogrodja
ActionScript / Adobe Flex FlexUnit 4, AsUnit
Ada AUnit
ASP ASPUnit
BPEL BPELUnit
C C++test, CUnit
C++ CppUnit, UnitTest
Cobol COBOLUnit
Common Lisp CLUnit
Delphi DUnit
Fortran FUnit
FSharp FsTest
Groovy easyb
Haskell HUnit
Splet (Internet) HtmlUnit, HttpUnit, SOAtest
Java JUnit, Jtest
JavaScript JSUnit, jsUnitTest
LaTeX qsunit
LISP lisp-unit
Programski jezik Naziv ogrodja
MATLAB mlUnit
MySQL utMySQL
.NET NUnit, MSTest
Objective-C Kiwi, OCUnit
Object Pascal FPCUnit
Perl PerlUnit
PHP PHPUnit
PL/SQL utPLSQL, PLUTO
PostgreSQL PGUnit
Prolog PlUnit
Python PyUnit
Ruby RSpec
Scala ScUnit
Lupina (Shell) ShUnit
SmallTalk SUnit
SQL SQLUnit
XML XUnit
XSLT XSLTUnit
Testno voden razvoj programskih rešitev Stran 47
Dogovor o poimenovanju testnih metod je zelo koristen. Priporoča se naslednja oblika:
[CiljnaMetoda]_[StanjeKiSeTestira]_[PričakovanRezultat]. Dejanski primer pa bi bil npr.
public void OdpriDatoteko_DatotekaJeNull_JaviIzjemoArgumentNull [30].
3.5.4 Struktura testa enote
Pisanje testov v standardni obliki pomeni lažje razumevanje. Tako so izjave in pričakovani
rezultati vedno na istem mestu v strukturi. Jasen znak slabega načrtovanja je, ko je test zelo
težko napisati v standardni strukturi. Glavni sestavni deli takšne strukture so [4]:
1. Priprava (angl. Setup) konteksta oz. okolja, v katerem bo izvršena ciljna metoda.
2. Izvršitev (angl. Execute) proži to ciljno kodo.
3. Preverjanje (angl. Verify) vidnega odziva, ki smo ga pričakovali.
4. Čiščenje (angl. Teardown) ostankov, da ne vplivajo na druge teste.
Pri pisanju testnih metod pa se je najboljše držati sintakse AAA. Na začetku pripravimo
vse potrebno za test (angl. Arrange). Nadaljujemo s proženjem metod, ki jih želimo
testirati (angl. Act). Za konec pa v izjavah preverimo ustreznost dobljenih rezultatov (angl.
Assert).
3.5.5 Pokritost kode s testi
Nabor testov enot služi kot varovalo, da z vnosom novih sprememb nismo česa pokvarili.
Problem pa nastane, ko se pri tem pokvari nekaj, kar ni bilo testirano. V ta namen je
potrebno natančno vedeti, katera koda se izvršuje v naših testih. Idealno bi bilo, če bi testi
pokrivali 100 % kode.
Testiranja po metodi črne škatle (testiranje brez poznavanja notranjega delovanja,
preverjajo se samo vhodi in izhodi) pokrivajo samo javno dostopne dele API-ja. Ker se
ravnamo po dokumentaciji, nimamo nobenega vpogleda v implementacijo. Tako ne
testiramo vseh možnih vejitvev v metodah. Samo testiranje metod pa je premalo. Zato je
bolj primerno testiranje po metodi bele škatle (testiranje notranjosti programa na način, da
preverimo čim več možnih poti). Z vpogledom v metodo lahko nadzorujemo tako vhode
kot tudi sekundarne objekte (nadomestke). To nam daje možnost, da poleg javnih
napišemo teste tudi za zaščitene in privatne metode, kar poveča pokritost kode s testi [31].
Testno voden razvoj programskih rešitev Stran 48
Nekaj kriterijev za pokritost [32]
Pokritost funkcij – je bila klicana vsaka metoda v programu?
Pokritost izjav – se je izvedla vsaka izjava programa?
Pokritost odločitev – se izvede vsaka veja IF in CASE vozlišč?
Pokritost pogojev – so bile vse binarne izjave ovrednotene kot pravilne in napačne?
Pokritost pogojev in odločitev – kombinacija zgornjih dveh pokritosti.
3.6 Integracijsko testiranje
Integracijsko testiranje je faza testiranja programske opreme, pri kateri se kombinirajo
moduli (enote) in se potem skupaj testirajo. Eden od razlogov je lahko, da z nadomestki
nismo uspeli izolirati modula. Izvaja se za testiranjem enot in pred sistemskim testiranjem.
Za takšno testiranje se uporablja ogrodje xUnit ali FIT. Osnova za testiranje so moduli, ki
so že bili testirani. Povežemo jih v večje celote in nad njimi izvajamo integracijske teste. V
primeru, da so testi uspešni, je sistem pripravljen na sistemsko testiranje.
Namen testiranja je preveriti funkcionalne, zmogljivostne in zahteve glede zanesljivosti.
Skupki enot so testirani preko lastnih vmesnikov po metodi črne škatle s pomočjo
parametrov in vhodnih podatkov. Preverja se deljenje podatkov med več enotami in
medprocesna komunikacija. Posamezni podsistemi se preverjajo preko njihovih vhodnih
vmesnikov. Testni primeri so pripravljeni tako, da preverijo, če se vse enote v skupku
ustrezno sporazumevajo. Ideja je, da se postopoma dodajajo nove enote, ki so bile prej
testirane in se tako gradi vedno večja celota [33].
Vrste integracijskega testiranja [33], [37]
Veliki pok (angl. Big Bang) – skoraj vsi moduli so združeni v celoto in potem
testirani. Ta način prihrani veliko časa, če je ustrezno načrtovan.
Inkrementalni pristop (angl. Incremental Approach) – postopno združevanje
komponent.
Regresijsko testiranje (angl. Regression Testing) – preverja, da ob spremembah
niso bile vnesene nove napake.
Testiranje posledic (angl. Smoke Testing) – prvi test, ki se izvede ob prenovi oz.
razširitvi sistema.
Testno voden razvoj programskih rešitev Stran 49
Od zgoraj navzdol (angl. Top-down) – najprej začnemo testirati zgornje module in
tako postopoma navzdol vse module.
Od spodaj navzgor (angl. Bottom-up) – najprej se testirajo komponente, ki so
najnižje v hierarhiji. To ponavljamo, dokler ne pridemo do komponent, ki so na
vrhu.
Hibrid je kombinacija zgornjih dveh vrst.
3.7 Sistemsko testiranje
Je del testnega razvojnega cikla in se izvaja pred prevzemnim testiranjem. Sistemsko
testiranje (Slika 20) se izvaja na končanem in integriranem sistemu, da se preveri skladnost
s specifikacijami. Gre za testiranje po principu črne škatle [47].
Vhod predstavljajo integrirani deli sistema, ki so uspešno opravili integracijsko testiranje.
Preverjajo se tako funkcionalne kot tudi sistemske zahteve. Gre za preiskovalno testno
fazo, kjer na destruktiven način poteka testiranje zasnove, obnašanja in celo predvidenih
pričakovanj naročnika. Na takšen način se preverja, če integrirani deli sistema delujejo in
komunicirajo med seboj tako, da zadostijo funkcionalnim zahtevam [47].
Število sistemskih testov je sorazmerno manjše kot pri integracijskem testiranju, saj naj bi
večino napak odkrili že testi v prejšnjih fazah testiranja (testiranje enot in integracije).
Stroški odprave napak, ki se pojavijo v fazi sistemskega testiranja, pa so seveda ustrezno
višji [46].
Slika 20: Sistemsko testiranje [47]
Testno voden razvoj programskih rešitev Stran 50
Pod sistemsko testiranje pa spada tudi [47]:
funkcijsko testiranje (Functional testing),
GUI-testiranje (User interface testing),
testiranje uporabnosti (Usability testing),
testiranje združljivosti (Compatibility testing),
testiranje modelov (Model based testing),
testiranje izhodov ob napakah (Error exit testing),
testiranje pomoči uporabniku (User help testing),
testiranje varnosti (Security testing),
testiranje kapacitet (Capacity testing),
testiranje zmogljivosti (Performance testing),
testiranje racionalnosti (Sanity testing),
regresijsko testiranje (Regression testing),
testiranje zanesljivosti (Reliability testing),
testiranje obnašanja ob napakah (Recovery testing),
testiranje namestitve (Installation testing),
testiranje vzdrževanja (Maintenance testing) in
testiranje dostopnosti (Accessibility testing).
3.8 Prevzemno testiranje
Gre za testiranje po principu črne škatle, ki se izvaja nad celotnim sistemom. Vsak
posamezen test oz. uporabniški primer preverja določeno funkcionalnost ali uporabniški
pogoj. Rezultat takšnega testa je logična vrednost, pravilno ali narobe. Testno okolje je
običajno enako ali kar se da podobno pričakovanemu okolju končnih uporabnikov. Takšni
testi morajo biti opremljeni z vhodnimi podatki ali s formalnim opisom operacije, ki se
izvaja, da se lahko izvede test. Najboljše, če vsebuje kar oboje.
Teste običajno napišejo kar poslovni uporabniki v jeziku poslovne domene. To so končni
testi, ki preverijo popolnost implementiranih uporabniških zgodb. Idealno je, če teste v
sodelovanju napišejo poslovni uporabniki in analitiki, testerji ter razvijalci. Uspešna
izvedba teh testov daje stranki zagotovilo, da poteka razvoj v pravi smeri. Pomembno je,
da takšni testi vključujejo tako poslovno logiko kot tudi kontrolne elemente uporabniškega
Testno voden razvoj programskih rešitev Stran 51
vmesnika. Kadar prevzemni test obsega več uporabniških zgodb, je potrebno neaktualne
nadomestiti. Uporabniška zgodba je zaključena, ko je pozitiven njen prevzemni test [34].
Tabela 4: Ogrodja za prevzemno testiranje [34]
Glavni cilj je, da se preveri, če sistem ustreza poslovnim zahtevam tako stranke kot
uporabnikov. To je tudi zadnja stopnja preverjanja kvalitete, kjer se pojavijo napake, ki so
prej ostale skrite. Ko so uspešni vsi prevzemni testi in je zadoščeno še morebitnim ostalim
prevzemnim pogojem, bodo naročniki odobrili plačilo projekta.
Tipi prevzemnih testiranj [34]
Uporabniško prevzemno testiranje s tovarniškimi uporabniki (angl. Factory Users)
ali uporabniki ob strani.
Operativno prevzemno testiranje preveri uporabnost in možnost vzdrževanja
sistema. To lahko obsega mehanizme za varnostno kopiranje, scenarije za
katastrofe, usposabljanje končnih uporabnikov, vzdrževalne in varnostne
procedure.
Pogodbeno in nadzorno prevzemno testiranje temelji na pogojih, zapisanih v
pogodbi in ostalih legalnih ter varnostnih pogojih.
Alfa testiranje se izvaja znotraj razvojnih skupin. Beta testiranje pa se izvaja pri
manjši skupini uporabnikov, ki potem nudijo povratne informacije razvijalcem.
Naziv ogrodja Programski jezik
Abbot Java
Concordion Java
Cucumber Ruby
FitNesse in Fit Java, .NET, C++, Delphi, Python, Ruby, Smalltalk, Perl
Frankenstein Java
ItsNat AJAX, Splet (Internet)
iMacros Splet (Internet)
Ranorex MFC, .NET, Java, Splet
RSpec Ruby
Selenium Splet (Internet), .NET, Java, Groovy, Perl, PHP, Phyton, Ruby
Test Automation FX .NET
Watir Splet (Internet)
Testno voden razvoj programskih rešitev Stran 52
3.9 Vedenjsko testiranje
Takšno testiranje predstavlja svež pristop k razvoju programske opreme. Zahteve s
pomočjo jezika domene in nekaterih konstruktov opišemo v strukturiranemu načinu. To
predstavlja izvršljivo specifikacijo, ki je osnova za nadaljnji razvoj. Takšen način odpravlja
pomanjkljivost TDD, kjer je bila vrzel med testom in pripadajočo programsko kodo kar
nekoliko prevelika. Testi se potem izvedejo v ogrodju za testiranje enot.
Vedenjsko voden razvoj se začne s pogovorom med vsemi vpletenimi v razvoj. Na takšen
način se vsi seznanijo s funkcionalnostmi, ki bodo implementirane. Razjasnijo pa se tudi
morebitne nejasnosti v razumevanju. To zajema tudi pričakovane rezultate, ki nam jih te
funkcionalnosti dajejo. Ko jih implementiramo, vemo, da smo zaključili z delom na tej
funkcionalnosti.
Funkcionalnosti se zapišejo kot uporabniške zgodbe v malo bolj formalni obliki (Slika 21).
Slika 21: Uporabniška zgodba [39]
Ko smo se z zgodbo seznanili, se osredotočimo na scenarije, ki opisujejo obnašanje
sistema, kot jih pričakuje uporabnik. Scenarije sestavlja zaporedje korakov, zapisanih v
formalni obliki (Slika 22).
Slika 22: Oblika scenarija [39]
BDD-orodja delimo na xBehave (opis pričakovanja uporabnikov) in xSpec (opis
specifikacij na nivoju enote).
Testno voden razvoj programskih rešitev Stran 53
Tabela 5: Seznam ogrodij za testiranje obnašanja [40]
Programski jezik Naziv ogrodja
C# SpecFlow, NSpec, MSpec, NBehave, Specter
F# TickSpec
Go Go Behave, GoSpec
Java JBehave, Instinct, JDave, Easyb, BDoc, Concordion
JavaScript Jasmine, BehaviourJS, JSpec
Objektni C Cedar
PHP Behad
Ruby RBehave, RSpec, Cucumber
3.10 A/B-testiranje
Osnovna ideja je primerjalno testiranje. Med seboj primerjamo več eksperimentov
(alternativ) in merimo uspešnost. Uporabimo ga lahko za predstavitev novih
funkcionalnosti, merjenje odziva na spremembe v starih funkcionalnostih, spremembah v
prikazu itd. Seveda lahko naenkrat primerjamo tudi več kot pa samo dve alternativi. Ideja
je, da se nove ideje predstavljajo uporabnikom v iteracijah. Njihov odziv pa narekuje
smernice razvoja [44].
Eksperimenti potem tečejo kot del aplikacije, ki jo razvijamo. Za prikazovanje alternativ
skrbi ogrodje tako, da so vse alternative prikazane enakomerno med uporabniki (Slika 23).
V ozadju se skrbno beleži vsa statistika teh alternativ in v vsakem trenutku je možno
pogledati poročilo stanja (Slika 24). Eksperimenti naj tečejo dovolj dolgo, da se izognemo
naključnim rezultatom. V primeru, da je katera od alternativ neprimerno bolj uspešna od
ostalih, je možno eksperiment zaključiti že predčasno [44].
Ta tehnika izhaja iz marketinga. Najbolj dodelano ogrodje za eksperimentalno voden
razvoj se imenuje Vanity in je napisano za programski jezik Ruby.
Testno voden razvoj programskih rešitev Stran 54
Slika 23: A/B-testiranje [43]
Slika 24: Primer poročila EDD [42]
Testno voden razvoj programskih rešitev Stran 55
4 SODOBNI KONCEPTI RAZVOJA PROGRAMSKIH REŠITEV
Na hitro preletimo koncepte sodobnih programskih rešitev, ki omogočajo enostavnejše
testiranje in razvoj. Te koncepte smo uporabili pri izdelavi praktičnega dela diplomske
naloge. Koncepti skupaj pokrivajo vse nivoje programskih rešitev in so tesno povezani s
TDD. Pravzaprav šele uporaba teh konceptov omogoča ustrezno testiranje in sodoben
razvoj programskih rešitev.
4.1 Vzorec MVVM
MVVM je arhitekturni vzorec (angl. Model-View-ViewModel – MVVM) (Slika 25) za
predstavitveni nivo aplikacije. Izhaja iz vzorca MVC in je namenjen WPF in Silverlight
aplikacijam. Omogoča jasno ločevanje med grafično predstavitvijo in poslovno logiko z
minimalno količino programske kode na predstavitveni strani [48].
Pogled (angl. View) predstavlja grafični vmesnik v jeziku XAML. Model predstavlja
podatkovni nivo oz. objektni model. Model in pogled pa povezuje vmesnik (angl.
ViewModel). Ta predstavlja podatkovni kontekst pogleda, odziva se na spremembe, izvaja
preverjanje podatkov in posluša dogodke.
Takšno ločevanje omogoča, da se grafični oblikovalec osredotoči na predstavitveni del,
razvijalec pa na poslovno logiko. Obstajajo tudi ogrodja MVVM, ki še izboljšajo razvoj, a
so zahtevna za uporabo. Tako razvito programsko rešitev je možno tudi enostavno testirati,
saj je vmesnik ločen od ostalih delov sistema [48].
Slika 25: Vzorec MVVM [48]
Testno voden razvoj programskih rešitev Stran 56
4.2 Obrat kontrole
Obrat kontrole (angl. Inversion of Control – IOC) je v tesni povezavi z vrivanjem
odvisnosti. Formalna definicija pravi, da je IOC-vidik arhitekturnih rešitev, pri katerih je
kontrolni tok sistema obrnjen v primerjavi s tradicionalnim načinom. To v praksi pomeni,
da je kreiranje objektov prepuščeno ogrodju in ne programu, tako kot je bila praksa v
preteklosti. Vloge so zamenjane [50].
4.3 Vrivanje odvisnosti
Vrivanje odvisnosti (angl. Dependency Injection – DI) je načrtovalski vzorec, ki omogoča
razvoj šibko sklopljenih aplikacij. Namesto, da komponenti priskrbimo vse odvisne
objekte, raje samo vodimo seznam odvisnosti. Dobavo teh odvisnosti pa prepustimo
ogrodju za njihovo vrivanje, ki tako prevzame upravljanje celotnega življenjskega cikla
odvisnih objektov. Odvisnosti naj bodo predvsem vmesniki in ne konkretne
implementacije [49].
Tako razvita programska rešitev omogoča enostavno ponovno uporabo, lažje vzdrževanje,
testiranje in delo z nadomestki. Celotno aplikacijo drži skupaj samo nekaj vrstic kode, ki
povedo ogrodju, kako naj sestavi odvisnosti. Vrivanje izvede ogrodje, brez da bi določili
kdaj, kje, kako ali zakaj.
Obstaja več tipov vrivanja odvisnosti [49]:
preko konstruktorja,
preko lastnosti,
preko vmesnikov in
preko metod.
4.4 Stalna integracija
Je praksa razvoja programskih rešitev, pri kateri člani razvojne ekipe svoje delo pogosto
združujejo v celoto. Vsak član izvaja integracijo vsaj dnevno. To pomeni več integracij
celotne ekipe dnevno. Vsaka integracija se avtomatsko prevede, morebitne napake pa so
takoj vidne [51].
Testno voden razvoj programskih rešitev Stran 57
Na takšen način se preverja, kako se posamezni deli rešitve skladajo med seboj. Vse to se
dogaja v okolju, ki je precej podobno produkcijskemu. Rešitev pa se vedno ohranja v
delujočem stanju. V primeru, da temu ni tako, se celotna ekipa posveti tej težavi.
Osnova za stalno integracijo (angl. Continuous integration – CI) je (porazdeljen) sistem za
upravljanje konfiguracije (angl. (Distributed) Version Control System – (D)VCS), katerega
sprememba proži CI-strežnik. Ta prenese zadnjo različico izvorne kode in jo prevede,
požene avtomatizirane teste vseh vrst, izvede analizo, preračuna metrike in podobno. Za
vse našteto izdela poročila in o rezultatih obvesti ekipo [51].
4.5 Načrtovalski vzorci
Pri razvoju programskih rešitev predstavljajo vzorci splošne rešitve za pogoste probleme
načrtovanja. Predstavljajo splošen opis ali predlogo rešitve, ki se lahko uporabijo v različne
namene. Namenjeni so predvsem objektno orientiranemu načinu programiranja [52].
Vzorci lahko pospešijo razvoj, saj prinašajo testirane in preverjene paradigme. Vzorci so
kombinacija dobrih praks in izkušenj, ki so jih sestavili eksperti.
Obstaja več tipov vzorcev [52]:
ustvarjalni vzorci,
strukturni vzorci,
vedenjski vzorci in
vzorci za večnitna okolja.
4.6 Principi SOLID
V objektno orientiranem programiranju SOLID predstavlja akronim za pet osnovnih pravil
OO-programiranja in načrtovanja. Tako razvite rešitve so enostavne za vzdrževanje in
nadgradnjo. Principi SOLID so kombinacija petih akronimov za principe: ena odgovornost
(angl. Single Responsibility Principle – SRP), odprtosti in zaprtosti (angl. Open Closed
Principle), Liskove zamenjave (angl. Liskov Substitution Principle), vmesniške ločitve
(angl Interface Segregation Principle) in obrata odvisnosti (angl. Dependency Inversion
Principle).
Testno voden razvoj programskih rešitev Stran 58
Koncepti, ki jih principi promovirajo [53]:
vsak objekt naj ima samo eno odgovornost;
razredi naj bodo odprti za razširitve, a zaprti za spremembe;
menjava objektov z lastnimi podtipi naj ne vpliva na pravilnost programa;
vmesniki naj bodo specializirani in majhni;
uporaba abstrakcije namesto konkretnih objektov.
4.7 Praktični nasveti za programiranje in testiranje
Naštejmo nekaj dobrih praks, ki so se izkazale za uspešne pri načrtovanju in testiranju
programske opreme [35], [36]:
izogibajmo se operatorju »new«, raje uporabimo tovarne (Factory);
v konstruktorju naj bo samo inicializacija spremenljivk in nič drugega;
izogibamo se globalnim spremenljivkam;
vzorec Edinec (Singleton pattern) se naj uporablja redko;
izogibajmo se statičnim razredom, metodam in lastnostim;
dedovanje naj ne bo pregloboko; raje kompozicija kot dedovanje;
vejitve nadomestimo s polimorfizmom, če je možno;
uporaba vmesnikov (Interface) in ograjevanja (Encapsulation);
komunikacija in vedenje enot naj bo omejeno (Law of Demeter);
vrivanje odvisnosti (Dependency injection);
virtualne metode in lastnosti omogočajo lažje nadomeščanje (Mocking);
razredi naj imajo samo eno odgovornost (Single Responsibility Principle);
ločevanje poslovne logike od ostalih delov sistema (npr. vzorec MVVM ali MVC);
testi enote ne komunicirajo s podatkovno bazo, preko omrežja, z datotečnim
sistemom in ne potrebujejo posebne konfiguracije;
samo tista koda, ki je ni, je še boljša od enostavne kode;
minimalno število izjav na testno metodo – testi naj ne bodo odvisni med seboj;
uporaba konstant namesto magičnih nizov (angl. Magic strings);
ustrezno poimenovanje parametrov in metod;
izbira programskega jezika glede na domeno problema (DSL).
Testno voden razvoj programskih rešitev Stran 59
5 PRAKTIČNI PRIMER RAZVOJA PO METODOLOGIJI TDD
Kot je bilo napovedano že v samem uvodu, bomo metodologijo TDD preizkusili tudi v
praksi. Najprej bomo predstavili idejo, nato pa še nabor orodij in funkcionalnosti, ki bodo
implementirane. Vse skupaj bomo zaključili z analizo dobljenih rezultatov in izkušenj.
5.1 Predstavitev in ideja
Ideja je, da se po metodologiji TDD razvije neka pilotska aplikacija. Razvoj naj bo podprt
s testi različnih nivojev (enot, integracije in sistema), ki bodo narekovali razvoj. Vse
glavne aktivnosti razvoja pa se bodo časovno merile za kasnejše analize.
Razvili bomo namizno aplikacijo za sledenje projektov. Omogočala bo meritve časa za
poljubne aktivnosti na projektu. Podatki bodo shranjeni v lokalni relacijski bazi, kar bo
omogočalo nemoteno delo tudi brez povezave v splet. Vse skupaj pa bo možno
pregledovati in analizirati s pomočjo izpisov. Grafični vmesnik pa bo kar se da enostaven
in prilagodljiv.
Osnovne funkcionalnosti aplikacije:
merjenje časa (štoparica),
registracija in urejanje uporabnikov,
urejanje meritev,
planiranje,
dodajanje opravljenega dela,
urejanje projektov in aktivnosti,
hramba podatkov (podatkovna baza),
o uporabniki, projekti, aktivnosti in meritve
pregled opravljenega dela,
izdelava poročil,
tiskanje in izvoz poročil,
obveščanje (pošiljanje elektronske pošte).
Testno voden razvoj programskih rešitev Stran 60
5.2 Iteracija NIČ
Vsak projekt se začne z zbiranjem poslovnih in tehničnih zahtev. Preden lahko začnemo s
pisanjem programske kode, je potrebno postaviti ustrezno infrastrukturo, izbrati
programske platforme in pomožne knjižnice, ki se bodo uporabljali pri razvoju rešitve. Gre
za grobe odločitve, ki se kasneje še vedno lahko spremenijo, če se izkaže, da je bila ocena
napačna.
Tabela 6: Infrastruktura
Orodje Različica Opis
ClockingIT 0.99.3 Servis za meritve časa (http://www.clockingit.com/)
TeamCity 6.5.3 CI-strežnik
VisualSVN Server 2.1.9 SVN-strežnik
YouTrack 3.0.3 Sledenje napak in funkcionalnosti
Dobra infrastruktura (Slika 26) omogoča varnejši, hitrejši, kvalitetnejši in preglednejši
razvoj programskih rešitev.
Slika 26: Uporabljena infrastruktura
Testno voden razvoj programskih rešitev Stran 61
Ob spremembi izvorne kode na SVN-strežniku se proži proces stalne integracije na CI-
strežniku TeamCity, ki ga sestavlja pet korakov.
Tabela 7: Koraki stalne integracije
Korak Opis
1. Prevajanje izvorne kode v načinu Release.
2. Izvedba testiranja na ogrodju NUnit in izračun pokritosti kode.
3. Statična analiza kode (FxCop).
4. Iskanje podvojene kode.
5. Analiza SVN-repozitorija in sprememb.
Programska oprema in komponente pa temeljijo na Microsoftovih tehnologijah ter na
priljubljenih odprtokodnih projektih. To zagotavlja razvijalcu podporo, primere,
dokumentacijo in združljivost.
Tabela 8: Seznam uporabljenih tehnologij
Tehnologija Različica Opis
.NET C# 4.0 Programsko ogrodje
DevExpress 11.1.6 Poročila
Entity Framework 4.1 Ogrodje za delo z bazo
Extended WPF Toolkit 1.5 Dodatne komponente
SQLCE 4.0 Podatkovna baza
WPF 4.0 Grafični vmesnik
Balsamiq Mockups 2.1.2 Skiciranje
Najbolje je izbrati tista orodja, ki jih priporoča agilna in TDD-skupnost. V .NET-okolju
izstopa predvsem orodje ReSharper, ki v zadnji različici še posebej olajša preoblikovanje
in delo s kodo. Za delo s knjižnicami in ogrodji je najboljše izbrati kar NuGet. To je
razširitev za Visual Studio, ki prevzame delo s knjižnicami. Dobra praksa je, da so te
knjižnice in ogrodja tudi del sistema za upravljanje konfiguracije. Na takšen način se
zagotovi konsistentnost paketov med razvijalci na različnih delovnih postajah. Nekatera
izmed uporabljenih orodij pa ne dodajajo novih funkcionalnosti, ampak samo omogočajo
lepši zapis ali prikaz.
Testno voden razvoj programskih rešitev Stran 62
Tabela 9: Seznam uporabljenih orodij
Orodje Različica Opis
Debugger Canvas 1.0.3 Grafični vmesnik za razhroščevanje
dotCover 1.1.1 Pokritost kode s testi
Fluent Assertions 1.5 Lepša sintaksa izjav
Microsoft Visual Studio 2010, SP1 Razvojno okolje
Moq 4.0 Delo z nadomestki
Ninject 2.2.1.4 Vrivanje odvisnosti
NuGet 1.5 Delo s paketi (knjižnicami)
NUnit 2.5.10 Ogrodje za testiranje enot
ReSharper 6 Preoblikovanje in poganjanje testov
TortoiseSVN 1.6 SVN-ogrodje
VisualSVN 2.0.6 SVN-integracija za Visual Studio
Pomembna je tudi organizacija izvorne kode znotraj rešitve (Slika 27). Enote je potrebno
ločiti na ustrezne projekte in imenska področja.
Slika 27: Struktura rešitve
Testno voden razvoj programskih rešitev Stran 63
Za programsko kodo, ki ima podobno zgradbo, si je priporočljivo narediti predloge. Slika
28 prikazuje predlogo in njeno uporabo. Predloga predpisuje poimenovanje in sintakso
AAA NUnit-testa.
Slika 28: Predloga in uporaba
5.3 Rešitev
Po metodi TDD smo razvijali knjižnico, ki vsebuje poslovno logiko celotne programske
rešitve. Knjižnica poleg poslovne logike zajema tudi podatkovni nivo. Predstavitveni nivo
ni pokrit s testi, saj povečini le kliče metode vmesnika v vzorcu MVVM.
Praktični razvoj je dobro začeti s skico uporabniškega vmesnika (Slika 29), saj tako poleg
problema dobimo še uporabniški pogled na rešitve.
Testno voden razvoj programskih rešitev Stran 64
Slika 29: Skice vmesnikov
Potem se napiše enostaven test enote, ki predstavlja načrt programskega vmesnika, ki ga
bomo implementirali. Test poženemo (Slika 30), da se prepričamo, če pade. Šele potem
začnemo pisati programsko kodo. Vsake toliko počistimo za seboj in preoblikujemo
programsko kodo, da ustreza začrtanim smernicam razvoja.
Slika 30: Testi enot in pokritost kode
Nabor obsega 181 testov, od tega 126 testov enot, 49 integracijskih in 6 sistemskih testov.
Pokritost kode poslovnega in podatkovnega nivoja pa je 100 %.
Testno voden razvoj programskih rešitev Stran 65
Za dostop do baze se uporablja vzorec repozitorij, ki izhaja iz domenskega načrtovanja in
se uporablja kot vmesni člen med domenskim modelom in podatkovno bazo. Predstavlja
objektno orientiran pogled na hrambo podatkov, kar omogoča testiranje ob ustrezni
uporabi nadomestkov (Slika 31).
Slika 31: Uporaba nadomestkov pri testiranju
S podatkovno bazo upravlja Entity Framework, ki omogoča pretvorbo podatkovnega
modela v relacijsko bazo. Razvijalec samo definira razrede in povezave, ogrodje pa jih
preslika v entitete in relacije (Slika 32). Nad podatkovnim modelom pa je možno uporabiti
tudi dedovanje.
Testno voden razvoj programskih rešitev Stran 66
Slika 32: Entitetno relacijski model
Z uporabo vmesnikov in vrivanja odvisnosti dosežemo šibko sklopljenost med
komponentami sistema. Na takšen način razredi ne poznajo neposrednih povezav med
seboj (Slika 33).
Ogrodje za vrivanje odvisnosti in vzorec tovarniške metode pa poskrbita za izdelavo
objektov in vrivanje v konstruktorje, kjer se odvisnosti potrebujejo (Slika 34). Celotna
rešitev je dobesedno zlepljena v nekaj vrsticah kode. Ogrodje zna samo ustvariti konkretne
objekte, vmesnike pa je potrebno ročno povezati z ustreznimi objekti.
Testno voden razvoj programskih rešitev Stran 67
Slika 33: Razredni diagram
Testno voden razvoj programskih rešitev Stran 68
Slika 34: Vezava odvisnosti
Testi morajo biti preprosti, enako pa velja tudi za programsko kodo. Uporaba
razhroščevalnika zato naj ne bi bila pogosta. V primerih, ko pa je potreben, pa je dobro, da
se uporabi orodje, ki omogoča grafični prikaz poti v programski kodi. Na takšen način je
odkrivanje napak preglednejše in hitrejše.
Slika 35: Grafični prikaz poteka programa
Ob koncu implementacije nove funkcionalnosti najprej lokalno poženemo celoten nabor
testov. Izvorno kodo objavimo v SVN-repozitoriju samo v primeru, ko so vsi testi izvedeni
uspešno. To proži strežnik stalne integracije, ki podobno ponovi nad celotno programsko
rešitvijo v okolju, ki je podobno produkcijskemu (Slika 36).
Testno voden razvoj programskih rešitev Stran 69
Slika 36: Strežnik stalne integracije
Končna aplikacija ima prilagodljiv grafični vmesnik, ki je praktičen in enostaven za
uporabo (Slika 37).
Slika 37: Del rešitve
Testno voden razvoj programskih rešitev Stran 70
Strežnik stalne integracije pa vodi tudi nekaj lastnih statistik, ki poenostavijo spremljanje
razvoja projektov (Slika 38).
Slika 38: CI statistika
Testno voden razvoj programskih rešitev Stran 71
6 ANALIZA
Med razvojem aplikacije smo skrbno beležili čas, ki smo ga porabili za posamezno
aktivnost.
Seznam aktivnosti, ki smo jih merili:
čas načrtovanja (analiza, učenje in načrtovanje),
implementacija (programiranje in oblikovanje),
pisanje testov,
popravljanje napak,
preoblikovanje in
vzdrževanja testov (popravljanje testov).
Za izvajanje meritev smo uporabljali spletno aplikacijo ClockingIT, ki omogoča enostavno
beleženje opravljenega dela (Slika 39).
Slika 39: Izpis dela na ClockingIT
Tabela 10: Meritve aktivnosti
Aktivnost Delovni čas Dejanski čas Trajanje [min] Porazdelitev
Vzdrževanje testov 4h 29min 4h 29min 269 4 %
Popravljanje napak 1d 1h 20min 9h 20min 560 9 %
Preoblikovanje 1d 4h 14min 12h 14min 734 12 %
Pisanje testov 1d 5h 23min 13h 23min 803 13 %
Načrtovanje 1d 5h 25min 13h 25min 805 13 %
Implementacija 1t 1d 3h 2min 2d 3h 2min 3062 49 %
Skupaj 2t 2d 7h 55min 4d 7h 53m 6233 100 %
Testno voden razvoj programskih rešitev Stran 72
Za dejansko uporabo teh meritev je bilo zbrane vrednosti najprej potrebno preračunati v
dejanski čas. Beležili smo namreč delovni čas – 8h/dan oz. 40h/teden. Dobljeni dejanski
čas smo tako pretvorili v minute in prikazali časovno porazdelitev truda po aktivnostih
(Slika 40).
Slika 40: Delež truda po posameznih aktivnostih
Za primerjavo smo v literaturi poiskali podatke o štirih projektih in primerjali časovno
porazdelitev aktivnosti pri razvoju. Projekti predstavljajo eksperiment na dodiplomskih in
podiplomskih študijskih programih računalništva [55]. Ker se aktivnosti niso čisto ujemale,
smo jih smiselno združili. Pisanje in vzdrževanje testov smo združili v testiranje. Tudi
preoblikovanje ni bilo v najdenih virih merjeno kot ločena aktivnost. Merili pa smo pregled
(angl. review). Preoblikovanje in popravljanje napak smo združili z implementacijo,
preglede pa z načrtovanjem.
Projekt, razvit s testiranjem po implementaciji (angl. Test-Last Development – TLD), pa
predstavlja enak projekt, le da je razvit na klasičen način.
Tabela 11: Porazdelitev aktivnosti (prirejeno po [55])
Aktivnost Projekt #1
(dodiplomski)
Projekt #2
(podiplomski)
Projekt #3
(podiplomski)
Projekt #4 TLD
(dodiplomski)
Praktični
del
Testiranje 10 % 15 % 22 % 17 % 17 %
Načrtovanje 40 % 62 % 32 % 29 % 13 %
Implementacija 50 % 23 % 46 % 54 % 70 %
Tako združeni podatki omogočajo primerjavo časovnih porazdelitev aktivnosti (Slika 41).
4% 9%
12%
13%
13%
49%
Aktivnosti pri TDD
Vzdrževanje testov
Popravljanje napak
Preoblikovanje
Pisanje testov
Načrtovanje
Implementacija
Testno voden razvoj programskih rešitev Stran 73
Slika 41: Primerjava časovnih porazdelitev TDD-projektov (prirejeno po [55])
10%
40%
50%
Projekt #1 (dodiplomski)
Testiranje
Načrtovanje
Implementacija
15%
62%
23%
Projekt #2 (podiplomski)
Testiranje
Načrtovanje
Implementacija
22%
32%
46%
Projekt #3 (podiplomski)
Testiranje
Načrtovanje
Implementacija
17%
13%
70%
Praktični del
Testiranje
Načrtovanje
Implementacija
Testno voden razvoj programskih rešitev Stran 74
Primerjalni projekt so izvajali študenti v skupinah skozi celoten semester. Študenti niso
imeli preteklih izkušenj s programiranjem po testu. Razvidno je, da testiranje predstavlja
najkrajšo aktivnost. Razbrati je možno tudi, da se delež testiranja povečuje z izkušenostjo
razvijalcev. Izkušenejši namenijo dosti več časa testiranju. Pričakovano je tudi obratno
sorazmerje med implementacijo in načrtovanjem. Več časa kot je vloženega v načrtovanje,
krajša je implementacija.
Slika 42 prikazuje primerjavo časovne porazdelitve primerjalnega projekta razvitega po
metodi TLD in našega praktičnega dela.
Slika 42: Primerjava praktičnega dela s TLD (prirejeno po [55])
Dodiplomski študenti so morali za svoje projekte izdelati še dokumentacijo – od tod tudi
občutna razlika pri načrtovanju. Primerjalni projekti so bili razviti v Javi in manj
kompleksni kot naš praktični del. To je tudi razvidno iz trajanja testiranja in
implementacije.
Časovna porazdelitev zelo malo pove o obsegu rešitve. Za jasnejšo sliko moramo
primerjati metrike.
17%
29% 54%
Projekt #4 (dodiplomski - TLD)
Testiranje
Načrtovanje
Implementacija
17%
13%
70%
Praktični del
Testiranje
Načrtovanje
Implementacija
Testno voden razvoj programskih rešitev Stran 75
Tabela 12: Primerjava metrik kode (prirejeno po [55])
Metrike kode Število
razredov Število
vrstic
[LOC]
Število vrstic
testov [TLOC] Vrstic kode
na metodo Pokritost
kode s testi
Projekt #1
(dodiplomski)
13 1053 168 12,1 28 %
Projekt #2
(podiplomski)
6 940 1999 22,38 92 %
Projekt #3
(podiplomski)
7 361 177 30,08 56 %
Projekt #4
(dodiplomski TLD)
4 259 38 7,4 25 %
Praktični del 29 775 828 3,91 100 %
Pokritost kode s testi je dosti višja pri izkušenejših razvijalcih. Razbrati je možno, da
metodologija razvoja zelo vpliva na pokritost kode s testi. Projekti razviti po TDD imajo
precej boljšo pokritost v primerjavi s TLD. Zanimivo je tudi, da so TDD-projekti dosegli
boljšo pokritost v krajšem času. Količina časa namenjenega testiranju se neposredno
odraža na pokritosti kode. Metodologija TDD sama po sebi ne zagotavlja majhnih razredov
in metod, implementacija je odvisna samo od razvijalcev.
Tabela 13: Primerjava metrik testov (prirejeno po [55])
Metrike testov TLOC/LOC Trditev/LOC Trditev/metodo Trditev/razred
Projekt #1
(dodiplomski)
0,16 0,03 0,32 1,00
Projekt #2
(podiplomski)
2,13 0,30 6,67 31,11
Projekt #3
(podiplomski)
0,49 0,12 3,75 4,50
Projekt #4
(dodiplomski TLD)
0,15 0,03 0,20 1,75
Praktični del 1,07 0,23 0,91 6,24
Večja pokritost kode s testi se izraža v večjem številu trditev (angl. Assertion) na
implementirano metodo. Število izjav je bistveno večje pri TDD-projektih. Izkušenejši
razvijalci, ki so dosegli zelo visoko pokritost, so napisali več kode za teste kot pa za
dejansko implementacijo. Daljše metode seveda potrebujejo več izjav, ki preverjajo
ustreznost.
Testno voden razvoj programskih rešitev Stran 76
Slika 43 prikazuje izračunane metrike kode v razvojnem okolju Visual Studio. Število
vrstic kode celotne rešitve (brez grafičnega vmesnika) je okoli 2600. Indeks vzdrževanja za
knjižnice, ki so bile razvite s pomočjo TDD, je 89. To pomeni, da so enostavne za
vzdrževanje.
Slika 43: Metrike praktičnega dela v Visual Studiu
Tabela 14: Ostale metrike praktičnega dela
Metrika Vrednost
Vejitvena kompleksnost (povp.) 9,43
Globina dedovanja (povp.) 1,31
Razredna sklopljenost (povp.) 8,89
Število metod 198
Število izjav 534
Število trditev 181
Število trditev na testno metodo 1
Čas pisanja posameznega testa [min] 4,4
Napisanih testov na uro 13,51
Čas izvajanja vseh testov [s] 10
Vejitvena kompleksnost za posamezen razred se giblje med 1 in 20. Kar pomeni, da so
razredi sestavljeni iz enostavnih metod. Globina dedovanja posameznih razredov ne
preseže globine 4. Indeks razredne sklopljenosti pa se giblje med 1 in 50. Zgovorno je tudi
približno enako število vrstic kode med testi in implementacijo, ki jo ti testi pokrivajo.
Testi imajo več vrstic kode predvsem zaradi dodatnih metod, ki skrbijo za pripravo in
čiščenje za testi. V povprečju ena izjava na implementirano metodo pomeni, da smo
testirali samo željeno obnašanje (angl. Happy path).
Testno voden razvoj programskih rešitev Stran 77
6.1 Zaključek
Iz podanih primerjav lahko sklepamo, da ni možno določiti vzorca časovnih porazdelitev
med začetnimi uporabniki metodologije TDD. Na porazdelitev vplivajo izkušnje
razvijalcev, poznavanje tehnologije in domene, natančnost specifikacij, uporabljena
metodologija, velikost razvojne skupine ipd.
Zelo zahtevno pa je tudi zbiranje podatkov o trajanju posameznih aktivnosti. Natančne
meritve praktično niso možne. Razlog tiči v agilnih metodologijah. V primerjavi s togimi
procesi razvoja so agilne metodologije zelo zabrisale prehode med posameznimi
aktivnostmi. Pri TDD je to še posebej očitno, saj se razvijalec stalno vrti med testiranjem,
implementacijo, preoblikovanjem in finim načrtovanjem (Slika 44). Prehodi med temi
aktivnostmi se dogajajo iz minute v minuto, zato je to zelo težko učinkovito meriti.
Slika 44: Primerjava TFP in TLD [55]
Razvijalci skladno z izkušnjami vlagajo čas v načrtovanje in testiranje. Rezultati tega so
neposredno vidni v trajanju implementacije. Rešitve razvite s TDD prinašajo večje število
napisanih testov. Več testov pa pomeni tudi večjo pokritost kode s testi. Razvijalci pa
dosežejo boljšo pokritost v krajšem času v primerjavi s TLD.
Pri notranji kvaliteti kode ni bilo mogoče zaznati vplivov metodologije. Kvaliteta kode je
odvisna predvsem od izkušenj razvijalcev in uporabljenih sodobnih konceptov razvoja
programskih rešitev.
Kombinacija načrtovanja in testiranja pri TDD pa zahteva svoj davek pri produktivnosti.
To sovpada s študijo, ki so jo naredili v podjetjih IBM in Microsoft [54]. Vsak projekt sta
ločeno razvijali različni ekipi. Rezultati kažejo, da so projekti, ki so bili razviti po metodi
TDD, trajali od 15–35 % več časa (Slika 45). Število napak pa je manjše za 39–91 %.
Ravno to pa najbolj zmanjšuje stroške razvoja programskih rešitev. Dobri rezultati niso
nujno posledica izbrane metodologije, lahko so samo posledica daljšega razvoja.
Testno voden razvoj programskih rešitev Stran 78
Potrebno pa se je zavedati, da te ekipe sestavljajo vrhunski strokovnjaki, ki poznajo orodja,
principe, domeno in imajo ogromno izkušenj. Zato bi bilo podobne rezultate utopično
pričakovati v slabših razvojnih ekipah.
Slika 45: Študija o TDD [54]
Zunanja kvaliteta kode je izboljšana, saj pisanje testov razvijalcu pomaga boljše razumeti
problem. Boljše poznavanje domene pa vodi do rešitev, ki boljše služijo naročniku. Visoka
frekvenca poganjanja testov daje razvijalcu večjo samozavest, regresijsko testiranje pa ga
varuje pred vnosom novih napak. Posledica tega je manj napak v kodi, kar vpliva tudi na
zmanjšano uporabo razhroščevalnika. TDD pa razvijalcu tudi pomaga hitreje odpravljati
napake, saj napisan test zelo vpliva na razumevanje napake. Metodologija zahteva izkušnje
in disciplino. Za izkušene razvijalce je to načrtovalska, za neizkušene pa testirna tehnika.
Komu je namenjena, ni več dvomov, dejstvo pa je, da je vedno bolj priljubljena (Slika 46).
Slika 46: Trend razpisanih delovnih mest za TDD [56]
Testno voden razvoj programskih rešitev Stran 79
7 SKLEP
Čeprav je metodologija testno vodenega razvoja stara že več kot desetletje, so jo razvijalci
začeli resno uporabljati šele pred nekaj leti. To ni nič čudnega, saj je potreben čas, da
skupnost razvije potrebna orodja in dobre prakse. Še vedno pa se razvija tudi sama
metodologija. Osnovo je predstavljalo programiranje po testu, ki je bila ena izmed praks v
ekstremnemu programiranju. Kasneje se je iz nje razvil testno voden razvoj, ki je z
dodanimi prevzemnimi testi postal razvoj voden s prevzemnimi testi. Vedenjsko voden
razvoj pa predstavlja trenutno zadnjo stopnjo. Vse različice so tesno povezane z
domenskim načrtovanjem, saj se dobro dopolnjujejo. Eksperimentalno voden razvoj pa ni
v širši uporabi, saj ni na voljo ustreznih orodij za posamezne jezike.
S širšo uporabo pa je testno voden razvoj postal kar nekakšen sinonim za profesionalne
razvijalce. V teoriji je metodologija precej preprosta, saj temelji na konceptih, ki so bili
znani že prej. Prednosti se najboljše vidijo v manjših agilnih moštvih, kjer se uporabljajo
sodobni koncepti razvoja programskih rešitev. V kombinaciji s stalno integracijo in
sodobnimi koncepti tvori lepo zaokrožen ekosistem, ki predstavlja inkubator za boljšo
programsko kodo.
Odzivnost na spremembe, razširljivost ter enostavnejše in cenejše vzdrževanje so le
nekatere od prednosti, ki jih prinaša. Testi predstavljajo ažurno dokumentacijo, regresijsko
testiranje pa preprečuje vnos novih napak. Od razvijalcev pa zahteva stalno učenje,
eksperimentiranje in disciplino.
Med razvojem praktičnega dela se je pokazala strma krivulja učenja. Brez poznavanja
praks in orodij je nemogoče začeti. Zato smo se morali najprej seznaniti z orodji in
koncepti, ki omogočajo načrtovanje rešitev s testiranjem v mislih. Tu gre za prakse, ki niso
strogo vezane na testno voden razvoj in bi jih moral poznati vsak resnejši programer.
Razvoj je trajal malo dlje časa, saj smo se prvič srečevali z metodologijo, orodji in tudi
tehnologijo, ki smo jo uporabili. Domena je predstavljala še najmanjši problem.
Za dobro poznavanje in uvedbo te metodologije znotraj organizacije je potreben čas. Zato
brez podpore vodstva zagotovo ne bo šlo. Samo s skupnimi močmi je možno doseči cilj –
razvoj boljših programskih rešitev z manj stroški. Potrebno je postaviti kvaliteto pred
kvantiteto. Tudi če naredimo samo kakšen testni projekt, se bodo dobljene izkušnje s
časom zagotovo obrestovale.
Testno voden razvoj programskih rešitev Stran 80
8 VIRI
[1] Agilemanifesto, (2001), Manifesto for Agile Software Development, [Elektronski],
pridobljeno 25. 10. 2010, iz http://www.agilemanifesto.org/
[2] Extreme Programming, Extreme Programming: A gentle introduction,
[Elektronski], pridobljeno 8. 11. 2010, iz http://www.extremeprogramming.org/
[3] K. Auer, R. Miller, Extreme Programming Applied – Playing to win, Addison-
Wesley, 2002
[4] S. Freeman, N. Pryce, Growing Object-Oriented Software, Guided by Tests,
Addison-Wesley, 2010
[5] M. Feathers, S. Freeman, (2009), Test Driven Development: Ten Years Of Test
Driven Development, [Elektronski], pridobljeno 27. 11. 2010, iz
http://c2.com/cgi/wiki?TenYearsOfTestDrivenDevelopment
[6] M. Feathers, S. Freeman, Test Driven Development: Ten Years Later,
[Elektronski], [Video], http://www.infoq.com/presentations/tdd-ten-years-later
[7] L. Koskela, Test Driven Practical TDD and Acceptance TDD for Java Developers,
Manning, 2007
[8] Wikipedia, Test-driven development, [Elektronski], pridobljeno 3. 12. 2010 , iz
http://en.wikipedia.org/wiki/Test-driven_development
[9] Test Driven Development, [Elektronski], pridobljeno 3. 12. 2010, iz
http://www.c2.com/cgi/wiki?TestDrivenDevelopment
[10] J. Carr, TDD Anti-Patterns, [Elektronski], pridobljeno 9. 12. 2010, iz
http://blog.james-carr.org/2006/11/03/tdd-anti-patterns/
[11] R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship,
Prentice Hall, 2009
[12] M. Fowler, K. Beck, J. Brant, W. Opdyke, D. Roberts, Refactoring: Improving
the Design of Existing Code, Addison Wesley, 1999
[13] M. Fewster, D. Graham, Software Test Automation Effective use of test
execution tools, ACM Press, Addison-Wesley, 1999
[14] G. Meszaros, xUnit Test Patterns - Refactoring Test Code, Addison Wesley, 2007
[15] K. Beck, C. Andres, Extreme Programming Explained: Embrace Change, Second
Edition, Addison Wesley, 2004
Testno voden razvoj programskih rešitev Stran 81
[16] K. Beck, M. Fowler, Planning Extreme Programming, Addison Weley, 2000
[17] Wikipedia, Software development process, [Elektronski], pridobljeno 3. 11. 2010,
iz http://en.wikipedia.org/wiki/Software_development_process
[18] Wikipedia, Agile software development, [Elektronski], pridobljeno 13. 11. 2010,
iz http://en.wikipedia.org/wiki/Agile_software_development
[19] Wikipedia, History of software engineering, [Elektronski], pridobljeno 13. 11.
2010, iz http://en.wikipedia.org/wiki/History_of_software_engineering
[20] K. Beck, Extreme Programming Explained: Embrace Change, Addison Wesley,
1999
[21] G. Wiseman, Iterating and Incrementing to 'Get What You Need', [Elektronski],
pridobljeno 9. 12. 2010, iz http://www.infoq.com/news/2008/01/iterating-and-
incrementing
[22] Ambysoft, How Agile Are You?, [Elektronski], pridobljeno 20. 12. 2010, iz
http://www.ambysoft.com/surveys/howAgileAreYou2010.html
[23] R. Jeffries, Essential XP: Emergent Design, [Elektronski], pridobljeno 25. 12.
2010, iz http://xprogramming.com/classics/expemergentdesign/
[24] C. Lisa, G. Janet, Agile Testing – A Practical Guide for Testers and Agile Teams,
Addison-Wesley, 2009
[25] Ambysoft, Why Agile Software Development Techniques Work: Improved
Feedback, [Elektronski], pridobljeno 11. 2. 2011, iz
http://www.ambysoft.com/essays/whyAgileWorksFeedback.html
[26] Wikipedia, List of unit testing frameworks, [Elektronski], pridobljeno 24. 2. 2011,
iz http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks
[27] R. Osherove, The Art Of Unit Testing With Examples In .NET, Manning, 2008
[28] R.V. Rajendran, White paper on Unit Testing, [Elektronski], pridobljeno 25. 2.
2011 iz http://www.mobilein.com/WhitePaperonUnitTesting.pdf
[29] G. Meszaros, TestDouble, [Elektronski], pridobljeno 26. 2. 2011 iz
http://martinfowler.com/bliki/TestDouble.html
[30] J. H, Oh, so that’s why you people have such long unit test names, [Elektronski],
pridobljeno 28. 2. 2011 iz http://jacksonh.tumblr.com/post/963132661/oh-so-thats-
why-you-people-have-such-long-unit-test
[31] P. Tahchiev, F. Leme, V. Massol, G. Gregory, JUnit in Action, Second Edition,
Manning, 2008
Testno voden razvoj programskih rešitev Stran 82
[32] Wikipedia, Code coverage, [Elektronski], pridobljeno 2. 3. 2011, iz
http://en.wikipedia.org/wiki/Code_coverage
[33] Wikipedia, Integration testing, [Elektronski], pridobljeno 2. 3. 2011, iz
http://en.wikipedia.org/wiki/Integration_testing
[34] Wikipedia, Acceptance testing, [Elektronski], pridobljeno 4. 3. 2011, iz
http://en.wikipedia.org/wiki/Acceptance_testing
[35] Google Testing Blog, Writing Testable Code, [Elektronski], pridobljeno 11. 5.
2011, iz http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-
decided-to.html
[36] Stackoverflow, Writing »unit testable« code?, [Elektronski], pridobljeno 11. 5.
2011, iz http://stackoverflow.com/questions/1007458/writing-unit-testable-code
[37] B. B. Agarwal, S. P. Tayal, M. Gupta, Software Engineering & Testing, Jones
and Bartlett Publishers, 2010
[38] T. Riley, A. Goucher, Beautiful Testing, O’Reilly, 2010
[39] D. Chelimsky, The RSpec Book – Behaviour-Driven Development with RSpec,
Cucumber, and Friends, The Pragmatic Bookshelf, 2010
[40] Wikipedia, Behavior Driven Development, [Elektronski], pridobljeno 19. 5. 2011,
iz http://en.wikipedia.org/wiki/Behavior_Driven_Development
[41] N. Lakshminarayan, BDD is more than »TDD done right«, [Elektronski],
pridobljeno 19. 5. 2011, iz http://neelnarayan.blogspot.com/2010/07/bdd-is-more-
than-tdd-done-right.html
[42] S. Auvray, Experiment Driven Development - The Post-Agile Way, [Elektronski],
pridobljeno 20. 5. 2011, iz http://www.infoq.com/news/2010/02/edd-post-agile
[43] P. Chopra, The Ultimate Guide To A/B Testing, [Elektronski], pridobljeno 20. 5.
2011, iz http://www.smashingmagazine.com/2010/06/24/the-ultimate-guide-to-a-b-
testing/
[44] A. Arkin, Vanity: Experiment Driven Development for Rails, [Elektronski],
pridobljeno 20. 5. 2011, iz http://vanity.labnotes.org/
[45] InfoQ, Domain-DrivenDesign Quickly, [Elektronski], pridobljeno 1. 8. 2011, iz
http://www.infoq.com/minibooks/domain-driven-design-quickly
[46] A. Ramdeo, System Testing, [Elektronski], pridobljeno 2. 8. 2011, iz
http://www.testinggeek.com/system-testing
Testno voden razvoj programskih rešitev Stran 83
[47] KabInfo, Learn System Testing, [Elektronski], pridobljeno 2. 8. 2011, iz
http://www.kabinfo.net/kabinfol_SystemTesting.asp
[48] P. Dhawan, Step By Step Guide To MVVM, [Elektronski], pridobljeno 2. 8. 2011,
iz http://www.codeproject.com/KB/WPF/StepByStepGuideToMVVM.aspx
[49] Wikipedia, Dependency injection, [Elektronski], pridobljeno 2. 8. 2011, iz
http://en.wikipedia.org/wiki/Dependency_injection
[50] G. Patel, IOC (Inversion of control) Basics With Example, [Elektronski],
pridobljeno 2. 8. 2011, iz http://www.goospoos.com/2009/12/inversion-of-control-
basics-with-example/
[51] J. Humble, D. Farley, Continuous Delivery: Reliable Software Releases through
Build, Test and Deployment Automation, Addison-Wesley, 2011
[52] Wikipedia, Design pattern, [Elektronski], pridobljeno 3. 8. 2011, iz
http://en.wikipedia.org/wiki/Design_pattern_(computer_science)
[53] R. C. Martin, The Principles of OOD, [Elektronski], pridobljeno 3. 8. 2011, iz
http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
[54] Typemock, The Cost of Test Driven Development, [Elektronski], pridobljeno 11.
8. 2011, iz http://blog.typemock.com/2009/03/cost-of-test-driven-development.html
[55] D. S. Janzen, An Empirical Evaluation of the Impact of Test-Driven Development
on Software Quality, [Elektronski], pridobljeno 30. 8. 2011, iz
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.94.9412&rep=rep1&type
[56] Indeed, Indeed – one search, all jobs, [Elektronski], pridobljeno 31. 8. 2011, iz
http://www.indeed.com/
Testno voden razvoj programskih rešitev Stran 84
9 PRILOGE
9.1 Seznam slik
Slika 1: Prakse ekstremnega programiranja [20] .................................................................. 6
Slika 2: Prakse pri XP2 [15] .................................................................................................. 7
Slika 3: Razvoj v ciklih in odvisnosti med njimi [9] ........................................................... 11
Slika 4: Inkrementalni in iterativni razvoj [21] ................................................................... 12
Slika 5: Primerjava klasičnega in TDD-cikla ter alternativ [7] ........................................... 14
Slika 6: TDD v osnovi [4] ................................................................................................... 15
Slika 7: Širši pogled na TDD oz. ATDD [4] ....................................................................... 18
Slika 8: Celotno razvojno okolje [6] ................................................................................... 19
Slika 9: Preverjanje ustreznosti v agilnih moštvih [22] ...................................................... 20
Slika 10: BDD-cikel [39] .................................................................................................... 21
Slika 11: EDD-razvojni cikel [42] ...................................................................................... 22
Slika 12: TDD in zastarela koda [7] .................................................................................... 23
Slika 13: Povratne informacije od testov [4] ....................................................................... 29
Slika 14: Faktorji avtomatiziranega testiranja [13] ............................................................. 34
Slika 15: Stroški in povratne informacije napak pri različnih tehnikah testiranja [25] ....... 39
Slika 16: Vrste testov in način izvajanja [14] ..................................................................... 41
Slika 17: Piramida testov (prirejeno po [38]) ...................................................................... 43
Slika 18: Tipi testiranja enot in napake [28] ....................................................................... 44
Slika 19: Mreža objektov in testiranje z nadomestki [4] ..................................................... 45
Slika 20: Sistemsko testiranje [47] ...................................................................................... 49
Slika 21: Uporabniška zgodba [39] ..................................................................................... 52
Slika 22: Oblika scenarija [39] ............................................................................................ 52
Testno voden razvoj programskih rešitev Stran 85
Slika 23: A/B-testiranje [43]................................................................................................ 54
Slika 24: Primer poročila EDD [42] .................................................................................... 54
Slika 25: Vzorec MVVM [48] ............................................................................................. 55
Slika 26: Uporabljena infrastruktura ................................................................................... 60
Slika 27: Struktura rešitve ................................................................................................... 62
Slika 28: Predloga in uporaba .............................................................................................. 63
Slika 29: Skice vmesnikov .................................................................................................. 64
Slika 30: Testi enot in pokritost kode .................................................................................. 64
Slika 31: Uporaba nadomestkov pri testiranju .................................................................... 65
Slika 32: Entitetno relacijski model..................................................................................... 66
Slika 33: Razredni diagram ................................................................................................. 67
Slika 34: Vezava odvisnosti ................................................................................................ 68
Slika 35: Grafični prikaz poteka programa.......................................................................... 68
Slika 36: Strežnik stalne integracije .................................................................................... 69
Slika 37: Del rešitve ............................................................................................................ 69
Slika 38: CI statistika ........................................................................................................... 70
Slika 39: Izpis dela na ClockingIT ...................................................................................... 71
Slika 40: Delež truda po posameznih aktivnostih ............................................................... 72
Slika 41: Primerjava časovnih porazdelitev TDD-projektov (prirejeno po [55]) ................ 73
Slika 42: Primerjava praktičnega dela s TLD (prirejeno po [55]) ....................................... 74
Slika 43: Metrike praktičnega dela v Visual Studiu ............................................................ 76
Slika 44: Primerjava TFP in TLD [55] ................................................................................ 77
Slika 45: Študija o TDD [54] ............................................................................................... 78
Slika 46: Trend razpisanih delovnih mest za TDD [56] ...................................................... 78
Testno voden razvoj programskih rešitev Stran 86
9.2 Seznam tabel
Tabela 1: Mejniki TDD [5, 6] ............................................................................................... 9
Tabela 2: Seznam anti-vzorcev [10] .................................................................................... 31
Tabela 3: Ogrodja za testiranje enot [26] ............................................................................ 46
Tabela 4: Ogrodja za prevzemno testiranje [34] ................................................................. 51
Tabela 5: Seznam ogrodij za testiranje obnašanja [40] ....................................................... 53
Tabela 6: Infrastruktura ....................................................................................................... 60
Tabela 7: Koraki stalne integracije ...................................................................................... 61
Tabela 8: Seznam uporabljenih tehnologij .......................................................................... 61
Tabela 9: Seznam uporabljenih orodij ................................................................................ 62
Tabela 10: Meritve aktivnosti ............................................................................................. 71
Tabela 11: Porazdelitev aktivnosti (prirejeno po [55]) ....................................................... 72
Tabela 12: Primerjava metrik kode (prirejeno po [55]) ...................................................... 75
Tabela 13: Primerjava metrik testov (prirejeno po [55]) ..................................................... 75
Tabela 14: Ostale metrike praktičnega dela ........................................................................ 76
9.3 Naslov študenta
Ime in priimek: Bernard Atelšek
Naslov: Aškerčeva 5 b
Pošta: 3325 Šoštanj
Tel. študenta: +386 31 851 307
E-pošta študenta: [email protected]
Testno voden razvoj programskih rešitev Stran 87
9.4 Kratek življenjepis
Rojen: 10. 5. 1984 v Celju
Šolanje: 1991–1999 Osnovna šola Karla Destovnika Kajuha, Topolšica, Šoštanj
1999–2003 Poklicna in tehniška elektro in računalniška šola, Velenje
2003–2011 Fakulteta za elektrotehniko, računalništvo in informatiko, Maribor
Testno voden razvoj programskih rešitev Stran 88
Testno voden razvoj programskih rešitev Stran 89
Testno voden razvoj programskih rešitev Stran 90