TDD vs BDD: Differenze chiave, una funzionalità realizzata in entrambi i modi e quando utilizzarli ciascuno
TDD aiuta gli sviluppatori a progettare il codice corretto. BDD aiuta un intero team a concordare cosa dovrebbe fare il software. Scopri la stessa funzionalità di sconto creata con ciascun approccio e scopri come i due interagiscono.
- TDD significa scrivere prima un test fallito, poi il codice sufficiente per superarlo, quindi riordinare.
- BDD descrive il comportamento in un linguaggio condiviso, solitamente come Dato, Quando, Allora.
- TDD guida principalmente la progettazione del codice. BDD costruisce principalmente una comprensione condivisa.
- Non sono rivali e molti team utilizzano BDD per le funzionalità e TDD per i dettagli riportati di seguito.
Il dibattito TDD vs BDD viene spesso inquadrato come la scelta di una parte. In realtà, rispondono a domande diverse. Lo sviluppo basato sui test chiede se il codice fa ciò che lo sviluppatore intendeva, mentre lo sviluppo basato sul comportamento chiede se il team è d’accordo su cosa dovrebbe fare il software.
Per rendere concreta la differenza, creiamo una piccola funzione di sconto con ogni approccio. Abbiamo eseguito il codice su Python 3.13, utilizzando la libreria standard, e i risultati seguenti provengono da quell’esecuzione. La parte BDD utilizza un nostro piccolo runner per mostrare il meccanismo, quindi un progetto reale utilizzerebbe uno strumento come Cucumber o si comporterebbe invece.
La risposta breve
Lo sviluppo basato sui test (TDD) è una pratica di codifica. Scrivi un piccolo test fallito, fallo passare con il codice più semplice e poi migliori il design. I test sono scritti in codice e principalmente dagli sviluppatori. Kent Beck ha reso popolare la pratica.
Lo sviluppo guidato dal comportamento (BDD) è una pratica di collaborazione nata dal TDD e Dan North ha introdotto il termine. Sviluppatori, tester e uomini d’affari concordano su esempi di come dovrebbe comportarsi il sistema, scritti in un linguaggio semplice, e tali esempi possono quindi essere automatizzati.
TDD e BDD fianco a fianco
| Aspetto | TDD | BDD |
|---|---|---|
| Obiettivo principale | Codice corretto e ben progettato | Comprensione condivisa del comportamento |
| Scritto dentro | Linguaggio di programmazione | Linguaggio semplice, come Gherkin |
| Scritto da | Sviluppatori | Business, QA e sviluppatori insieme |
| Leggibile da | Sviluppatori | Tutti nella squadra |
| Livello tipico | Unità | Caratteristica o accettazione |
| Punto di partenza | Un test fallito | Una conversazione con esempi |
| Ciclo | Rosso, verde, refactoring | Scopri, formula, automatizza |
| Strumenti comuni | pytest, JUnit, Jest | Cetriolo, comportati bene, SpecFlow, Reqnroll |
La caratteristica
Ecco la regola aziendale che implementeremo: Gli ordini di 1.000 o più ricevono uno sconto del 10%. I membri ricevono uno sconto extra del 5% su questi ordini.
Può sembrare chiaro, ma è già ambiguo. Un membro che spende 500 ottiene uno sconto del 5%? La frase dice “quegli ordini”, quindi presumiamo di no. Tienilo a mente, perché questo è esattamente il tipo di domanda che BDD è progettato per far emergere presto.
Costruirlo con TDD
TDD segue un breve ciclo noto come red, green, refactor:
| Passo | Cosa fai |
|---|---|
| Rosso | Scrivi un test per un comportamento che ancora non esiste e guardalo fallire |
| Verde | Scrivi il codice più semplice che lo faccia passare |
| Refactoring | Migliora il codice, mantenendo i test verdi |
Questi sono i nostri test, con uno per ogni regola:
class DiscountTests(unittest.TestCase):
def test_no_discount_below_1000(self):
self.assertEqual(discount(999, False), 0)
def test_ten_percent_from_1000(self):
self.assertEqual(discount(1000, False), 100)
def test_member_gets_extra_five_percent(self):
self.assertEqual(discount(1000, True), 150)
Abbiamo quindi scritto il codice in tre fasi, eseguendo i test dopo ciascuna. Una sessione TDD rigorosa aggiunge un test alla volta e mostra la stessa progressione:
Fase 1: la cosa più semplice
def discount(total, is_member):
return 0
# 3 tests, 2 failed
# test_member_gets_extra_five_percent
# test_ten_percent_from_1000
Restituire zero sembra sciocco, ma dimostra che i test possono effettivamente fallire e l’unico test che supera è la regola “nessuno sconto”.
Fase 2: aggiungere la soglia
def discount(total, is_member):
if total >= 1000:
return total * 0.1
return 0
# 3 tests, 1 failed
# test_member_gets_extra_five_percent
Fase 3: aggiungi membri e ripulisci
def discount(total, is_member):
rate = 0.1 if total >= 1000 else 0
if is_member:
rate += 0.05
return round(total * rate, 2)
# 3 tests, 0 failed
Passano tutti e tre. Ma leggi attentamente questa versione. Offre a un membro che spende 500 uno sconto del 5%, che non è ciò che pensavamo significasse la regola. Nessun test ha coperto quel caso, quindi niente lo ha segnalato. TDD protegge solo il comportamento che pensavi di testare. Questa è una vera limitazione, ed è il motivo per cui le conversazioni sugli esempi sono così importanti.
Perché il ciclo è importante
- Il rosso dimostra che il test funziona. Un test che non ha mai fallito potrebbe non testare nulla.
- Il verde ti mantiene concentrato. Scrivi solo il codice necessario, il che aiuta a prevenire un’ingegneria eccessiva.
- Refactor mantiene elevata la qualità. I test ti danno la sicurezza necessaria per modificare il progetto in modo sicuro.
- Influenza il design. Il codice difficile da testare è spesso troppo strettamente accoppiato e lo noti subito.
Il nostro tutorial pytest mostra un modo più conveniente per scrivere test come questi e l’analisi dei valori limite spiega perché abbiamo scelto 999 e 1000 come valori.
Costruirlo con BDD
BDD inizia con una conversazione, non con gli strumenti. Spesso si tratta di un breve incontro noto come i tre amigos, con un addetto al prodotto, uno sviluppatore e un tester che discutono esempi concreti:
Tester: What about a member spending 500?
Product: Good question. No discount. It has to reach 1000 first.
Dev: And exactly 1000 counts?
Product: Yes, 1000 or more.
Quell’unico scambio avrebbe rilevato il problema nel nostro codice TDD. Quindi registrano i loro esempi in un formato strutturato chiamato Gherkin:
Feature: Order discounts
Scenario: Member reaches the discount threshold
Given a member with a cart worth 1000
When they view the amount to pay
Then the amount to pay is 850
Scenario: Member below the threshold gets no discount
Given a member with a cart worth 500
When they view the amount to pay
Then the amount to pay is 500
| Parola chiave | Scopo |
|---|---|
| Dato | La situazione di partenza |
| Quando | L’azione o l’evento |
| Poi | Il risultato atteso |
Chiunque nel team può leggerlo e dire “sì, è giusto” o “no, è sbagliato”. Anche se non lo automatizzi mai, lo scenario ha già svolto un lavoro utile.
Collegamento dei passaggi al codice
Gli strumenti collegano ciascuna riga in linguaggio semplice a una funzione, chiamata definizione del passaggio. Per mostrare l’idea, abbiamo scritto noi stessi una piccola versione ed eseguito il primo scenario:
@step("a member with a cart worth 1000")
def given(ctx):
ctx["cart"] = Cart(is_member=True)
ctx["cart"].add(1000)
@step("they view the amount to pay")
def when(ctx):
ctx["amount"] = ctx["cart"].payable()
@step("the amount to pay is 850")
def then(ctx):
assert ctx["amount"] == 850
# ok Given a member with a cart worth 1000
# ok When they view the amount to pay
# ok Then the amount to pay is 850
Gli strumenti reali aggiungono parametri, in modo che un singolo passaggio possa corrispondere sia a “1000” che a “500”, oltre al reporting. Il risultato è una documentazione vivente: una semplice descrizione del sistema che fallisce clamorosamente se smette di essere vera.
Suggerimento: Il valore di BDD risiede nella conversazione. I team in cui una persona scrive Gherkin da sola una volta terminato il codice ottengono il costo dello strumento, senza quasi alcun vantaggio.
Scrivere buoni scenari
| Debole | Meglio |
|---|---|
Quando faccio clic sul pulsante con ID "btn-77" |
Quando applico il coupon |
Allora il risultato è corretto |
Quindi l'importo da pagare è 850 |
| Venti passi in uno scenario | Un comportamento per scenario |
| Descrive lo schermo | Descrive l’intento commerciale |
Gli scenari che descrivono clic e ID campo si interrompono a ogni riprogettazione e i lettori aziendali smettono di leggerli. Conserva i dettagli dell’interfaccia utente nel codice del passaggio. Questi scenari sono cugini stretti degli esempi in caso di test vs scenario di test e sono spesso utilizzati anche nei test di accettazione dell’utente.
Usandoli entrambi insieme
I due si inseriscono naturalmente in un flusso di lavoro “outside-in”:
- Il team concorda sul comportamento e scrive uno scenario BDD. Fallisce, perché non esiste ancora nulla.
- Uno sviluppatore utilizza TDD per costruire le piccole unità necessarie.
- I test unitari vengono superati e, alla fine, anche lo scenario.
- Tutto viene eseguito nella tua pipeline CI/CD.
Mantieni ciascuno al giusto livello. Dovresti avere alcuni scenari per comportamenti importanti e molti test unitari veloci per i dettagli. La nostra guida ai test unitari e di integrazione copre l’equilibrio.
Strumenti
| Lingua | TDD (test unitario) | BDD |
|---|---|---|
| Pitone | pytest, unittest | comportati bene, pytest-bdd |
| JavaScript | Scherzo, Vitest | Cucumber.js |
| Giava | JUnit, TestNG | Cetriolo-JVM |
| .NET | xUnità, NUnità | Reqnroll, SpecFlow |
Quando utilizzarli ciascuno
Il TDD si adatta bene quando:
- Stai scrivendo una logica complessa, come prezzi, analisi o autorizzazioni.
- Stai correggendo un bug, poiché puoi riprodurlo prima con un test fallito.
- Stai lavorando su un codice che cambierà spesso.
BDD si adatta bene quando:
- Le regole aziendali sono complesse e le incomprensioni sono costose.
- Le persone non tecniche leggeranno e discuteranno davvero gli scenari.
- Diversi team necessitano di una definizione condivisa di come si comporta qualcosa.
Potrebbero essere eccessivi quando: stai scrivendo un prototipo usa e getta o esplorando un’idea tecnica poco chiara. Molti sviluppatori prima effettuano il picco, poi riscrivono con i test. Anche gli strumenti BDD sono uno scarso investimento se nessuno al di fuori dell’ingegneria esamina mai gli scenari. In tal caso, i test semplici con nomi validi sono più semplici.
Errori comuni
- Trattare BDD come strumento di test. Il cetriolo senza collaborazione è solo un modo dettagliato per scrivere test.
- Implementazione del test in TDD. I test legati ai dettagli privati si interrompono durante ogni refactoring.
- Saltando il passaggio del refactoring. Puoi superare i test anche se il codice è disordinato.
- Scrivere test dopo il codice e chiamarlo TDD. È comunque utile, ma si perde il feedback sulla progettazione.
- Scrivere troppi scenari dell’interfaccia utente. Questi diventano lenti e instabili. Vedi test instabili.
- Alla ricerca di numeri di copertura. Una buona copertura non tiene ancora conto di comportamenti che nessuno ha preso in considerazione, come mostra il nostro esempio.
Considerazioni finali
TDD ti aiuta a costruire la cosa giusta e BDD ti aiuta a costruire la cosa giusta. Il nostro esempio di sconto ha mostrato entrambi i lati: il codice ha superato tutti i test, ma una semplice conversazione ha rivelato un presupposto sbagliato. Provalo sul tuo prossimo piccolo servizio. Scrivi tre esempi concreti con un collega prima di qualsiasi codice, quindi costruiscilo un test fallito alla volta.
Domande frequenti
Qual è la differenza principale tra TDD e BDD?
TDD si concentra sugli sviluppatori che scrivono test di codice falliti prima dell’implementazione. BDD si concentra su un intero team che concorda sul comportamento utilizzando esempi in linguaggio semplice.
Puoi usare TDD e BDD insieme?
Sì, e molte squadre lo fanno. Gli scenari BDD definiscono le funzionalità dall’esterno e TDD guida le unità più piccole di codice sottostanti.
BDD è migliore di TDD?
Nessuno dei due è complessivamente migliore, poiché risolvono problemi diversi. BDD aiuta con la comprensione condivisa e TDD aiuta con la progettazione del codice e un feedback rapido.
Cos’è il Gherkin?
Gherkin è un formato strutturato in linguaggio semplice per scrivere scenari, utilizzando parole chiave come Dato, Quando e Allora. Strumenti come Cucumber possono eseguirlo.
Il TDD rallenta lo sviluppo?
All’inizio può sembrare più lento, soprattutto durante l’apprendimento. I team spesso recuperano tempo riducendo i bug e apportando modifiche più sicure, anche se l’effetto varia in base al progetto.
Continua a imparare
Hai bisogno di aiuto con test, WordPress o SEO?
Aiutiamo i team a fornire software migliore e a far crescere i loro siti. Raccontaci a cosa stai lavorando e ti suggeriremo chiaramente il prossimo passo.
Altre guide su test, intelligenza artificiale e sviluppo moderno
Ti è piaciuto questo articolo? Scegli un argomento e passa direttamente ai nostri tutorial più recenti.
Spiegazione delle allucinazioni dell'intelligenza artificiale: perché i modelli linguistici inventano le cose e come ridurle
Spiegazione delle allucinazioni dell'intelligenza artificiale: cosa sono, perché i modelli linguistici le producono, tipi comuni, come ridurle con b…
Leggi l'articolo →DevOpsSpiegazione delle variabili d'ambiente: file .env, segreti e configurazione eseguiti correttamente
Variabili d'ambiente spiegate con esempi Python che abbiamo eseguito: lettura e impostazione, perché ogni valore è una stringa, file .env, ereditari…
Leggi l'articolo →Sviluppo softwareSpiegazione della limitazione della velocità API: algoritmi, intestazioni e gestione degli errori 429
La limitazione della velocità dell'API spiegata con una simulazione che abbiamo eseguito: bucket di token, finestra fissa, finestra scorrevole, risp…
Leggi l'articolo →Automazione dei testDerisione in Python: unittest.mock, patch e gli errori che fanno mentire i test
Mocking in Python con unittest.mock, testato: Mock e return_value, side_effect, patch dove viene utilizzato un nome, specifiche e autospec, date moc…
Leggi l'articolo →Test del softwareSpiegazione della copertura del codice: copertura di linea o di filiale e cosa significa realmente il 100%.
Copertura del codice spiegata con una vera esecuzione di cover.py: copertura di linea, ramo e condizione, lettura di un rapporto, perché la copertur…
Leggi l'articolo →Database e SQLDELETE vs TRUNCATE vs DROP in SQL: differenze, rollback e quando utilizzarli
DELETE vs TRUNCATE vs DROP spiegato con SQL testato: cosa rimuove, rollback, trigger, reimpostazione dell'identità, velocità e come differiscono Pos…
Leggi l'articolo →Database e SQLSpiegazione delle visualizzazioni SQL: crea, utilizza ed evita le insidie, con esempi
Viste SQL spiegate con le query che abbiamo eseguito: creazione di una vista, risultati in tempo reale, nascondere colonne, prestazioni, viste aggio…
Leggi l'articolo →Sviluppo softwareInterfaccia TypeScript e tipo: differenze reali, testate con il compilatore
Interfaccia TypeScript vs tipo, verificata con il vero compilatore: cosa possono fare entrambi, cosa può fare solo uno, fusione delle dichiarazioni,…
Leggi l'articolo →Sviluppo softwareSpiegazione di Python *args e **kwargs: funzioni flessibili con esempi chiari
Python *args e **kwargs spiegati con il codice che abbiamo eseguito: compressione e decompressione, ordine dei parametri, argomenti solo per parole …
Leggi l'articolo →Sviluppo softwareFunzioni Lambda Python: sintassi, usi reali e quando non usarli
Funzioni lambda Python spiegate con il codice che abbiamo eseguito: sintassi, ordinamento con chiave, mappa e filtro, chiusure, trap dell'associazio…
Leggi l'articolo →Intelligenza artificialeCosa sono gli agenti AI? Come funzionano, con un semplice ciclo di agenti che puoi eseguire
Cosa sono gli agenti IA? Una semplice guida al ciclo dell'agente, agli strumenti, alla memoria e ai guardrail, con un piccolo ciclo Python che abbia…
Leggi l'articolo →DevOpsMonitoraggio vs osservabilità: la vera differenza, con log, metriche e tracce
Spiegazione del monitoraggio e dell'osservabilità: errori noti e sconosciuti, log, metriche e tracce, una procedura dettagliata sugli incidenti di p…
Leggi l'articolo →DevOpsVolumi Docker e montaggi di associazione: mantenere i dati del contenitore nel modo giusto
Volumi Docker e montaggi di collegamento spiegati: perché i dati del contenitore scompaiono, volumi denominati, montaggi di collegamento, tmpfs, ese…
Leggi l'articolo →Automazione dei testTDD vs BDD: differenze chiave, una caratteristica costruita in entrambi i modi e quando utilizzarli ciascuno
TDD vs BDD spiegato con una funzione di sconto costruita in entrambi i modi: refactoring rosso-verde con il codice che abbiamo eseguito, scenari "Da…
Leggi l'articolo →Sviluppo softwareGit merge vs rebase: differenze, esempi e quando usare ciascuno
Git merge vs rebase spiegato con output reale del log, una tabella di confronto, la regola d'oro del rebase e un flusso di lavoro sicuro per i team.
Leggi l'articolo →Sviluppo softwareList comprehension in Python: sintassi, esempi e quando evitarla
List comprehension in Python spiegata: sintassi, filtri, if-else, cicli annidati, tempi misurati e i casi in cui un normale ciclo for è meglio.
Leggi l'articolo →Database e SQLSQL JOIN spiegati con esempi (Inner, Left, Right, Full)
Capisci i JOIN SQL passo dopo passo con tabelle e query di esempio: INNER, LEFT, RIGHT, FULL, CROSS e SELF, con errori comuni e consigli sulle prest…
Leggi l'articolo →