Sviluppo Software

Strumenti, idee e visione per diventare uno sviluppatore consapevole, ambizioso e pronto a costruire il proprio futuro con coraggio e competenza.

Collaudo meccanico e codice C#
Come al banco di prova: validare i singoli componenti prima dell'assemblaggio finale.

Unit Testing in C#: il banco di collaudo che ti salva il venerdì sera

Scritto da Marco Morello il 10 Agosto 2026

💡 In sintesi: Nessun meccanico sano di mente revisionerebbe una pompa per poi richiudere serbatoio e carene sperando che la moto parta al primo colpo a trecento chilometri da casa. Prima, il pezzo si fissa alla morsa e si mette in pressione. Nel software, invece, spesso assembliamo tutto e testiamo avviando l'intera applicazione. In questo articolo esploriamo come strutturare un banco di collaudo affidabile usando xUnit e il pattern AAA.


Prima di serrare l'ultimo dado sul telaio, il pezzo si fissa alla morsa. Si collegano i tubi di mandata, si attacca il manometro e si mette in pressione il circuito. Se una guarnizione perde o un paraolio cede sotto sforzo, lo scopri lì: su un banco d'acciaio pulito, in cinque minuti, con uno straccio a portata di mano. Non a piedi su un passo di montagna sotto la pioggia battente, né in corsia di sorpasso.

Nel software, invece, molti sviluppatori continuano a montare il motore direttamente sulla strada. Modificano tre righe nella logica di calcolo, premono F5, aspettano che l'applicazione compili, effettuano il login manuale a schermo, navigano attraverso quattro schermate, compilano un form con dati fittizi e guardano se il risultato torna.

Ripetono questo ciclo quindici volte al giorno. Poi rilasciano in produzione il venerdì pomeriggio e passano il fine settimana a rincorrere le eccezioni non gestite sollevate dagli utenti.

I test unitari non sono un esercizio accademico per puristi del codice o una voce da spuntare su una checklist aziendale: sono il banco di collaudo dei singoli componenti prima dell'assemblaggio finale.

1. L'illusione del "Non ho tempo per scrivere test"

L'obiezione più frequente nei team di sviluppo è sempre la stessa: "Dobbiamo consegnare la feature entro fine sprint, non abbiamo tempo per scrivere codice che testa altro codice".

È lo stesso ragionamento di chi dice di non avere tempo per controllare la pressione degli pneumatici prima di un viaggio di tremila chilometri perché deve partire subito. Finisci per fermarti ogni cinquanta chilometri a gonfiare la gomma a mano, chiedendoti perché il viaggio duri il doppio del previsto.

Testare manualmente avviando l'intera applicazione comporta costi occulti enormi:

L'evoluzione del costo di un bug nel ciclo di vita del software
Sviluppo
$ MINIMO
QA / Test
$$ MEDIO
Rilascio
$$$ ALTO
Produzione
$$$$ CRITICO

Scrivere unit test significa spostare il rilevamento del guasto a monte (Shift-Left), quando risolverlo costa pochi secondi e zero frustrazione.


2. Il Pattern AAA: Come si allestisce la prova

