Nuovo ai test unitari, come scrivere grandi test? [chiuso]


267

Sono abbastanza nuovo nel mondo dei test unitari e ho appena deciso di aggiungere una copertura di test per la mia app esistente questa settimana.

Questo è un compito enorme, principalmente a causa del numero di classi da testare, ma anche perché scrivere test è tutto nuovo per me.

Ho già scritto dei test per un sacco di lezioni, ma ora mi chiedo se lo sto facendo bene.

Quando scrivo test per un metodo, ho la sensazione di riscrivere una seconda volta ciò che ho già scritto nel metodo stesso.
I miei test sembrano così strettamente legati al metodo (testare tutti i codepati, aspettandomi che alcuni metodi interni vengano chiamati più volte, con alcuni argomenti), che sembra che se dovessi mai refactoring il metodo, i test falliranno anche se il il comportamento finale del metodo non è cambiato.

Questo è solo un sentimento e, come detto prima, non ho esperienza di test. Se alcuni tester più esperti là fuori potessero darmi consigli su come scrivere grandi test per un'app esistente, sarebbe molto apprezzato.

Modifica: Vorrei ringraziare Stack Overflow, ho avuto grandi input in meno di 15 minuti che hanno risposto più delle ore di lettura online che ho appena fatto.


1
Questo è il miglior libro per i test unitari: manning.com/osherove Illustra tutte le migliori pratiche, le cose da fare e non per i test unitari.
Ervi B,

Una cosa che tutte queste risposte tralasciano è che i test unitari sono come documentazione. Ergo, se scrivi una funzione, documenteresti la sua intenzione, descrivendone input e output (e, possibilmente, effetti collaterali). Un test unitario ha lo scopo di verificarlo, quindi. E se tu (o qualcun altro) successivamente apporti modifiche al codice, i documenti dovrebbero spiegare i limiti di quali modifiche possono essere apportate e i test unitari assicurano che i limiti vengano mantenuti.
Thomas Tempelmann,

Risposte:


187

I miei test sembrano così strettamente legati al metodo (testare tutti i codepati, aspettandomi che alcuni metodi interni vengano chiamati un numero di volte, con alcuni argomenti), che sembra che se dovessi mai refactoring il metodo, i test falliranno anche se il il comportamento finale del metodo non è cambiato.

Penso che tu stia sbagliando.

Un test unitario dovrebbe:

  • prova un metodo
  • fornire alcuni argomenti specifici per quel metodo
  • prova che il risultato è come previsto

Non dovrebbe guardare all'interno del metodo per vedere cosa sta facendo, quindi la modifica degli interni non dovrebbe causare il fallimento del test. Non si dovrebbe testare direttamente che vengono chiamati metodi privati. Se sei interessato a scoprire se il tuo codice privato è in fase di test, utilizza uno strumento di copertura del codice. Ma non essere ossessionato da questo: una copertura del 100% non è un requisito.

Se il tuo metodo chiama metodi pubblici in altre classi e queste chiamate sono garantite dalla tua interfaccia, puoi verificare che queste chiamate vengano effettuate utilizzando un framework di derisione.

Non è necessario utilizzare il metodo stesso (o il codice interno utilizzato) per generare dinamicamente il risultato previsto. Il risultato atteso dovrebbe essere codificato nel caso di test in modo che non cambi quando l'implementazione cambia. Ecco un esempio semplificato di cosa dovrebbe fare un unit test:

testAdd()
{
    int x = 5;
    int y = -2;
    int expectedResult = 3;
    Calculator calculator = new Calculator();
    int actualResult = calculator.Add(x, y);
    Assert.AreEqual(expectedResult, actualResult);
}

Si noti che il modo in cui viene calcolato il risultato non viene verificato, ma solo che il risultato è corretto. Continua ad aggiungere casi di test sempre più semplici come quello sopra finché non avrai coperto il maggior numero di scenari possibile. Utilizza il tuo strumento di copertura del codice per vedere se hai perso percorsi interessanti.


13
Grazie mille, la tua risposta è stata più completa. Ora capisco meglio a cosa servono davvero gli oggetti finti: non ho bisogno di asserire ogni chiamata ad altri metodi, solo quelli pertinenti. Inoltre non ho bisogno di sapere come vengono fatte le cose, ma che fanno correttamente.
pixelastic

2
Rispettosamente penso che tu stia sbagliando. I test unitari riguardano il flusso di esecuzione del codice (test del riquadro bianco). Il test black box (cosa stai suggerendo) è di solito la tecnica utilizzata nei test funzionali (test di sistema e integrazione).
Wes

1
"Un test unitario dovrebbe testare un metodo" In realtà non sono d'accordo. Un test unitario dovrebbe testare un concetto logico. Mentre quello è spesso rappresentato come un metodo, non è sempre così
robertmain

