2013-04-25 12 views
5

Sto cercando una soluzione/modello per il seguente caso di test unitario.Unit Testing pattern

Il caso:

Facciamo finta che abbiamo tre classi, A, B, C ciascuno con un metodo. Il metodo di A chiama il metodo di B che chiama il metodo di C. Quindi, A-> B-> C. Ogni metodo accetta un input (Input A per il metodo A, input B, input C). L'uscita risultante della chiamata al metodo A, sarebbe una struttura ad albero quali:

Root (creato dal metodo A) - Nodo B (creato dal metodo B) - Nodo C1 - Nodo C2 (entrambi creati con il metodo C)

Per me il test dell'unità consiste nel testare l'uscita dall'input di un metodo in isolamento. Quindi, scriverei unit test per ciascuno dei metodi sopra indicati. Poiché i test sono scritti in modo isolato, simuleremo il metodo B durante la scrittura dei test unitari per il metodo A e prendiamo in giro il metodo C durante la scrittura dei casi di test unitario per il metodo B.

Finora tutto va bene, possiamo scrivere aspettative sull'output di ciascun metodo per assicurarsi che la struttura ad albero risultante sia rispettata.

Il problema:

Vediamo ora aggiungere un altro di classe che chiamare il metodo B in modo da avere anche la seguente catena di chiamate: D-> B-> C. La radice dell'albero risultante sarebbe simile a questa:

  • Root D
    • Nodo B
      • Nodo C1
      • nodo C2

Durante lo sviluppo, qualcuno si rende conto che il requisito per Metodo A è stato frainteso e il risultato albero dovrebbe dovuto essere così:

  • Root Un
    • Nodo B
      • Nodo C

Fortunatamente lo sviluppatore avrebbe cambia il metodo C in modo che l'output restituisca solo un nodo anziché due. Avrebbe cambiato i test unitari in modo che riflettesse tali cambiamenti. Tuttavia, i requisiti del metodo D non dovrebbero essere cambiati e l'output di tale metodo dovrebbe avere ancora il nodo C1 e il nodo C2.

La domanda:

Come avresti scritto il test di unità in modo che il secondo sviluppatore sarebbe stato allertato di rompere il cambiamento avrebbe introdotto per il metodo di D? Preferirei evitare i test di integrazione che sembrerebbero la soluzione migliore qui.

Grazie.

risposta

0

Se si vuole attaccare con test di unità isolate (che io per la maggior parte dei miei oggetti centrali dal momento che trovo quasi impossibile scrivere la giusta quantità di test integrati con la giusta profondità e tenerli veloci), un approccio interessante è il Contract and Collaboration Tests di Joe Rainsberger. Solleva il punto che non si deve prendere in giro una dipendenza per comportarsi in qualche modo in un test a meno che non si abbiano test contrattuali corrispondenti che assicurino che le implementazioni concrete di tale dipendenza si comportino in questo modo nel mondo reale.

Nel tuo esempio, dicendo che "il metodo C ora restituisce solo un nodo anziché due" significa cambiando il contratto di C e le sue prove di contratto di conseguenza. Ciò introduce la possibilità che una C derisa diventi fuori sincrono con il nuovo contratto, quindi dovresti cercare nei tuoi test ovunque C sia deriso e aggiustare i mock se necessario.

che dopo un attento esame delle prove esistenti avrebbe poi si porterà a rendersi conto che non si vuole veramente cambiare il contratto per il metodo C, ma piuttosto introdurre un nuovo metodo C2 che si adatta alle esigenze di A.

Rainsberger descrive il processo che usa per assicurarsi di avere quella reciprocità nei test here. Puoi anche guardare la tecnica in azione nel this video.

1

Idealmente il test di unità dovrebbe penetrare in tutta la strada fino alla classe C. Un test unità non significa necessariamente Un metodo su una classe. Significa semplicemente che è possibile eseguirlo senza bisogno di altre dipendenze come librerie, database ecc. Quindi è necessario un test di integrazione.

Il test dell'unità deve penetrare fino a C, e quando il requisito cambia per A, il test dell'unità si interromperà e lo sviluppatore di D saprà che qualcosa non va.

+0

Unità significa "un'unità logica". In questo caso, il comportamento che stai testando su D o A è definito in parte da C, quindi includilo nella tua unità. – dascandy

2

È necessario disporre di un mix di test unitari "puro¹", test di unità impure, test di integrazione (colpire risorse esterne) e test di stack completo (interfaccia utente abbassata). I test unitari "puri" non sono la fine dei test.

Così si finirà con alcuni dei seguenti modelli di prova per ogni funzione si sviluppano:

  • A Unità di prova - ha colpito un in isolamento - se i livelli più bassi sono complicate o colpire una risorsa esterna.
  • Anche Unit Test (ma a volte anche chiamato un test di integrazione o componente) - Colpisci A al livello (approssimativo) prima di una risorsa esterna (es. Database) - Se i livelli inferiori sono complicati, ma testati o se i livelli inferiori non sono complicati
  • Test di integrazione: premere A e il database.
  • Test a pila intera - Colpisci l'interfaccia utente che a volte chiama U. - Avrei l'intenzione di avere almeno un paio in questo livello (come Cukes o simili) o il livello sotto.

Fintantoché si dispone di alcuni test di livello superiore, essi dovrebbero fallire anche se il test che ha colpito A isolamento no.

¹ Usando solo la frase "unit test puri" come alcune persone lo hanno le loro teste che la definizione uno unit test è il metodo in isolamento

+0

cosa intendi con "puro"? Solo una lezione? –

+0

Aye. Non è quello che definirei un "test unitario", ma alcune persone lo fanno e gli do un nome. –