Behavior Driven Development (BDD) în Python
Behavior Driven Development (BDD) în Python
Teorie
Behavior Driven Development (BDD) este o metodologie de dezvoltare software care derivă din Test Driven Development (TDD). În BDD, accentul se pune pe definirea comportamentului aplicației din perspectiva utilizatorului final și pe colaborarea strânsă între dezvoltatori, testeri și oamenii de afaceri non-tehnici.
Principiile BDD
- Colaborare: BDD încurajează comunicarea și colaborarea între echipele de dezvoltare, testare și business. Toate părțile interesate contribuie la definirea cerințelor și a scenariilor de testare.
- Limbaj comun: Scenariile BDD sunt scrise într-un limbaj natural (limbajul Gherkin), ceea ce face ca cerințele și comportamentele să fie ușor de înțeles pentru toți membrii echipei.
- Documentație mereu actuală: Scenariile BDD servesc drept documentație vie a cerințelor și a comportamentelor aplicației. Pe măsură ce aplicația evoluează, scenariile BDD sunt actualizate pentru a reflecta noile cerințe.
Limbajul Gherkin
Gherkin este limbajul folosit pentru a scrie scenarii BDD. Este un limbaj simplu, structurat, care permite descrierea comportamentului aplicației prin exemple. Un fișier Gherkin (fișier .feature) este structurat astfel:
Feature: [Descrierea caracteristicii]
As a [rol]
I want [funcționalitate]
So that [beneficiu]
Scenario: [Descrierea scenariului]
Given [context inițial]
When [acțiune]
Then [rezultat așteptat]
Practică
Notă:
Dacă doriți să urmați acest tutorial în mod interactiv și să executați codul prezentat într-un editor de cod, direct în cloud, lansați următorul sandbox - VSCodeBehave necesită o structură specifică de fișiere pentru a funcționa, după cum urmează:
features/
steps/
bank_account_test.py
test_spec.feature
În folderul principal, creați un fișier numit bank_account.py
și adăugați următorul cod:
class BankAccount:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
if amount > 0:
self.balance += amount
else:
raise ValueError("Suma depusă nu poate fi mai mică sau egală cu 0.")
def withdraw(self, amount):
if amount > self.balance:
raise ValueError("Nu puteți retrage mai mult decât aveți în cont.")
elif amount <= 0:
raise ValueError("Nu puteți retrage o sumă mai mică sau egală cu 0.")
else:
self.balance -= amount
def get_balance(self):
return self.balance
După ce ați creat structura de mai sus cu toate folderele și fișierele,
puteți adăuga primul scenariu în fișierul bank_account_spec.feature
.
Feature: Administrarea contului bancar
Scenario: Crearea unui cont nou
Given Un cont nou este creat pentru 'Daniela R.'
Then Soldul trebuie să fie 0
Să implementăm acum testul corespunzător în Python pentru acest scenariu.
În fișierul bank_account_test.py
, adăugați următorul cod:
from behave import given, when, then
from bank_account import BankAccount
@given("Un cont nou este creat pentru {client}")
def test_account_creation(context, client):
context.account = BankAccount(client)
@then("Soldul trebuie să fie 0")
def test_initial_deposit(context):
assert context.account.get_deposit() == 0
Să rulăm acest cod pentru a valida corectitudinea testului.
behave features
După rularea testului, ar trebui să obținem următorul rezultat în consolă:
Feature: Administrarea contului bancar # features/test_spec.feature:1
Scenario: Crearea unui cont nou # features/test_spec.feature:2
Given Un cont nou este creat pentru 'Daniela R.' # features/steps/bank_account_test.py:4 0.000s
Then Soldul trebuie să fie 0 # features/steps/bank_account_test.py:8 0.000s
1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
2 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.000s
Hai să adăugăm mai multe scenarii de testare în fișierul de specificație tehnică.
Scenario: Depunerea de bani în cont
Given Un cont nou este creat pentru 'Daniela R.'
When 1000 Lei sunt depusi în cont
Then soldul initial trebuie să fie 1000 Lei
Scenario: Retragerea de bani din cont
Given Un cont nou este creat pentru 'Daniela R.'
And 1500 Lei sunt depusi în cont
When 550 Lei sunt retrasi din cont
Then soldul ramas trebuie să fie 950 Lei
Scenario: Încercarea de a retrage mai mulți bani decât sunt disponibili
Given Un cont nou este creat pentru 'Daniela R.'
And 1500 Lei sunt depusi în cont
When se încearca retragerea a 2500 Lei din cont
Then o eroare este afisata la ecran
sau
Feature: Administrarea contului bancar
Background:
Given Un cont nou este creat pentru 'Daniela R.'
Scenario: Verificarea soldului initial
Then Soldul initial trebuie să fie 0
Scenario: Depunerea de bani în cont
When 1000 Lei sunt depusi în cont
Then soldul trebuie să fie 1000 Lei
Scenario: Retragerea de bani din cont
Given 1500 Lei sunt depusi în cont
When 550 Lei sunt retrasi din cont
Then soldul ramas trebuie să fie 950 Lei
Scenario: Încercarea de a retrage mai mulți bani decât sunt disponibili
Given 2000 Lei sunt depusi în cont
When se încearca retragerea a 2500 Lei din cont
Then o eroare este afisata la ecran
Ambele abordări sunt acceptabile, însă unii ar putea argumenta că varianta a doua este tehnic greșită deoarece toate testele reutilizează aceeași clauză "Background", care stabilește precondițiile testului la nivel global, ceea ce le face interdependente. Este o practică bună ca testele să fie izolate și să nu depindă de factori externi, însă există situații în care prima variantă poate fi considerată rezonabilă. Este ideal să cunoașteți ambele metode și să decideți care este soluția mai potrivită în funcție de necesitățile proiectului vostru.
Pentru acest exemplu, am decis să optăm pentru prima opțiune.
Să implementăm acum restul testelor pentru acest fișier .feature
.
from behave import given, when, then
from bank_account import BankAccount
@given("Un cont nou este creat pentru {client}")
def test_account_creation(context, client):
context.account = BankAccount(client)
@then("Soldul initial trebuie să fie 0")
def test_initial_deposit(context):
assert context.account.get_balance() == 0
@when("1000 Lei sunt depusi în cont")
def test_money_deposit(context):
context.account.deposit(1000)
@then("soldul trebuie să fie 1000 Lei")
def test_check_balance_after_deposit(context):
assert context.account.get_balance() == 1000
@given("1500 Lei sunt depusi în cont")
def test_money_deposit_1(context):
context.account.deposit(1500)
@when("550 Lei sunt retrasi din cont")
def test_money_withdrawal(context):
context.account.withdraw(550)
@then("soldul ramas trebuie să fie 950 Lei")
def test_check_balance_after_withdrawal(context):
assert context.account.get_balance() == 950
@given("2000 Lei sunt depusi în cont")
def test_money_deposit_2(context):
context.account.deposit(1500)
@when("se încearca retragerea a 2500 Lei din cont")
def test_exceeding_sum_withdrawal(context):
try:
context.account.withdraw(2500)
except ValueError as e:
context.error = e
@then("o eroare este afisata la ecran")
def test_error_after_withdrawal(context):
assert context.error.args[0] == "Nu puteti extrage mai mult decat aveti in cont"
Testele pot fi rulate ori de câte ori este nevoie folosind aceeași comandă.
behave features
sau dacă doriți să adăugați instrucțiuni print
care vor fi afișate la consolă:
behave features --no-capture --no-capture-stderr
In concluzie Python Behave oferă o modalitate elegantă și ușor de înțeles
pentru testarea comportamentală a aplicațiilor Python.
Prin definirea clară a scenariilor în fișiere .feature
și implementarea pașilor în Python,
puteți asigura că aplicația funcționează conform specificațiilor
și că toate funcționalitățile sunt testate într-un mod comprehensiv.