Glossario · Fondamenti del testing
Test double
Detto anche: controfigura di test, oggetto mock, mock object, test stub
Definizione
Qualsiasi oggetto che sostituisce una dipendenza reale in un test, come dummy, stub, fake, spy o mock, così il codice sotto test gira in modo veloce e prevedibile.
Un test double sostituisce una dipendenza reale, come un gateway di pagamento, un database, un orologio o un servizio email, con qualcosa che il test controlla. Il nome, reso popolare da Gerard Meszaros, viene dalla controfigura (stunt double) del cinema. I double rendono i test veloci e deterministici e ti permettono di simulare situazioni difficili da riprodurre nella realtà, come un timeout o una carta rifiutata.
I cinque tipi
| Tipo | Cosa fa | Esempio |
|---|---|---|
| Dummy | Passato solo per riempire un parametro, mai usato | Un logger null o vuoto che il metodo richiede ma che questo test non tocca mai |
| Stub | Restituisce risposte predefinite alle chiamate | rates.getRate("EUR") restituisce sempre 0.92 |
| Spy | Uno stub che registra anche come è stato chiamato | Registra che send() è stato chiamato una volta con un certo indirizzo |
| Mock | Preprogrammato con aspettative; il test verifica le interazioni | Verifica che gateway.charge(4999) sia stato chiamato esattamente una volta |
| Fake | Un’implementazione funzionante ma semplificata | Un repository in memoria basato su una HashMap |
Nel linguaggio comune si dice “mock” per tutti questi, e librerie come Mockito creano oggetti che possono fare da stub, spy o mock. Conoscere la distinzione aiuta comunque, soprattutto stub contro mock: uno stub fornisce input al codice sotto test (verifica dello stato), mentre un mock controlla gli output che il codice invia alle sue dipendenze (verifica del comportamento).
Esempio (JUnit 5 con Mockito)
@Test
void declinedCardDoesNotSendConfirmation() {
PaymentGateway gateway = mock(PaymentGateway.class);
EmailService email = mock(EmailService.class);
when(gateway.charge(4999)).thenReturn(PaymentResult.DECLINED); // stub
var checkout = new Checkout(gateway, email);
var result = checkout.placeOrder(4999);
assertEquals(OrderStatus.PAYMENT_FAILED, result.status());
verify(email, never()).sendConfirmation(any()); // mock-style verification
}
(Gli import statici da org.mockito.Mockito, org.mockito.ArgumentMatchers e org.junit.jupiter.api.Assertions sono omessi.)
Errori comuni
- Mockare tutto. I test che verificano ogni chiamata interna si rompono a ogni refactoring e possono passare mentre l’integrazione reale è rotta. Usa i mock ai confini architetturali; al loro interno preferisci oggetti reali o fake.
- Double che si allontanano dalla realtà. Un’API stubbata che restituisce un campo che il servizio reale ha rinominato mesi fa dà una falsa sicurezza. I contract test e qualche test di integrazione reale mantengono onesti i double.
- Mockare tipi che non ti appartengono. Incapsula i client di terze parti in una tua interfaccia e crea il double di quella.