Behaviour Driven Development
BDD? TDD? Melyik milyen előnnyel szolgálhat számunkra? Milyen minőségjavulást hozhat, ha az egyik, vagy a másik módszerrel készítjük az implementációt? Melyik módszerrel tudjuk jobban az üzleti igényeket lefedni? Egyszerű példán keresztül bemutatva láthatjuk a különböző módszerekkel implementált alkalmazásokat és azok minőségbeli változásait.
Bevezetés
Dan North brit tesztelő újra és újra hasonló problémákba ütközött tréningjei során. Agile technikákkal kapcsolatban észrevette, hogy hallgatói és ő maga is munkája során ugyanazokkal a problémákkal szembesül. A hallgatók, akik unitteszteket írtak kódjaikhoz, általában a következő nehézségekkel találták szembe magukat:
- Nem értették, hol kezdjék el a tesztelést, miből induljanak ki?
- Melyek azok a részek, amit tesztelniük kell és melyeket nem?
- Mennyit kell tesztelniük?
- Nehéz megtalálni, megérteni, miért lett egy teszt sikertelen.
Akkoriban Test Driven Developmenttel (tesztvezérelt vagy tesztalapú fejlesztéssel) foglalkozott, ez szolgált a viselkedés alapú fejlesztés (BDD) alapjául. A BDD nem csak a TDD, hanem más, már meglévő fejlesztési és tesztelési metodikákat is felhasznál. Ilyenek pl. Domain-driven Development, Acceptance Test Driven Development, Definition of Done, User Stories , stb. A BDD lényege, hogy az adott szoftvertermék viselkedését veszi alapul, ezt használja fel tesztek, ill. az implementáció elkészítéséhez, ami azért jó, mert a viselkedés nagyon közel áll az üzleti követelményekhez, azaz jobban kiszolgálja a megrendelő elvárásait. Ez egyértelműen pozitív hatással van a minőségre.
Üzleti elvárások
Képzeljük el a következő problémát.
A megrendelőnk üzleti követelménye egy olyan program, ami kiszámítja és kiírja a képernyőre egy nem-negatív szám faktoriálisát. Hogyan valósítanánk ezt meg hagyományos szoftverfejlesztési modellben? Valószínűleg egy jelentős tervezési fázis után egyszer csak megérkezik a probléma egy fejlesztőhöz, aki megírja a programot, aminek a lelke maga a faktoriális függvény. A fejlesztőnk Pythonban implementált, és ezt a függvényt írta:def factorial (number):
if (number == 0) or (number == 1):
return 1
else:
return number*factorial(number – 1)
Jól látszik, hogy a klasszikus, rekurzív algoritmust valósította meg. A tesztelők már alig várták, hogy megírják a teszteket a programhoz. A következő teszteket specifikálták a követelmények alapján:
Input | Elvárt eredmény |
0 | 1 |
1 | 1 |
5 | 120 |
-2 | Wrong INPUT! |
3,2 | Wrong INPUT! |
asd | Wrong INPUT! |
Bizony, az utolsó 3 tesztesetnek nem felelt meg az implementáció, tesztelőink hibát találtak, ugyanis a függvény nem kezeli a hibás bemenetet. Ezek után fejlesztőnk a következőre javította a függvényt:def factorial(number):
if (number < 0):
print(“Wrong INPUT!”)
return -1
if (number == 0) or (number == 1):
return 1
else:
return number * factorial(number – 1)
A hibakezelést úgy oldotta meg, hogy ha egy nem nem-negatív input érkezik, akkor a függvénynek -1-et ad át. Így az utolsó 3 teszteset is lefut sikeresen.
Ugyanez TDD-ben
A Test Driven Developmentről manapság is könyveket írnak, ennek a cikknek nem feladata a TDD teljes tárgyalása, csak annyira említjük meg, amennyi kapcsolata van a BDD-vel. Nézzük, meg, hogy egy másik cég, aki TDD-ben fejleszt, hogyan valósítaná meg az előző feladatot. Szintén egy kezdeti tervezési fázis után nekiállnak a fejlesztésnek, azonban ebben a módszertanban a tesztek születnek meg először.
Tegyük fel, hogy teljesen ugyanazokat a teszteket specifikálják a tesztelők, mint az előbb, de még implementáció nincs hozzá. Ezek után a fejlesztők megírják az implementációt, de úgy, hogy szem előtt tartják a már megvalósított teszteket. Így születik meg pontosan ugyanaz a javított kód, mint az előző bekezdésben. Tehát a fejlesztők (beszélhetünk unit tesztről is, így a tesztelő és a fejlesztő személye egybe eshet) rá vannak kényszerítve, hogy megfeleljen az implementáció a már megírt teszteknek. Ezt a „tests first” megközelítést használják fel a BDD-ben is.
Ugyanez BDD-ben
Valósítsuk meg BDD-vel az előbbieket mi magunk! Az implementálást Pythonban készítjük el, a BDD-eszköz pedig legyen a lettuce (www.lettuce.it)! Az előbbi példa Gabriel Falcaotól származik, ő a lettuce megalkotója. A lettuce egy Python kiegészítés, ami lehetővé teszi a BDD-tesztek írását Pythonban. Először írjuk körül a viselkedését, ebből generálódnak a tesztesetek, majd folyamatosan készül el az implementáció is. Itt a legfőbb kapcsolat a TDD-vel, hogy a tesztek itt is megelőzik a megvalósítást.
Nézzük a gyakorlatban!
Először szerezzük be a lettuce-t! Az installáláshoz bővebben itt találunk segítséget: http://lettuce.it/intro/install.html#intro-install. Valamint érdemes megnézni a Quick Start Tutorial részt, ahol ugyanez a faktoriális probléma van részletezve.
Ha az installáláson túlvagyunk, hozzuk létre az alábbi könyvtárstruktúrát:<tetszőleges elérési út>\factorial
| tests
| features
– zero.feature
– steps.py
Hozzuk létre a zero.feature és a steps.py üres fájlokat. Egyikben írjuk le a viselkedést, a másikban az implementáció születik. Írjuk be a következőket a zero.feature fájlba:Feature: Compute factorial
In order to learn Lettuce
As beginners
We’ll implement factorial
Scenario: Factorial of 0
Given I have the number 0
When I compute its factorial
Then I see the number 1
A feature fájl Gherkin nyelven íródott, ami lehetővé teszi, hogy definiáljuk az éppen tesztelt feature-t (a faktoriális számítása), valamint a teszteseteket (Scenario). Ezek szerint az első tesztünk nullának a faktoriálisát ellenőrzi. Meghatározza a bemenetet (Given) és hogy milyen esemény hatására (When) milyen elvárt eredményt szeretnénk látni (Then). Bővebben a Gherkinről itt olvashatsz: https://github.com/cucumber/cucumber/wiki/Gherkin
Most írjuk meg az implementációt pythonban (steps.py):from lettuce import *
@step(‘I have the number (\d+)’)
def have_the_number(step, number):
world.number = int(number)
@step(‘I compute its factorial’)
def compute_its_factorial(step):
world.number = factorial(world.number)
@step(‘I see the number (\d+)’)
def check_number(step, expected):
expected = int(expected)
assert world.number == expected, \
“Got %d” % world.number
def factorial(number):
return -1
Jól látható, hogy a feature-fájlban található mondatokhoz tartozik egy-egy függvény, így lesznek végrehajthatóak a tesztjeink. Jelenleg a függvényünk -1-et ad vissza. Nyissunk egy parancssort, álljunk a tests könyvtárba, majd futtatáshoz egyszerűen írjuk be: lettuce
Ekkor a lettuce megtalálja a feature-fájlban leírt Feature részt, valamint az egyetlen tesztesetet, majd futtatja azt, ami hibára fut, hiszen 0 faktoriálisa nem -1. A lettuce az egyes mondatokra rákeres a python-fájlban, majd az azt követő utasításokat végrehajtja. Ezek a mondatok természetesen többször is felhasználhatóak a teszt során. Most javítsuk ki a függvényünket! Írjuk meg a rekurzív faktoriális függvényt! A steps.py-ban javítsuk át a függvényt erre:def factorial(number):
number = int(number)
if (number == 0) or (number == 1):
return 1
else:
return number*factorial(number-1)
Most futtassuk újra a lettuce-t! Azt látjuk, hogy egyetlen tesztünk zöldre fut. Most adjunk hozzá több Scenariót a feature fájlhoz! Azaz bővítsük ki a teszteseteket! Pl. ellenőrizzük 1, ill. 5 faktoriálisát is. Nézzenek ki így a feature fájl Scenariói: Scenario: Factorial of 0
Given I have the number 0
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 1
Given I have the number 1
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 5
Given I have the number 5
When I compute its factorial
Then I see the number 120
Futtassunk ismét, majd örüljünk, hiszen mind a 3 tesztünk pass. Egészítsük ki még 3 tesztesettel a Scenariókat, adjuk még hozzá a feature fájlhoz:
Scenario: Factorial of a minus number
Given I got a strange input, for example: -2
When I compute its factorial
Then I see an error message
Scenario: String input for factorial
Given I got a strange input, for example: asd
When I compute its factorial
Then I see an error message
Scenario: Factorial of a strange number
Given I got a strange input, for example: 3.12
When I compute its factorial
Then I see an error message
Ha most futtatunk, akkor faild-re fut az utolsó 3 tesztünk, hiszen még az implementáció nem kezeli a hibás bemenetet. Sőt az „I got a strange input, for example:” mondathoz/tesztlépéshez még nem tartozik utasítás, ezért is reklamál a lettuce. Javítsuk ki steps.py-t a következőre:
from lettuce import *
@step(‘I have the number (\d+)’)
def have_the_number(step, number):
world.number = int(number)
@step(u’I got a strange input, for example: (.*\D+)’)
def have_the_number(step, number):
world.number = -1
@step(‘I compute its factorial’)
def compute_its_factorial(step):
world.number = factorial(world.number)
@step(‘I see the number (\d+)’)
def check_number(step, expected):
expected = int(expected)
assert world.number == expected, “Got %d” % world.number
@step(‘I see an error message’)
def error_message(step):
assert world.number == -1, ‘Wrong INPUT!’
def factorial(number):
if (number<0):
print(“Wrong INPUT!”)
return -1
if (number==0) or (number==1):
return 1
else:
return number*factorial(number-1)
Jól látható, hogy implementáltuk a hibás bemenethez tartozó tesztlépést, ami nem tesz mást, mint -1-gyel hívja meg függvényünket, ami fel van készítve, hogy hiba üzenetet jelentessen meg, valamint speciálisan -1-et ad vissza. Ha most futtatunk, láthatjuk, az összes tesztesetünk sikeresen lefut.
Összefoglalás
A BDD legnagyobb előnye az érthetőség. Ha ugyanis a tesztünk hibára fut, jól látható, hogy melyik lépés volt sikertelen. Ezt olyan szöveges formában kapjuk meg, amit egy nem szakmai ember is megért. Sőt lehetőség van a Gherkin kifejezések testreszabására is. Így akár magyarul is definiálhatjuk a mondatokat. Ezek a mondatok nem csak azt mutatják meg, hogy hol futott hibára a teszt, hanem azt is, hogy éppen mit tesztelünk és az milyen üzleti követelménnyel van kapcsolatban, növelve ezzel a megértést és a tesztelési lefedettséget. Ma már minden programozási nyelvhez találunk BDD eszközt. A php-hez behat, a Javához JBehave, a c#-hoz SpecFlow vagy pl. a Ruby-hez a Cucumber áll a rendelkezésünkre.
Szerző: Tóth Árpád
A szerző
- Okleveles programozó matema- tikus. 3 éve foglalkozik szoft- verteszteléssel. Kezdetben a Nokia Siemens Networks, jelenleg pedig az IT-Services tesztmérnöke. Érdeklődési területe a modell alapú tesztelés, valamint a terheléses tesztelési technikák. Korábban kifejezetten funkcionális, jelenleg pedig performance tesztekkel foglal- kozik. A szakmán kívül, többszörös magyar bajnok formációs latin táncos. Show- tánc Európa- és világbajnok.
Cikkek
- 2013.02.12MódszertanA tesztterv halott!
- 2012.12.23Módszertan(Branch || Decision) ?
- 2011.11.23MódszertanBehaviour Driven Development
- 2011.09.01TeszttechnikákTeljesítménytesztelés