35

Per i test unitari, ho trovato sia Test Driven (test prima, codice secondo) che codice prima, test secondo, per essere estremamente utili.

Invece di scrivere codice, quindi scrivere test. Scrivi il codice, quindi guarda cosa pensi che dovrebbe fare il codice. Pensa a tutti gli usi previsti e poi scrivi un test per ciascuno. Trovo che scrivere test sia più veloce ma più coinvolto della codifica stessa. I test dovrebbero testare l'intenzione. Pensando anche alle intenzioni che finisci per trovare casi angolari nella fase di scrittura del test. E ovviamente durante la scrittura dei test potresti trovare uno dei pochi usi che causa un errore (qualcosa che trovo spesso, e sono molto contento che questo errore non abbia corrotto i dati e non sia stato controllato).

Tuttavia, testare è quasi come codificare due volte. In effetti avevo applicazioni in cui c'era più codice di test (quantità) rispetto al codice dell'applicazione. Un esempio era una macchina a stati molto complessa. Ho dovuto assicurarmi che dopo aver aggiunto più logica ad esso, l'intera cosa funzionasse sempre su tutti i casi d'uso precedenti. E poiché quei casi erano piuttosto difficili da seguire guardando il codice, ho finito per avere una suite di test così buona per questa macchina che ero fiducioso che non si sarebbe rotto anche dopo aver apportato modifiche e i test mi hanno salvato il culo alcune volte . E mentre gli utenti o i tester trovavano bug con i casi di flusso o corner non presi in considerazione, indovina un po ', aggiunti ai test e mai più accaduti. Ciò ha davvero dato agli utenti la fiducia nel mio lavoro oltre a rendere il tutto super stabile. E quando doveva essere riscritto per motivi di performance, indovina un po ',

Tutti i semplici esempi come function square(number)sono fantastici e tutti, e probabilmente sono cattivi candidati per passare molto tempo a testare. Quelli che svolgono una logica aziendale importante, è qui che il test è importante. Testare i requisiti. Non limitarti a testare l'impianto idraulico. Se i requisiti cambiano, indovina cosa, anche i test devono.

I test non dovrebbero essere testati letteralmente per quella funzione per la barra delle funzioni invocata 3 volte. Questo è sbagliato. Controlla se il risultato e gli effetti collaterali sono corretti, non la meccanica interna.


2
Bella risposta, mi ha dato la certezza che scrivere test dopo il codice può ancora essere utile e possibile.
pixelastic,

2
Un perfetto esempio recente. Ho avuto una funzione molto semplice. Passalo vero, fa una cosa, falso fa un'altra. MOLTO SEMPLICE. Ho avuto 4 test di controllo per assicurarsi che la funzione fa quello che intende fare. Cambio un po 'il comportamento. Esegui test, POW un problema. La cosa divertente è che quando si utilizza l'applicazione il problema non si manifesta, è solo in un caso complesso che lo fa. Il test case lo ha trovato e mi sono risparmiato ore di mal di testa.
Dmitriy Likhten

"I test dovrebbero testare l'intenzione." Questo, a mio avviso, riassume che dovresti passare attraverso gli usi previsti del codice e assicurarti che il codice possa adattarli. Indica anche l'ambito di ciò che il test dovrebbe effettivamente testare e l'idea che, quando si apporta una modifica del codice, nel momento in cui non si può considerare il modo in cui tale modifica influisce su tutti gli usi prescritti del codice: il test difende da un cambiamento che non soddisfa tutti i casi d'uso previsti.
Greenstick,

18

Vale la pena notare che i test unitari di adattamento retroattivo al codice esistente sono molto più difficili che guidare la creazione di quel codice con test in primo luogo. Questa è una delle grandi domande nel trattare con applicazioni legacy ... come testare le unità? Questo è stato chiesto molte volte prima (così si può essere chiuso come una domanda vittima), e la gente di solito finisce qui:

Spostamento del codice esistente in Test Driven Development

Seguo la raccomandazione del libro della risposta accettata, ma oltre a ciò ci sono più informazioni collegate nelle risposte lì.


3
Se scrivi i test prima o seconda, va bene entrambi, ma quando scrivi i test ti assicuri che il tuo codice sia testabile in modo da poter scrivere i test. Si finisce per pensare "come posso provarlo" spesso che di per sé provoca la scrittura di codice migliore. Il retrofit dei casi di test è sempre un grande no-no. Molto difficile. Non è un problema di tempo, è un problema di quantità e testabilità. Non posso venire dal mio capo in questo momento e dire che voglio scrivere casi di test per i nostri oltre mille tavoli e usi, è troppo ora, mi ci vorrebbe un anno e alcune delle logiche / decisioni vengono dimenticate. Quindi non rimandare troppo a lungo: P
Dmitriy Likhten,