In xUnit (il framework di test standard de facto nell'ecosistema moderno di .NET), un test unitario ben strutturato segue una regola ferrea: il pattern AAA (Arrange, Act, Assert).

Se hai familiarità con un banco di flussaggio o una prova di trazione meccanica, il parallelo è millimetrico:

1. Arrange
Allestimento:
Morsa e Parametri iniziali
2. Act
Azione:
Esecuzione della sollecitazione
3. Assert
Verifica:
Misura reale vs Tolleranza

Immaginiamo di dover collaudare una logica di officina reale: un componente che calcola la correzione della coppia di serraggio quando si utilizza una prolunga sulla chiave dinamometrica. La formula fisica richiede che la coppia impostata sulla scala della chiave sia inferiore a quella effettiva richiesta sul bullone, in base alla lunghezza della prolunga.

Cimpostata = Ctarget × [ Lchiave / (Lchiave + Lprolunga) ]

Vediamo la classe di dominio pulita, isolata da qualsiasi database o interfaccia grafica:

namespace Officina.Core.Meccanica;

public class CalcolatoreDinamometrica
{
    public decimal CalcolaCoppiaScala(decimal coppiaTargetNm, decimal lunghezzaChiaveMm, decimal prolungaMm)
    {
        if (coppiaTargetNm <= 0)
            throw new ArgumentOutOfRangeException(nameof(coppiaTargetNm), "La coppia target deve essere positiva.");

        if (lunghezzaChiaveMm <= 0)
            throw new ArgumentOutOfRangeException(nameof(lunghezzaChiaveMm), "La lunghezza della chiave deve essere positiva.");

        if (prolungaMm < 0)
            throw new ArgumentOutOfRangeException(nameof(prolungaMm), "La prolunga non può avere misura negativa.");

        if (prolungaMm == 0)
            return coppiaTargetNm;

        decimal rapporto = lunghezzaChiaveMm / (lunghezzaChiaveMm + prolungaMm);
        return Math.Round(coppiaTargetNm * rapporto, 2);
    }
}

Ed ecco il relativo banco prova scritto con xUnit sfruttando l'attributo [Fact], che identifica un test a scenario singolo:

namespace Officina.Core.Tests;

using Xunit;
using Officina.Core.Meccanica;

public class CalcolatoreDinamometricaTests
{
    [Fact]
    public void CalcolaCoppiaScala_ConProlungaValida_DeveRidurreCoppiaImpostataCorrettamente()
    {
        // 1. Arrange (Allestimento del banco)
        var calcolatore = new CalcolatoreDinamometrica();
        decimal coppiaTarget = 100m;
        decimal lunghezzaChiave = 400m;
        decimal lunghezzaProlunga = 100m;
        decimal valoreAtteso = 80.00m; // 100 * (400 / 500) = 80

        // 2. Act (Esecuzione della misura)
        decimal risultatoReale = calcolatore.CalcolaCoppiaScala(coppiaTarget, lunghezzaChiave, lunghezzaProlunga);

        // 3. Assert (Verifica tolleranza)
        Assert.Equal(valoreAtteso, risultatoReale);
    }

    [Fact]
    public void CalcolaCoppiaScala_SenzaProlunga_DeveRestituireLaCoppiaTargetInalterata()
    {
        // Arrange
        var calcolatore = new CalcolatoreDinamometrica();
        decimal coppiaTarget = 50m;
        decimal lunghezzaChiave = 300m;
        decimal prolungaNulla = 0m;

        // Act
        decimal risultatoReale = calcolatore.CalcolaCoppiaScala(coppiaTarget, lunghezzaChiave, prolungaNulla);

        // Assert
        Assert.Equal(coppiaTarget, risultatoReale);
    }

    [Fact]
    public void CalcolaCoppiaScala_ConCoppiaTargetNegativa_DeveLanciareArgumentOutOfRangeException()
    {
        // Arrange
        var calcolatore = new CalcolatoreDinamometrica();

        // Act & Assert (Verifica della sicurezza intrinseca per scenari di errore)
        Assert.Throws<ArgumentOutOfRangeException>(() => 
            calcolatore.CalcolaCoppiaScala(-10m, 400m, 50m));
    }
}

La convenzione dei nomi è esplicita: Metodo_Condizione_RisultatoAtteso. Chiunque legga il report del test sa esattamente cosa si è rotto senza dover aprire il file sorgente.


3. [Theory] e [InlineData]: Il Collaudo Parametrico

Nella produzione reale non provi un solo pezzo a una sola temperatura. Vuoi vedere come risponde il componente al minimo, a regime nominale e al limite di snervamento.

Copiare e incollare lo stesso metodo di test dieci volte cambiando solo due numeri genera codice spazzatura, difficile da mantenere. Per questo xUnit mette a disposizione l'attributo [Theory] combinato con [InlineData]:

public class CollaudoIdraulicoTests
{
    [Theory]
    [InlineData(10, 100, 90.0)]   // Regime normale: 10 cicli = 90 bar residui
    [InlineData(50, 100, 50.0)]   // Mezza vita: 50 cicli = 50 bar residui
    [InlineData(90, 100, 10.0)]   // Fine vita: 90 cicli = 10 bar residui
    [InlineData(100, 100, 0.0)]   // Esaurimento totale: 0 bar
    public void CalcolaPressioneResidua_DatiCicliLavoro_RestituiscePressioneCorretta(
        int cicliEseguiti, 
        decimal pressioneInizialeBar, 
        decimal pressioneAttesaBar)
    {
        // Arrange
        var circuito = new CircuitoPneumatico();

        // Act
        decimal pressioneReale = circuito.CalcolaPressioneResidua(cicliEseguiti, pressioneInizialeBar);

        // Assert
        Assert.Equal(pressioneAttesaBar, pressioneReale);
    }
}

Con una sola dichiarazione, xUnit esegue quattro collaudi distinti nel terminale. Se la formula fallisce soltanto per il caso limite a 100 cicli, il runner isola esattamente la riga incriminata senza bloccare l'ispezione delle altre.


4. Cosa collaudare (e cosa lasciare stare)

Uno degli errori tipici di chi inizia con gli unit test è voler testare qualsiasi riga di codice:

Regola d'officina: Concentrati sulla logica di calcolo, sulle ramificazioni condizionali (gli if/else complessi), sulle eccezioni di guardia (valori nulli) e sui valori limite matematici (zero, negativi, overflow). Testa lì dove un errore costa ore di fermo macchina o frustrazione per l'utente.

5. Il Banco Prova: Metti alla prova la suite xUnit

Per toccare con mano la differenza tra un collaudo superato e una rottura a banco, usa il simulatore sottostante. Il codice simula il calcolo del decadimento di pressione idraulica in base ai cicli macchina.

[Banco Prova xUnit] CircuitoIdraulicoTests.cs IDLE
// Logica sotto test (SUT - System Under Test)
public decimal CalcolaPressioneResidua(int cicli, decimal barIniziali)
{
return barIniziali - (cicli * 1.0m);
}
Esito Test Runner:
In attesa di esecuzione banco prova...

Conclusioni: L'abitudine che fa la differenza

Imparare a scrivere test unitari all'inizio richiede uno sforzo cosciente: ti costringe a scrivere classi disaccoppiate, a rispettare il principio di singola responsabilità (SRP) e a evitare metodi monolitici da cinquecento righe impossibili da isolare.

Tuttavia, una volta acquisito l'automatismo, il ritorno sull'investimento è immediato:

Prossimamente...

Nel prossimo articolo affronteremo il gemello operativo del collaudo: la scatola nera del software. Vedremo come implementare un logging pratico e strutturato con ILogger per diagnosticare i problemi sul campo senza dover tirare a indovinare.

← Torna indietro