Qual è il vantaggio decisivo dei test unitari rispetto ai test di integrazione?
Questa è una falsa dicotomia.
I test unitari e i test di integrazione hanno due scopi simili, ma diversi. Lo scopo del test unitario è assicurarsi che i metodi funzionino. In termini pratici, i test unitari assicurano che il codice soddisfi il contratto indicato dai test unitari. Ciò è evidente nel modo in cui sono progettati i test unitari: essi specificano nello specifico cosa dovrebbe fare il codice e affermano che il codice lo fa.
I test di integrazione sono diversi. I test di integrazione esercitano l'interazione tra i componenti software. È possibile disporre di componenti software che superano tutti i test e continuano a non superare i test di integrazione perché non interagiscono correttamente.
Tuttavia, se i test unitari presentano un vantaggio decisivo, è questo: i test unitari sono molto più facili da impostare e richiedono molto meno tempo e sforzi rispetto ai test di integrazione. Se utilizzati correttamente, i test unitari incoraggiano lo sviluppo di codice "testabile", il che significa che il risultato finale sarà più affidabile, più facile da capire e più facile da mantenere. Il codice testabile ha alcune caratteristiche, come un'API coerente, un comportamento ripetibile e restituisce risultati facili da affermare.
I test di integrazione sono più difficili e più costosi, perché spesso hai bisogno di derisione elaborata, configurazione complessa e asserzioni difficili. Al massimo livello di integrazione del sistema, immagina di provare a simulare l'interazione umana in un'interfaccia utente. Interi sistemi software sono dedicati a questo tipo di automazione. Ed è l'automazione che stiamo cercando; i test umani non sono ripetibili e non si ridimensionano come i test automatici.
Infine, i test di integrazione non forniscono garanzie sulla copertura del codice. Quante combinazioni di loop di codice, condizioni e rami stai testando con i tuoi test di integrazione? Lo sai davvero? Esistono strumenti che è possibile utilizzare con unit test e metodi in fase di test che indicano la copertura del codice e la complessità ciclomatica del codice. Ma funzionano davvero bene solo a livello di metodo, dove vivono i test unitari.
Se i tuoi test cambiano ogni volta che fai refactoring, questo è un problema diverso. Si suppone che i test unitari riguardino la documentazione di ciò che fa il tuo software, dimostrando che lo fa e quindi dimostrando che lo fa di nuovo quando rifletti l'implementazione sottostante. Se la tua API cambia o hai bisogno che i tuoi metodi cambino in conformità con una modifica nella progettazione del sistema, è quello che dovrebbe succedere. Se sta succedendo molto, considera di scrivere i tuoi test prima di scrivere il codice. Questo ti costringerà a pensare all'architettura generale e ti consentirà di scrivere codice con l'API già stabilita.
Se passi molto tempo a scrivere unit test per codici banali come
public string SomeProperty { get; set; }
allora dovresti riesaminare il tuo approccio. Il test unitario dovrebbe testare il comportamento e non esiste alcun comportamento nella riga di codice sopra. Tuttavia, hai creato una dipendenza nel tuo codice da qualche parte, dal momento che quella proprietà verrà quasi sicuramente citata altrove nel tuo codice. Invece di farlo, considera la possibilità di scrivere metodi che accettano la proprietà necessaria come parametro:
public string SomeMethod(string someProperty);
Ora il tuo metodo non ha dipendenze da qualcosa al di fuori di sé, ed è ora più testabile, dal momento che è completamente autonomo. Certo, non sarai sempre in grado di farlo, ma sposta il tuo codice nella direzione di essere più testabile, e questa volta stai scrivendo un test unitario per il comportamento effettivo.