La differenza fondamentale, secondo me, è che i test di integrazione rivelano se una funzionalità funziona o è rotta, poiché sottolineano il codice in uno scenario vicino alla realtà. Invocano uno o più metodi o funzionalità del software e verificano se agiscono come previsto.
Al contrario, un test unitario che verifica un singolo metodo si basa sul presupposto (spesso sbagliato) che il resto del software funzioni correttamente, perché prende in giro esplicitamente ogni dipendenza.
Pertanto, quando un unit test per un metodo che implementa alcune funzionalità è verde, ciò non significa che la funzionalità funzioni.
Supponi di avere un metodo da qualche parte come questo:
public SomeResults DoSomething(someInput) {
var someResult = [Do your job with someInput];
Log.TrackTheFactYouDidYourJob();
return someResults;
}
DoSomething
è molto importante per il tuo cliente: è una caratteristica, l'unica cosa che conta. Ecco perché di solito scrivi una specifica del cetriolo affermandola: desideri verificare e comunicare se la funzione funziona o meno.
Feature: To be able to do something
In order to do something
As someone
I want the system to do this thing
Scenario: A sample one
Given this situation
When I do something
Then what I get is what I was expecting for
Senza dubbio: se il test ha esito positivo, puoi affermare che stai offrendo una funzionalità funzionante. Questo è ciò che puoi chiamare Business Value .
Se vuoi scrivere un test unitario DoSomething
dovresti far finta (usando alcune simulazioni) che il resto delle classi e dei metodi stiano funzionando (cioè che tutte le dipendenze che il metodo sta usando stiano funzionando correttamente) e affermare che il tuo metodo sta funzionando.
In pratica, fai qualcosa del tipo:
public SomeResults DoSomething(someInput) {
var someResult = [Do your job with someInput];
FakeAlwaysWorkingLog.TrackTheFactYouDidYourJob(); // Using a mock Log
return someResults;
}
Puoi farlo con Dependency Injection, o qualche metodo Factory o qualsiasi Mock Framework o semplicemente estendendo la classe sotto test.
Supponiamo che ci sia un bug Log.DoSomething()
. Fortunatamente, le specifiche Gherkin lo troveranno e i tuoi test end-to-end falliranno.
La funzione non funzionerà, perché Log
è rotta, non perché [Do your job with someInput]
non sta facendo il suo lavoro. E, a proposito, [Do your job with someInput]
è la sola responsabilità di quel metodo.
Supponiamo inoltre che Log
venga utilizzato in altre 100 funzioni, in altri 100 metodi di altre 100 classi.
Sì, 100 funzioni falliranno. Ma, fortunatamente, anche 100 test end-to-end stanno fallendo e stanno rivelando il problema. E sì: stanno dicendo la verità .
Sono informazioni molto utili: so di avere un prodotto rotto. Sono anche informazioni molto confuse: non mi dice nulla su dove si trovi il problema. Mi comunica il sintomo, non la causa principale.
Tuttavia, DoSomething
il test unitario è verde, perché utilizza un falso Log
, costruito per non rompersi mai. E sì: sta chiaramente mentendo . Sta comunicando che una funzionalità non funzionante funziona. Come può essere utile?
(Se DoSomething()
il test unitario fallisce, assicurati: [Do your job with someInput]
ha alcuni bug.)
Supponiamo che questo sia un sistema con una classe rotta:
Un singolo bug interromperà diverse funzionalità e vari test di integrazione falliranno.
D'altra parte, lo stesso bug interromperà solo un test unitario.
Ora confronta i due scenari.
Lo stesso bug interromperà solo un test unitario.
- Tutte le tue funzionalità che usano il rotto
Log
sono rosse
- Tutti i test unitari sono verdi, solo il test unitario
Log
è rosso
In realtà, i test unitari per tutti i moduli che utilizzano una funzionalità non funzionante sono verdi perché, usando i mock, hanno rimosso le dipendenze. In altre parole, corrono in un mondo ideale, completamente immaginario. E questo è l'unico modo per isolare i bug e cercarli. Test unitario significa beffardo. Se non stai deridendo, non stai testando l'unità.
La differenza
I test di integrazione indicano cosa non funziona. Ma non servono a indovinare dove potrebbe essere il problema.
I test unitari sono gli unici test che indicano dove si trova esattamente il bug. Per disegnare queste informazioni, devono eseguire il metodo in un ambiente deriso, dove tutte le altre dipendenze dovrebbero funzionare correttamente.
Ecco perché penso che la tua frase "O è solo un test unitario che si estende su 2 classi" è in qualche modo spostata. Un test unitario non dovrebbe mai comprendere 2 classi.
Questa risposta è fondamentalmente un riassunto di ciò che ho scritto qui: I test unitari mentono, ecco perché li adoro .