Immagine di copertina: Test-Driven Development: Guida Pratica all'Unit Testing

Test-Driven Development: Guida Pratica all'Unit Testing

2026/01/29

Quasi tutti gli sviluppatori dicono che l'unit testing è importante. Quasi nessuno lo scrive davvero, almeno non in modo sistematico. La verità è che scrivere test prima del codice sembra una perdita di tempo finché non hai pagato il conto di non averlo fatto.

Il Test-Driven Development ribalta l'ordine: prima scrivi il test (che fallisce), poi il codice che lo fa passare, poi rifattorizzi. Niente magia, solo un loop stretto fra cosa vuoi che faccia e cosa fa davvero. È parte delle metodologie Agili e dell'extreme programming.

L'unit testing puro è verificare la correttezza di singole unità di codice — funzioni o metodi piccoli — in isolamento. Lo scopo è banale: sapere se il pezzo funziona, senza dover lanciare l'intera applicazione.

I test fanno tre cose utili in più: documentano il comportamento atteso (più affidabili di un README che nessuno aggiorna), permettono di rifattorizzare senza paura, e ti dicono quando un cambiamento ha rotto qualcosa che funzionava — al volo, prima di mandare il codice in produzione.

Esempio pratico con xUnit, framework di testing per C#:

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

public class CalculatorTests
{
    [Fact]
    public void Add_ReturnsCorrectSum()
    {
        // Arrange
        var calculator = new Calculator();
        int a = 5, b = 3;

        // Act
        var result = calculator.Add(a, b);

        // Assert
        Assert.Equal(8, result);
    }
}

Tre fasi: Arrange (prepari), Act (esegui), Assert (verifichi). Test scritti così sono leggibili dopo sei mesi e fanno da documentazione del comportamento atteso.

Il loop del TDD

  • Scrivi una lista di casi di test
  • Scrivi un test per ogni nuova funzionalità
  • Verifica l'esito del test (scrivi codice finché non passa)
  • Refactoring del codice
  • Eventuale Commit/Deploy
  • Ripeti il processo durante tutto lo sviluppo

Quanto costa davvero

Non vendiamoci favole. Scrivere test costa tempo, e all'inizio sembra tempo "perso" rispetto a far partire la nuova feature. Ecco i tre costi reali, in ordine di impatto.

Tempo di scrittura iniziale

Scrivere test che coprano una funzionalità decentemente richiede 30-60% di tempo in più sulla feature stessa. Per un'azienda che misura sulla velocità di consegna, è una pillola difficile da ingoiare. Il payback è dopo 3-6 mesi, quando il primo bug grosso che avresti avuto in produzione lo prendi sul branch in 5 secondi.

Curva di apprendimento

Scrivere test buoni è un mestiere, non un riflesso automatico. La differenza fra un test utile e un test che fa solo finta di testare è l'esperienza. Serve tempo, qualche libro decente (Kent Beck su tutti) e qualche errore.

Codice non testabile

Le applicazioni grandi con molte dipendenze e codice spaghetti sono un incubo da testare. Per renderle testabili serve refactoring strutturale: dependency injection, separazione fra logica e I/O, rottura delle dipendenze cicliche. È la parte di TDD che spaventa di più, perché tocca il codice esistente.

Manutenzione dei test

I test invecchiano insieme al codice. Cambi una signature, devi sistemare 12 test. È un costo continuo che si riduce solo scrivendo test al livello giusto: troppo accoppiati all'implementazione e cambierai test ogni volta, troppo astratti e non testano niente.

Il punto

Bilanciare costi e benefici significa non testare tutto. La copertura 100% è un trofeo, non un obiettivo. Concentrati sulla business logic, sui calcoli, sulle aree dove un bug costa soldi veri. Il resto può vivere senza test, almeno all'inizio.

Cosa testare e cosa no

L'errore più comune è inseguire la code coverage come fosse un KPI. Una copertura del 90% su getter/setter è inutile. Una copertura del 60% sulla business logic critica vale il doppio.

Le cose che vale la pena testare sempre

Calcoli (importi, sconti, IVA), regole di business ("un ordine sopra 100€ ha sconto X"), parser e validatori, tutto ciò che ha edge case (date, fusi orari, valute). Il costo del test è basso e la regressione, quando arriva, costa caro.

Le cose che spesso non conviene testare

Codice glue (controller che chiamano servizi che chiamano repo), wrapper banali, codice che cambia ogni settimana per A/B test. Lì conviene di più un test di integrazione che copre il flusso end-to-end con un test unit per ogni metodo singolo.

Mocking, ma con misura

Il mocking serve a isolare la unità sotto test dalle dipendenze esterne (database, API). Se ti ritrovi a mockare metà del file di test, di solito è il codice che ha troppe dipendenze, non il mock che è sbagliato.

La parte umana, che è quella vera

Il TDD non funziona se solo uno del team lo applica. I test diventano un peso che gli altri ignorano o eliminano alla prima occasione. Funziona quando il team intero lo adotta, il management dà il tempo, e nessuno spedisce codice senza test "perché è solo un fix veloce". È più una questione di cultura che di tecnologia.

CI: i test che girano da soli

I test scritti e mai eseguiti automaticamente valgono poco. Esempio di pipeline GitHub Actions che gira la suite a ogni push:

name: .NET

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'
      - name: Build with dotnet
        run: dotnet build YourSolution.sln --configuration Release
      - name: Run tests
        run: dotnet test YourSolution.sln

Con questa CI, ogni commit passa la suite prima di poter essere mergiato. È quello che trasforma "test scritti" in "test che proteggono davvero la codebase".

Il TDD non è una religione, è un investimento. Costa di più all'inizio e fa risparmiare molto di più dopo, ma solo se lo applichi dove ha senso e il team ti accompagna.

"Write tests until fear is transformed into boredom" - Kent Beck

Parliamone!

Raccontaci il tuo progetto e ti risponderemo al più presto.