unit-testing - software - unit test vs integration test




Il valore dei test unitari di alto livello e degli oggetti finti (4)

Sto iniziando a credere che un'unità che collauda codice di alto livello e ben scritto, che richiede un uso estensivo di oggetti finti, abbia poco o nessun valore. Mi chiedo se questa asserzione sia corretta o manco qualcosa?

Cosa intendo per alto livello? Queste sono le classi e le funzioni vicino alla parte superiore della catena alimentare. Il loro input e output tende ad essere l'input dell'utente e l'interfaccia utente. La maggior parte del loro lavoro consiste nel prendere input da parte dell'utente e fare una serie di chiamate a entità di livello inferiore. Spesso hanno valori di ritorno significativi o nulli.

Cosa intendo per scritto bene? In questo caso, mi riferisco al codice che è disaccoppiato dalle sue dipendenze (usando le interfacce e l'iniezione di dipendenza), e linea per linea è a un livello coerente di astrazione. Non ci sono algoritmi complicati e pochi condizionali.

Odio scrivere test di unità per questo tipo di codice. I test unitari consistono quasi interamente in mock object setup. Linea per linea, il test unitario viene letto quasi come un'immagine speculare dell'implementazione. In effetti, scrivo i test unitari osservando l'implementazione. "Per prima cosa asserisco che questo metodo falso è stato chiamato, quindi asserisco che questo metodo falso è chiamato ...", ecc . Dovrei testare il comportamento del metodo, non che stia chiamando la giusta sequenza di metodi . Un'altra cosa: ho scoperto che questi test sono estremamente fragili per il refactoring . Se un test è così fragile, si rompe completamente e deve essere riscritto quando il codice in prova viene refactored, quindi non è stato perso uno dei principali vantaggi del test delle unità?

Non voglio che questo post sia contrassegnato come argomentativo, o non una domanda. Quindi dirò direttamente la mia domanda: qual è il modo corretto di testare il tipo di codice che ho descritto, o è inteso che non tutto ha bisogno di un test unitario ?


Una parte

Solo per toccare la tua affermazione audace:

"Dovrei testare il comportamento del metodo, non che stia chiamando la giusta sequenza di metodi"

Il comportamento dell'oggetto-sotto-test è la sequenza di azioni che prende. Questo è in realtà un test "comportamentale", mentre quando si dice "comportamento del metodo", penso che si intende test di stato, come in, dargli un input e verificare l'output corretto.

Faccio questa distinzione perché alcuni puristi del BDD arrivano al punto di argomentare che è molto più significativo testare ciò che la vostra classe dovrebbe richiedere, piuttosto che cosa siano gli input e gli output, perché se si conosce appieno il comportamento del proprio sistema, allora i tuoi input e output saranno corretti.

Una risposta

A parte questo, personalmente non scrivo mai test completi per il livello dell'interfaccia utente. Se stai utilizzando un pattern MVVM, MVP o MVC per la tua applicazione, quindi a livello di "1-team di sviluppatori", è per me un modo per intorpidire e controproducente. Riesco a vedere i bug nell'interfaccia utente e sì, i comportamenti di derisione a questo livello tendono a essere fragili. Sono molto più interessato ad assicurarmi che il mio dominio sottostante e i layer DAL stiano funzionando correttamente.

Ciò che è di valore al livello più alto è un test di integrazione. Hai un'app Web? Invece di affermare che i metodi del controller restituiscono un ActionResult (test di scarso valore), scrivi un test di integrazione che richiede tutte le pagine nella tua app e verifica che non ci siano 404 o 403. Eseguilo una volta per ogni distribuzione.

Risposta finale

Seguo sempre la regola 80/20 con il test delle unità . Per ottenere che l'ultima copertura del 20% dell'alto livello di cui parli, sarà l'80% del tuo impegno. Per il mio personale e la maggior parte dei miei progetti di lavoro, questo non paga.

In breve, sono d'accordo. Scriverò test di integrazione e ignorerò i test unitari per il codice che descrivi.


In generale, ritengo che testare questo tipo di metodo / comando sia maturo per il livello di test di integrazione. Nello specifico, io "unit test" per comandi di basso livello, che (generalmente) non hanno effetti collaterali. Se voglio davvero testare qualcosa che non si adatta a quel modello, la prima cosa che faccio è vedere se riesco a rifattorizzare / ridisegnare per adattarlo.

Al livello di test più elevato, di integrazione (e / o di sistema), arrivo al test di cose che hanno effetti collaterali. Cerco di deridere il meno possibile (forse solo risorse esterne) a questo punto. Un esempio potrebbe essere il mocking del livello del database per:

  1. Registra come è stato chiamato per ottenere i dati
  2. Restituisci dati in scatola Registra come è stato
  3. Registrare come è stato chiamato per inserire dati manipolati

Penso che sia altamente dipendente dall'ambiente. Se siete in team relativamente piccoli e potete mantenere l'integrità del test, le parti più complesse della vostra applicazione dovrebbero avere test unitari. La mia esperienza è che mantenere l'integrità del test su grandi team è piuttosto difficile, poiché i test sono inizialmente ok finché non si rompono inevitabilmente ... a quel punto sono a) "corretti" in un modo che nega completamente la loro utilità, oppure b ) prontamente commentato.

Il punto principale dei test Mock sembra essere tale che i manager possano affermare che la metrica di copertura del codice è a Foo% .... quindi tutto deve funzionare! L'unico caso eccezionale in cui sono probabilmente utili è quando è necessario testare una classe che è un enorme problema ricreare autenticamente (testare una classe di azione in Struts, ad esempio).

Sono un grande sostenitore nello scrivere test grezzi. Codice reale, con oggetti reali. Il codice all'interno dei metodi cambierà nel tempo, ma lo scopo e quindi il comportamento generale di solito no.


Se fai TDD non dovresti scrivere test dopo l'implementazione ma piuttosto il contrario. In questo modo eviterete anche il problema di rendere un test conforme al codice scritto. Probabilmente dovrai testare determinate chiamate di metodo all'interno di quelle unità, ma non la loro sequenza (se non è imperativa per il problema del dominio - processo di business).

E a volte è perfettamente fattibile non scrivere un test per un determinato metodo.





mocking