Quanto dovrebbero essere stretti i miei test unitari?

4

Ecco un esempio:

Ho un modulo di chat nella mia app, e c'è una classe ChatService che è responsabile per il networking, e c'è una classe helper ChatNotificationService che è responsabile per l'invio e ricezione di trasmissioni di messaggi e c'è anche un protocollo ChatNotificiationDelegate che ha dei metodi su di esso che vengono richiamati quando arriva una notifica.

Per testare l'invio di messaggi, in teoria potrei semplicemente chiamare ChatService.sendMessage () e controllare se è stato chiamato il metodo ChatNotificationDelegate.didSentMessage ().

Tuttavia, l'invio di un messaggio chiama diversi metodi prima che arrivi al delegato. Invia il messaggio al server, quindi il server invia una risposta che attiva l'invio della notifica, quindi il servizio di notifica riceve la notifica e chiama il metodo delegato.

L'intera catena può essere coperta da un test di unità, oppure potrei testare separatamente ciascuna funzionalità.

Quello che sto chiedendo è quale approccio dovrei prendere su questo, testare ogni bit sappiamo esattamente cosa è andato storto o per rendere i miei test il più generici possibile (solo gli endpoint) per consentire una maggiore libertà di refactoring del codice.

    
posta Marin Bencevic 25.12.2015 - 12:26
fonte

3 risposte

7

La domanda che dovresti effettivamente porsi è questa: è il mio codice testabile? Se ti ritrovi a scrivere elaborati test o a usare elaborate mock nei tuoi test, è colpa del codice in prova , non i test stessi.

I test unitari testano un'unità di codice. Quindi si tratta di "che cos'è un'unità?" Un'unità, in termini generali, è un piccolo bit di funzionalità chiaramente definita che è prontamente testabile. Di solito, è un singolo metodo . Se scrivi un codice testabile, la tua "larghezza" di test corretta dovrebbe nascere da quella.

In ogni caso, ciò che stai descrivendo nella tua domanda non è un test unitario; è un test di integrazione . I meriti relativi dei test unitari vs test di integrazione sono discussi a lungo altrove, ma basti dire che si scrivono test unitari per assicurarsi che il codice funzioni, ma si scrivono test di integrazione per assicurarti che il tuo codice funzioni insieme .

    
risposta data 25.12.2015 - 13:26
fonte
0

Un approccio che a volte uso è scrivere test sull'interfaccia, ad es.

SendMessageTest(IChatService service)
{
    service.SendMessage(message);
    Assert.IsTrue(message.Sent);
}

Quindi utilizzare i test basati sui dati per chiamare il test con più configurazioni di servizio. Primo con tutti i mock, questo è il tuo test unitario, solo testando il servizio stesso, quindi un servizio In-process, quindi utilizzando i messaggi reali ma tutto istanziato nel processo per il debug. Quindi più versioni di integrazione che utilizzano una versione client del servizio che si connette a test, uat e ambienti live.

ogni caso di test dovrebbe essere etichettato come "Integrazione" o qualsiasi altra cosa in modo da poter scegliere quale eseguire in base a quale scenario.

Quindi per le build CI è possibile eseguire solo i test unitari, per eseguire il debug è possibile eseguire quelli in corso, dopo la distribuzione per test è possibile eseguire quelli di integrazione di test e se si ha un problema strano in diretta è possibile eseguire il live quelli di integrazione.

Il vantaggio di questo è che devi solo scrivere una serie di test, ma sono in grado di eseguirli in vari modi.

PS. È possibile / dovrebbe anche scrivere ulteriori test per logica specifica ecc., ma trovo questo test di "livello superiore" l'uso più pratico. Dopo tutto il più delle volte sai che 'dovrebbe funzionare' stai cercando di scoprire perché non in circostanze X

    
risposta data 25.12.2015 - 12:43
fonte
0

Stai confrontando 2 tipi di test: unit test e test di integrazione. I test unitari dovrebbero essere veloci. A me piacciono i test unitari per eseguire una frazione di secondo. Questo è importante quando si esegue ripetutamente un test durante la scrittura / modifica del codice. Per rendere veloce il test delle unità, il test non può chiamare alcuna risorsa remota o accedere a un database. Sicuramente, non si desidera avviare un server di chat nel test dell'unità.

I test unitari dovrebbero aiutarti anche con il refactoring. È meglio farlo quando il tuo test unitario va solo al confine architettonico più vicino. Si prova che si raggiunge il confine e si passa il messaggio corretto oltre il confine. Puoi anche verificare che il tuo codice gestisca correttamente tutto ciò che può essere restituito da oltre il confine. In questo modo il tuo sistema è liberamente accoppiato e mutevole - e così anche i tuoi test.

Oltre ai test unitari, si verifica anche che il sistema interagisca correttamente oltre i limiti e con altri sistemi. Le persone chiamano questi test di interazione o test di integrazione. Potrebbero richiedere più tempo per l'esecuzione perché in genere eseguono l'accesso remoto e potrebbero anche essere necessario avviare un'istanza di test di un server. Poiché hai già disaccoppiato i tuoi layer in test di unità, non è necessario testare ogni possibile permutazione di input e output nei test di integrazione. Ogni test di integrazione dura più a lungo, ma ne hai meno.

Mettendo tutto insieme, ti suggerisco di creare test di unità "piccoli" per il tuo ChatService, ChatNotificationDelegate e così via, andando solo fino al prossimo servizio. È possibile prendere in giro l'altro servizio per verificare che l'oggetto del test passi i parametri corretti oltre i limiti e reagisca correttamente ai resi. Mirerai a ottenere un'alta copertura dei test unitari. Inoltre, avrai un piccolo numero di test di integrazione che funzionano su un unico limite. O forse anche un solo fine per terminare il test di invio del messaggio, come suggerito.

Quindi la mia risposta a

test every bit we know exactly what went wrong, or to make my tests as general as possible (just the endpoints) to allow more freedom of refactoring the code

è entrambi: molti piccoli test di unità per sapere esattamente cosa è andato storto e un grande test di integrazione per assicurarsi che tutti i pezzi combacino. E vorrei dire che un singolo test end-to-end non "consente la libertà di refactoring". Il contrario è spesso vero. Un test di una scatola nera può dirti che qualcosa non va, ma non hai idea di cosa si sia rotto esattamente. Se il sistema può rompersi misteriosamente, senza un modo semplice per identificare o almeno individuare la causa principale, è più probabile che teme di apportare modifiche significative, come il refactoring.

    
risposta data 28.12.2015 - 05:14
fonte

Leggi altre domande sui tag