Perché TDD non funziona qui?

2

Voglio scrivere una classe A che ha un metodo calculate(<params>) . Quel metodo dovrebbe calcolare un valore usando i dati del database. Così ho scritto una classe Test_A per il test delle unità (TDD). L'accesso al database è fatto usando un'altra classe che ho deriso con una classe, chiamiamola Accessor_Mockup .

Ora, il ciclo TDD mi richiede di aggiungere un test che non riesce e di apportare le modifiche più semplici a A in modo che il test passi. Quindi aggiungo i dati a Accessor_Mockup e chiamo A.calculate con i parametri appropriati.

Ma perché A dovrebbe usare la classe accessor? Sarebbe più semplice (!) se la classe semplicemente "conosce" i valori che potrebbe recuperare dal database. Per ogni test che scrivo potrei introdurre un nuovo valore (o un if-branch o qualsiasi altra cosa).

Ma aspetta ... TDD è di più. C'è la parte del refactoring. Potrei refactoring di classe A utilizzando l'accessor di database. Ma potrei rifattenerlo in qualsiasi altro modo (ad esempio introducendo nuove classi che incapsulano i dati). Quindi, ancora una volta: Come faccio a scrivere test che costringono A a usare il database (e nient'altro)?

    
posta TobiMcNamobi 03.07.2013 - 11:46
fonte

5 risposte

11

Se hai appena scritto calculate () senza richiederlo per usare il database, dovrebbe avere i suoi dati da qualche altra parte. Lo so, suona banalmente semplice e noioso, ma per favore sopporta me. Quindi scrivilo. Diciamo che "da qualche altra parte" è naturalmente un nuovo parametro o due. Ora puoi testare la gioia del tuo cuore senza usare un vero database o prenderne in giro uno. E ora hai un'unità pulita, indipendente, ben disaccoppiata. Questo è un buon design. Ed è il design basato sui test. È possibile chiamare il nuovo metodo da una classe che ha accesso al database e alimentarlo dal database. Ma testare quell'interazione sarà meno arduo, perché già ti fidi - anzi sai - che l'unità calculate () funzioni correttamente. Quindi tutti i test che devi eseguire con il tuo calcolo associato al database sono che alimenta i giusti valori con il tuo metodo calculate () ben testato.

Questa è separazione delle preoccupazioni e questo è un buon design. Hai iniziato con un'ipotesi sulla tua progettazione: che calculate () avrebbe comportato sia la preoccupazione dell'accesso al database che la preoccupazione del calcolo. Questa è un'ipotesi un po 'fangosa, un design un po' debole. E la difficoltà che hai nei test sotto quell'assunzione rivela un design migliore. Questo è TDD.

    
risposta data 03.07.2013 - 17:47
fonte
9

Ma perché A dovrebbe usare la classe accessor?

Bene, perché dovresti usare il database? Perché il database può cambiare.

Verifica che calculate(A) restituisca il valore previsto, quindi modifica l'accessore e verifica che calculate(A) restituisca il nuovo valore previsto.

È test design guidato . Sai cosa vuoi (un database), quindi aggiungi test che guidano la tua codifica nella direzione di quell'ideale.

E per l'altra parte, dove si aggiungono sempre più if-statement per coprire tutti i parametri testati. Sì, questo è quello che dovresti fare: fino a quando non pensi che aggiungere un'altra if-statement sia più complicato di quello che effettivamente stai facendo (meno quantità) di lavoro effettivamente necessario.

Direi che un tipico programmatore dovrebbe raggiungere questo limite prima di scrivere quattro istruzioni if.

L'idea di base con "il più semplice" è di non rimanere mai bloccati su un singolo test di unità: se si conosce il risultato finale desiderato, restituirlo; pensa a un altro risultato che desideri e aggiungi quel test. Ripetere. Prima o poi capirai un modo più efficiente / semplice per farlo.

Modifica: come scrivo i test che costringono A a utilizzare il database (e nient'altro)?

La risposta irriverente è, non puoi. Dal punto di vista del test, il metodo è una scatola nera: gli dai l'input e verifica l'output; se ottiene quell'output interrogando il database o scrying non è qualcosa di cui il test è interessato.

In pratica però: il database, o mock-database è solo un altro input. Variare l'input e verificare l'output; ripeti fino a quando non riesci a pensare a test più falliti. Non dovrebbe passare molto tempo prima che la cosa più semplice che puoi fare è in realtà interrogare il (mock-) database.

    
risposta data 03.07.2013 - 12:26
fonte
2

Non dovresti scrivere test che Force class A per usare il database. Quello che puoi scrivere sono test che si aspettano che la classe chiami la classe Accessor. Come la classe Accessor ottiene i suoi dati è all'altezza, la classe A si aspetta solo qualcosa di nuovo.

Mi sembra che tu abbia affrontato lo stesso problema che ho avuto quando ho iniziato a usare TDD, pensando lungo tutta la catena. Scrivi test unitari per ciascun link della catena. Spero che questa analogia abbia un senso.

    
risposta data 03.07.2013 - 16:13
fonte
1

But why should A use the accessor class at all? It would be simpler (!) if the class just "knows" the values it could retrieve from the database. For every test I write I could introduce such a new value (or an if-branch or whatever).

TDD non funzionerà se il tuo obiettivo è ingannare i test quando scrivi il codice produttivo. Una cascata se per tutti i parametri utilizzati nel test non è un'implementazione ragionevole: i test devono solo garantire che qualsiasi implementazione semplice e non dannosa sia corretta.

Quindi, nell'esempio del database, assicurati di inserire abbastanza dati tramite lo stub accessor in modo che qualsiasi programmatore (pigro) implementerà una soluzione che utilizza tali dati, invece di compilare le risposte.

    
risposta data 04.07.2013 - 12:49
fonte
0

TDD non riguarda l'applicazione di un'implementazione specifica, un algoritmo specifico o uno specifico design interno sulle tue funzioni.

Applica solo una determinata firma e uno specifico comportamento di input / output delle tue funzioni ad alcuni casi di test. Ti guiderà anche a disaccoppiare le tue funzioni da risorse / dipendenze esterne. Niente di meno, non di più. Il modo in cui costruisci le tue cose internamente dipende ancora da te. E se si intende implementare una funzione in modo speci fi co in modo da soddisfare tutti i test unitari, ma immediatamente si interrompe in produzione perché non può funzionare insieme con un database, quindi TDD non lo impedisce.

Per implementare un programma reale, pronto per la produzione, dovrai preoccuparti di utilizzare strumenti diversi come

  • risoluzione dei problemi
  • progettazione algoritmo
  • prendere decisioni architettoniche
  • prendere decisioni su un'implementazione specifica
  • test di integrazione
  • test di accettazione

Queste sono cose con cui TDD non ti può davvero aiutare.

    
risposta data 03.07.2013 - 18:21
fonte

Leggi altre domande sui tag