2
Presumibilmente la risposta accettata è cambiata. C'è una risposta da Linx che consiglia The art of unit testing di Roy Osherove, manning.com/osherove
thelem

15

Non scrivere test per ottenere la copertura completa del tuo codice. Scrivi test che garantiscano le tue esigenze. Potresti scoprire codepati che non sono necessari. Al contrario, se sono necessari, sono lì per soddisfare un qualche tipo di requisito; trova quello che è e verifica il requisito (non il percorso).

Mantieni i tuoi test piccoli: un test per requisito.

Successivamente, quando è necessario apportare una modifica (o scrivere un nuovo codice), provare prima a scrivere un test. Solo uno. Quindi avrai fatto il primo passo nello sviluppo guidato dai test.


Grazie, ha senso fare solo piccoli test per piccoli requisiti, uno alla volta. Lezione imparata.
pixelastic,

13

Il test unitario riguarda l'output ottenuto da una funzione / metodo / applicazione. Non importa affatto come viene prodotto il risultato, importa solo che sia corretto. Pertanto, il tuo approccio al conteggio delle chiamate ai metodi interni e cose del genere è sbagliato. Quello che tendo a fare è sedermi e scrivere ciò che un metodo dovrebbe restituire dati determinati valori di input o un determinato ambiente, quindi scrivere un test che confronta il valore effettivo restituito con quello che mi è venuto in mente.


Grazie ! Avevo la sensazione che stavo sbagliando, ma avere qualcuno che mi dicesse davvero è meglio.
pixelastic,

8

Prova a scrivere un Unit Test prima di scrivere il metodo che sta per testare.

Ciò ti costringerà sicuramente a pensare in modo leggermente diverso su come vengono fatte le cose. Non avrai idea di come funzionerà il metodo, proprio quello che dovrebbe fare.

Dovresti sempre testare i risultati del metodo, non come il metodo ottiene quei risultati.


Sì, mi piacerebbe poterlo fare, tranne per il fatto che i metodi sono già stati scritti. Voglio solo provarli. Scriverò test prima dei metodi in futuro, comunque.
pixelastic,

2
@pixelastic finge che i metodi non siano stati scritti?
committedandroider

4

i test dovrebbero migliorare la manutenibilità. Se cambi un metodo e un test si interrompe, può essere una buona cosa. D'altra parte, se guardi il tuo metodo come una scatola nera, non dovrebbe importare cosa c'è dentro il metodo. Il fatto è che devi prendere in giro le cose per alcuni test e in quei casi non puoi davvero considerare il metodo come una scatola nera. L'unica cosa che puoi fare è scrivere un test di integrazione: carichi un'istanza completamente istanziata del servizio in prova e fai che funzioni come se fosse in esecuzione nella tua app. Quindi puoi trattarlo come una scatola nera.

When I'm writing tests for a method, I have the feeling of rewriting a second time what I          
already wrote in the method itself.
My tests just seems so tightly bound to the method (testing all codepath, expecting some    
inner methods to be called a number of times, with certain arguments), that it seems that
if I ever refactor the method, the tests will fail even if the final behavior of the   
method did not change.

Questo perché stai scrivendo i tuoi test dopo aver scritto il tuo codice. Se lo facessi al contrario (hai scritto prima i test) non ti sentiresti così.


Grazie per l'esempio della scatola nera, non l'ho pensato in questo modo. Vorrei aver scoperto unit test in precedenza, ma sfortunatamente non è così e sono bloccato con un'app legacy a cui aggiungere test. Non c'è modo di aggiungere test in un progetto esistente senza che si senta rotto?
pixelastic,

1
Scrivere test dopo è diverso da scrivere test prima, quindi sei bloccato con esso. tuttavia, ciò che puoi fare è impostare i test in modo che falliscano per primi, quindi farli passare mettendo la tua classe sotto test in .... fai qualcosa del genere, mettendo la tua istanza sotto test dopo che inizialmente il test fallisce. Stessa cosa con le beffe: inizialmente la simulazione non ha aspettative e fallirà perché il metodo sotto test farà qualcosa con la simulazione, quindi farà passare il test. Non sarei sorpreso se trovi molti bug in questo modo.
hvgotcodes,

inoltre, sii davvero specifico con le tue aspettative. Non affermare solo che il test restituisce un oggetto, verificare che l'oggetto abbia vari valori su di esso. Prova che quando un valore dovrebbe essere nullo, lo è. Puoi anche spezzarlo un po 'facendo del refactoring che volevi fare, dopo aver aggiunto alcuni test.
hvgotcodes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.