Filosofia sui test unitari sui metodi a catena?

8

Dichiarazione del problema:

Ho una classe che ha qualche convalida e assomiglia a qualcosa del tipo:

func a() {b()}
func b() {c()}
func c() {d()}
func d() {e()}
func e() {return}

Questa è una vista semplificata, ma l'idea è che queste chiamate di funzioni siano dipendenti l'una dall'altra mentre sto facendo alcune trasformazioni e convalide in ciascuna di esse.

Domanda:

Se sto testando func a() dovrei prendere in giro tutte le altre chiamate di funzione all'interno di quel metodo o solo prendere in giro le dipendenze che ho al di fuori di questa classe del tutto? Ad esempio, chiamate di database di simulazione, ecc.

    
posta John Lippson 31.05.2018 - 17:29
fonte

2 risposte

10

Il vero problema qui è che hai semplificato tutto il significato del tuo codice. Tutto ciò che rimane è una rappresentazione strutturale. Vuoi che rispondiamo in base alla struttura in modo che tu abbia una regola basata sulla struttura per seguire ciecamente senza dover pensare al significato, al contesto o alla semantica.

Non che non vorrei sapere una regola del genere se esistesse. So solo che non funziona e ho smesso di guardare molto tempo fa.

Ma se mi stai facendo questa domanda, penso di sapere dove ti trovi, quindi lascia che ti indichi alcune buone indicazioni:

If I am testing func a()

Se tutto ciò che vuoi è testare, puoi testare a() eseguendo qualsiasi codice che lo usi. Onestamente, qualsiasi codice. Puoi farlo a mano. Se in realtà vuoi dire che vuoi creare alcuni test automatici allora ...

should I mock all the other function calls within that method or only mock the dependencies I have outside of this class altogether? For example, mocking database calls, etc.

Velocità

Considera quanto sia tragico mescolare un test che impiega 0,01 secondi per essere eseguito con un test che impiega 3 minuti perché ora impiegano complessivamente 180,01 secondi per l'esecuzione.

Ne parlo perché quando leggi articoli sui test e si vantano che hanno impedito a un test di colpire il database, la lezione non "non ha mai colpito il DB con un test". È "il mio DB era lento, quindi ho capito come farne a meno durante il test di altre cose, puoi farlo anche tu".

Quindi quando mi chiedi cosa fare con a(b(c(d(e())))) la mia risposta è testare TUTTO ma prendi in giro le cose lente e metti quei test veloci in una pila diversa.

Se d() è lento, per qualsiasi motivo, (DB, filesystem, calcolatore di numeri primi, o web cam di una lampada di lava che genera random) quindi mock d() .

Per lento intendo a() b() c() e e() sono incredibilmente veloci per confronto. Tanto che vivrebbero nella tua pila di test "eseguimi ogni volta".

Il punto di derisione d() non è che utilizza un test malvagio che odia DB. È che ti impedisce di testare altre cose rapidamente.

Questi test rapidi sono così utili che molti IDE ti permetteranno di eseguirli senza fare clic su nulla. Eseguono mentre scrivi come il compilatore di IDE quando dà suggerimenti e sottolinei cose a causa di un errore del compilatore. È come se i test rapidi diventassero parte del tuo compilatore.

Anche gli errori del compilatore sono veloci, quindi perché non dovrebbero essere nella pila veloce?

Basta mantenere le prove lente. Non so voi ma odio aspettare mentre sto digitando.

La leggibilità

Sono favorevole a piccoli test isolati. Ma non perché credo che ogni classe e metodo abbia bisogno del suo test. In realtà odio quell'idea. Scoraggia la decomposizione. No, quello che voglio sono piccoli test che limitano ciò che sento di dover leggere per capire a() . È così. I miei limiti di test riguardano esclusivamente la lettura del codice. Non riguardo la sua struttura. Diamine, mi interessa solo la velocità del test perché rallenta la lettura. Si tratta di leggere. Perché? Perché se riesco a leggerlo facilmente so se funziona. Aggiungo test per rendere il codice di lettura più veloce.

Dovresti prendere in giro b() durante il test di a() ? Se sono entrambi veloci, quanto è difficile capire a() con b() che vivono al suo interno? Non modificherei b() a meno che la leggibilità non lo richieda. Ciò significa che puoi testare: a(b(c(dMock(e())))) e farla finita, se sei sicuro che i neofiti potranno seguirlo. Se non riescono a continuare a scomporlo più piccolo finché non possono. Considera sempre i neofiti.

C'è un tipo di considerazione strutturale degna di nota qui, ma è ancora un problema di leggibilità. Le cose pubbliche hanno molti clienti mentre i privati ne hanno pochi. Sono molto più propenso a isolare b() per i test e rendere chiaro l'uso previsto se è pubblico. Sembra ovvio, ma la ragione è questa: se devo controllare ogni bit di codice che lo utilizza, questo è un sacco di codice da leggere. Ma le funzioni private assorbono molta pressione.

Funzioni

Avrai notato che ora ho davvero ignorato il fatto che sono funzioni. Non mi interessa davvero se sono funzioni, oggetti, chiusure o subroutine. Posso riscrivere a() per fare tutto questo senza funzioni. Ciò che conta è la segregazione della velocità e della leggibilità del test. Mi piacerebbe rompere a() se stesso in più funzioni se questo mi porterebbe a.

Tipi di test

Potresti aver notato anche che non ho chiamato nulla di unit test o test di integrazione. Questo perché ora ho letto abbastanza spiegazioni conflittuali su questi nomi che sono stufo di cercare di spiegare cosa intendo davvero quando li uso. C'è un mucchio di test lento e un mucchio di test veloce. Puoi chiamarli Alice e Bob per quello che mi interessa.

Bene, lo riprendo. Qualunque cosa tu faccia, per favore dai loro nomi migliori allora.

    
risposta data 31.05.2018 - 22:50
fonte
2

If I am testing func a() should I mock all the other function calls within that method

Se vuoi testare la funzione a, devi solo prendere in giro b. Ciò significa che la catena di dipendenze di b non è importante per il test di a.

Se non lo facessi, testesti un AND b, c, d, ecc. Il che non è lo stesso che testare un

or only mock the dependencies I have outside of this class altogether? For example, mocking database calls, etc.

Se vuoi testare la classe contenente a, allora devi prendere in giro le dipendenze dall'esterno della classe.

Se non lo fai, stai testando la classe, oltre alle sue dipendenze.

Idealmente, vuoi testare la più piccola 'unità' possibile, direi che di solito è la classe piuttosto che i suoi metodi individuali. Lasciando da parte la programmazione funzionale e assumendo uno stile di programmazione dei servizi OOP o ADM +.

In teoria, se tutte le tue 'unità' sono testate e passate, il tuo programma funzionerà.

In pratica, vorrete anche testare l'intera cosa lavorando insieme.

Se fai solo questi test di "integrazione" e passano tutti, anche il tuo programma funziona.

Uno dei vantaggi dei test unitari è che ti dicono dove si trova il problema.

Il vantaggio dei test di integrazione è che ti dicono se il tuo programma in realtà funziona.

Dovresti fare entrambe le cose.

    
risposta data 01.06.2018 - 01:09
fonte

Leggi altre domande sui tag