Quando scrivi il codice "reale" in TDD?

146

Tutti gli esempi che ho letto e visto sui video di formazione hanno esempi semplicistici. Ma cosa non vedo se come faccio il codice "reale" dopo che divento verde. È questa la parte "Refactor"?

Se ho un oggetto abbastanza complesso con un metodo complesso, scrivo il mio test e il minimo indispensabile per farlo passare (dopo che fallisce, Red). Quando torno indietro e scrivo il codice reale? E quanto codice reale scrivo prima di ripetere il test? Immagino che l'ultimo sia più intuito.

Modifica: Grazie a tutti coloro che hanno risposto. Tutte le tue risposte mi hanno aiutato immensamente. Sembra che ci siano idee diverse su quello che stavo chiedendo o confuso, e forse c'è, ma quello che stavo chiedendo era, diciamo, che ho una domanda per costruire una scuola.

Nel mio design, ho un'architettura con cui voglio iniziare, User Story, e così via. Da qui, prendo quelle User Story e creo un test per testare User Story. L'utente dice, Abbiamo persone iscriversi per la scuola e pagare le tasse di registrazione. Quindi, penso a un modo per farlo fallire. In tal modo, progetto una classe di test per la classe X (forse Student), che fallirà. Quindi creo la classe "Studente". Forse "Scuola" non lo so.

Ma, in ogni caso, il TD Design mi costringe a pensare attraverso la storia. Se riesco a far fallire un test, so perché fallisce, ma questo presuppone che io possa farlo passare. Riguarda la progettazione.

Paragone questo al pensiero sulla ricorsione. La ricorsione non è un concetto difficile. Potrebbe essere più difficile tenerne traccia nella tua testa, ma in realtà, la parte più difficile è sapere, quando la ricorsione "si interrompe", quando fermarsi (la mia opinione, ovviamente.) Quindi devo pensare a cosa si ferma prima la Ricorsione. È solo un'analogia imperfetta e presuppone che ogni iterazione ricorsiva sia un "passaggio". Di nuovo, solo un parere.

Nell'implementazione, la scuola è più difficile da vedere. I registri numerici e bancari sono "facili" nel senso che puoi usare l'aritmetica semplice. Riesco a vedere a + b e a restituire 0, ecc. Nel caso di un sistema di persone, devo pensare più a fondo su come implementarlo . Ho il concetto di fallire, passare, refactoring (principalmente per lo studio e questa domanda).

Ciò che non so è basato sulla mancanza di esperienza, secondo me. Non so come fallire l'iscrizione a un nuovo studente. Non so come fallire qualcuno che digita un cognome e viene salvato in un database. So come fare un + 1 per la matematica semplice, ma con entità come una persona, non so se sto solo testando per vedere se ottengo un ID univoco del database o qualcos'altro quando qualcuno inserisce un nome in un database o entrambi o nessuno dei due.

O forse questo dimostra che sono ancora confuso.

    
posta johnny 24.07.2017 - 23:55
fonte

11 risposte

241

If I have a fairly complex object with a complex method, and I write my test and the bare minimum to make it pass (after it first fails, Red). When do I go back and write the real code? And how much real code do I write before I retest? I'm guessing that last one is more intuition.

Non "vai indietro" e scrivi "codice reale". È tutto un codice reale. Quello che fai è tornare indietro e aggiungere un altro test che ti costringe a cambiare il tuo codice per fare in modo che il nuovo test passi.

Per quanto riguarda la quantità di codice che scrivi prima di ripetere il test? Nessuna. Scrivi un codice zero senza un test fallito che ti costringe a scrivere altro codice.

Si noti il modello?

Esaminiamo (un altro) semplice esempio nella speranza che sia d'aiuto.

Assert.Equal("1", FizzBuzz(1));

Semplicemente pacchiano.

public String FizzBuzz(int n) {
    return 1.ToString();
}

Non quello che chiameresti codice reale, giusto? Aggiungiamo un test che costringe un cambiamento.

Assert.Equal("2", FizzBuzz(2));

Potremmo fare qualcosa di stupido come if n == 1 , ma salteremo alla soluzione sana.

public String FizzBuzz(int n) {
    return n.ToString();
}

Cool. Funzionerà per tutti i numeri non FizzBuzz. Qual è il prossimo input che costringerà il codice di produzione a cambiare?

Assert.Equal("Fizz", FizzBuzz(3));

public String FizzBuzz(int n) {
    if (n == 3)
        return "Fizz";
    return n.ToString();
}

E di nuovo. Scrivi un test che non passerà ancora.

Assert.Equal("Fizz", FizzBuzz(6));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    return n.ToString();
}

E ora abbiamo coperto tutti i multipli di tre (che non sono anche multipli di cinque, lo annunceremo e torneremo).

Non abbiamo ancora scritto un test per "Buzz", quindi scriviamolo.

Assert.Equal("Buzz", FizzBuzz(5));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    if (n == 5)
        return "Buzz"
    return n.ToString();
}

E ancora, sappiamo che c'è un altro caso che dobbiamo gestire.

Assert.Equal("Buzz", FizzBuzz(10));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    if (n % 5 == 0)
        return "Buzz"
    return n.ToString();
}

E ora possiamo gestire tutti i multipli di 5 che non sono anche multipli di 3.

Fino a questo punto, abbiamo ignorato la fase di refactoring, ma vedo alcune duplicazioni. Puliamolo adesso.

private bool isDivisibleBy(int divisor, int input) {
    return (input % divisor == 0);
}

public String FizzBuzz(int n) {
    if (isDivisibleBy(3, n))
        return "Fizz";
    if (isDivisibleBy(5, n))
        return "Buzz"
    return n.ToString();
}

Cool. Ora abbiamo rimosso la duplicazione e creato una funzione ben denominata. Qual è il prossimo test che possiamo scrivere che ci costringerà a cambiare il codice? Bene, abbiamo evitato il caso in cui il numero è divisibile sia con 3 che con 5. Scriviamolo ora.

Assert.Equal("FizzBuzz", FizzBuzz(15));

public String FizzBuzz(int n) {
    if (isDivisibleBy(3, n) && isDivisibleBy(5, n))
        return "FizzBuzz";
    if (isDivisibleBy(3, n))
        return "Fizz";
    if (isDivisibleBy(5, n))
        return "Buzz"
    return n.ToString();
}

I test passano, ma abbiamo più duplicati. Abbiamo delle opzioni, ma ho intenzione di applicare "Estrai la variabile locale" alcune volte in modo da eseguire il refactoring anziché riscrivere.

public String FizzBuzz(int n) {

    var isDivisibleBy3 = isDivisibleBy(3, n);
    var isDivisibleBy5 = isDivisibleBy(5, n);

    if ( isDivisibleBy3 && isDivisibleBy5 )
        return "FizzBuzz";
    if ( isDivisibleBy3 )
        return "Fizz";
    if ( isDivisibleBy5 )
        return "Buzz"
    return n.ToString();
}

E abbiamo coperto ogni input ragionevole, ma per quanto riguarda l'input irragionevole ? Cosa succede se passiamo 0 o un negativo? Scrivi quei casi di test.

public String FizzBuzz(int n) {

    if (n < 1)
        throw new InvalidArgException("n must be >= 1);

    var isDivisibleBy3 = isDivisibleBy(3, n);
    var isDivisibleBy5 = isDivisibleBy(5, n);

    if ( isDivisibleBy3 && isDivisibleBy5 )
        return "FizzBuzz";
    if ( isDivisibleBy3 )
        return "Fizz";
    if ( isDivisibleBy5 )
        return "Buzz"
    return n.ToString();
}

Comincia a sembrare ancora "codice reale"? Ancora più importante, a che punto ha smesso di essere "codice irreale" e transizione verso l'essere "reale"? È qualcosa su cui riflettere ...

Quindi, sono stato in grado di farlo semplicemente cercando un test che sapevo non sarebbe passato ad ogni passaggio, ma ho avuto molta pratica. Quando sono al lavoro, le cose non sono mai così semplici e potrei non sapere sempre quale test forzerà un cambiamento. A volte scrivo un test e sono sorpreso di vederlo già passato! Consiglio vivamente di prendere l'abitudine di creare una "Lista dei test" prima di iniziare. Questo elenco di test dovrebbe contenere tutti gli input "interessanti" che puoi immaginare. Potresti non usarli tutti e probabilmente aggiungerai dei casi man mano che andrai, ma questa lista serve da guida di marcia. La mia lista di test per FizzBuzz sarebbe simile a questa.

  • Negativo
  • Zero
  • Un
  • due
  • tre
  • Quattro
  • Cinque
  • Sei (multiplo non banale di 3)
  • Nove (3 al quadrato)
  • Dieci (non banale multiplo di 5)
  • 15 (multipli di 3 e 5)
  • 30 (multiplo non banale di 3 e 5)
risposta data 25.07.2017 - 04:22
fonte
46

Il codice "reale" è il codice che scrivi per far passare il test. davvero . È così semplice.

Quando le persone parlano di scrivere il minimo indispensabile per rendere il test verde, ciò significa semplicemente che il tuo codice reale dovrebbe seguire il YAGNI principio .

L'idea della fase di refactoring è solo quella di ripulire ciò che hai scritto una volta che sei soddisfatto del fatto che soddisfi i requisiti.

Finché i test che scrivi in realtà comprendono i requisiti del tuo prodotto, una volta che passano, il codice è completo. Pensaci, se tutte le tue esigenze aziendali hanno un test e tutti questi test sono ecologici, che altro c'è da scrivere? (Va bene, nella vita reale non tendiamo ad avere una copertura completa del test, ma la teoria è valida.)

    
risposta data 25.07.2017 - 00:21
fonte
14

La risposta breve è che il "codice reale" è il codice che fa passare il test. Se riesci a far passare il test con qualcosa di diverso dal codice reale, aggiungi altri test!

Sono d'accordo sul fatto che molti tutorial su TDD sono semplicistici. Questo funziona contro di loro. Un test troppo semplice per un metodo che, per esempio, calcola 3 + 8 non ha davvero altra scelta che calcolare anche 3 + 8 e confrontare il risultato. Questo fa sembrare che si stia duplicando il codice dappertutto, e che il test sia inutile, un lavoro supplementare soggetto a errori.

Quando sei bravo a provare, questo ti informerà su come strutturi la tua applicazione e su come scrivi il tuo codice. Se hai difficoltà a trovare test utili e sensati, dovresti probabilmente ripensare il tuo design un po '. Un sistema ben progettato è facile da testare, il che significa che i test sensibili sono facili da pensare e da implementare.

Quando scrivi i test per primi, guardali fallire e poi scrivi il codice che li fa passare, è una disciplina per garantire che tutto il tuo codice abbia test corrispondenti. Non seguo pedissequamente questa regola quando sto codificando; spesso scrivo prove dopo il fatto. Ma fare i primi test aiuta a mantenere la tua onestà. Con una certa esperienza, inizierai a notare quando ti stai codificando in un angolo, anche quando non stai scrivendo i test.

    
risposta data 25.07.2017 - 00:21
fonte
6

A volte alcuni esempi di TDD possono essere fuorvianti. Come altre persone hanno sottolineato prima, il codice che scrivi per far passare i test è il codice reale.

Ma non pensare che il codice reale appaia come una magia: è sbagliato. Hai bisogno di una migliore comprensione di ciò che vuoi ottenere e quindi devi scegliere il test di conseguenza, partendo dai casi più semplici e dai casi angusti.

Ad esempio, se hai bisogno di scrivere un lexer, inizi con una stringa vuota, poi con un gruppo di spazi bianchi, poi un numero, poi con un numero circondato da spazi bianchi, poi un numero sbagliato, ecc. Queste piccole trasformazioni ti porta all'algoritmo giusto, ma non passa dal caso più semplice a un caso altamente complesso scelto stupidamente per ottenere il codice reale.

Bob Martin lo spiega perfettamente qui .

    
risposta data 25.07.2017 - 09:24
fonte
5

La parte del refattore è pulita quando sei stanco e vuoi andare a casa.

Quando stai per aggiungere una feature, la parte refactor è ciò che cambi prima del prossimo test. Si rifatta il codice per fare spazio alla nuova funzione. Lo fai quando conosci quale sarà la nuova funzione. Non quando lo stai solo immaginando.

Può essere semplice come rinominare GreetImpl in GreetWorld prima di creare una classe GreetMom (dopo aver aggiunto un test) per aggiungere una funzione che stamperà "Ciao mamma".

    
risposta data 25.07.2017 - 03:04
fonte
1

Ma il codice reale apparirebbe nella fase di refactoring della fase TDD. Cioè il codice che dovrebbe far parte della versione finale.

I test dovrebbero essere eseguiti ogni volta che apporti una modifica.

Il motto del ciclo di vita TDD sarebbe: REFATTORE VERDE ROSSO

RED : scrivi i test

VERDE : effettua un tentativo onesto di ottenere codice funzionale che superi i test il più rapidamente possibile: codice duplicato, variabili con nome oscuro come hack di altissimo livello, ecc.

REFATTORE : ripulisci il codice, dai un nome appropriato alle variabili. ASCIUGA sul codice.

    
risposta data 25.07.2017 - 06:51
fonte
1

When do you write the “real” code in TDD?

La fase rossa è dove scrivi codice.

Nella fase l'obiettivo principale è quello di eliminare .

Nella fase rosso fai qualsiasi cosa per fare in modo che il test passi il più rapidamente possibile e ad ogni costo . Ignorerai completamente ciò che hai sentito di buone pratiche di codifica o di schemi di progettazione simili. Fare il test in verde è tutto ciò che conta.

Nella fase refactoring ripulisci il casino che hai appena fatto. A questo punto, per prima cosa, guarda se il cambiamento appena apportato è il tipo più in alto nella lista Priorità di trasformazione e se c'è qualsiasi duplicazione di codice che puoi rimuovere molto probabilmente applicando un patter di design.

Infine migliora la leggibilità rinominando gli identificatori ed estrai numeri magici e / o stringhe letterali alle costanti.

It's not red-refactor, it's red-green-refactor. – Rob Kinyon

Grazie per aver indicato questo.

Quindi è la fase verde in cui scrivi codice reale

Nella fase rosso scrivi la specifica eseguibile ...

    
risposta data 26.07.2017 - 09:52
fonte
1

Stai scrivendo Codice reale per tutto il tempo.

In ogni fase Stai scrivendo il codice per soddisfare le condizioni che il tuo codice soddisferà per i futuri chiamanti del tuo codice (che potresti essere tu o no ...).

Pensi di non scrivere codice utile ( vero ), perché in un momento potresti rifattorici.

Code-Refactoring is the process of restructuring existing computer code—changing the factoring—without changing its external behavior.

Ciò significa che anche se si sta modificando il codice, le condizioni che il codice soddisfa, rimangono invariate. E i controlli ( test ) che hai implementato per verificare il tuo codice sono già lì per verificare se le tue modifiche sono cambiate. Quindi il codice che hai scritto per tutto il tempo è lì, solo in un modo diverso.

Un altro motivo Si potrebbe pensare che non è un codice reale, è che stai facendo esempi in cui il programma finale può già essere previsto da te. Questo è molto buono, poiché mostra che hai conoscenza del dominio in cui stai programmando.
Ma molte volte i programmatori si trovano in un dominio che è nuovo , sconosciuto a loro. Non sanno quale sarà il risultato finale e TDD è una tecnica per scrivere i programmi passo dopo passo, documentando la nostra conoscenza su come questo sistema dovrebbe funzionare e verificando che il nostro il codice funziona così.

Quando ho letto The Book (*) su TDD, per me la caratteristica più importante che spicca è stata la seguente: TODO list. Mi ha dimostrato che, TDD è anche una tecnica per aiutare gli sviluppatori a concentrarsi su una cosa alla volta. Quindi questa è anche una risposta alla tua domanda su Quanto codice reale scrivere ? Direi abbastanza codice per concentrarsi su 1 cosa alla volta.

(*) "Sviluppo basato su test: per esempio" di Kent Beck

    
risposta data 27.07.2017 - 22:43
fonte
1

Non stai scrivendo codice per far fallire i test.

Scrivi i tuoi test per definire come dovrebbe essere il successo, il che dovrebbe inizialmente fallire perché non hai ancora scritto il codice che passerà.

L'intero argomento riguardante la scrittura di test inizialmente non validi consiste nel fare due cose:

  1. Copri tutti i casi - tutti i casi nominali, tutti i casi limite, ecc.
  2. Convalida i tuoi test. Se le vedi sempre passare, come puoi essere sicuro che segnaleranno in modo affidabile un errore quando si verifica?

Il punto dietro il refattore rosso-verde è che scrivere i test corretti ti dà la sicurezza di sapere che il codice che hai scritto per superare i test è corretto e ti permette di rifattarti con la sicurezza che i tuoi test ti informeranno non appena qualcosa si rompe, quindi puoi immediatamente tornare indietro e correggerlo.

Nella mia esperienza (C # /. NET), test-first puro è un po 'un ideale irraggiungibile, perché non è possibile compilare una chiamata a un metodo che non esiste ancora. Quindi, "testare prima" riguarda in realtà la codifica delle interfacce e delle implementazioni di stubing, quindi la scrittura di test sugli stub (che inizialmente falliscono) fino a quando gli stub non sono stati sviluppati correttamente. Non scrivo mai "codice fallito", semplicemente costruendo da stub.

    
risposta data 31.07.2017 - 05:10
fonte
0

Penso che potresti essere confuso tra test unitari e test di integrazione. Credo che ci possano essere anche test di accettazione, ma questo dipende dal tuo processo.

Dopo aver provato tutte le piccole "unità", le metti alla prova tutte assemblate o "integrate". Di solito è un intero programma o libreria.

Nel codice che ho scritto l'integrazione testa una libreria con vari programmi di test che leggono i dati e li inseriscono nella libreria, quindi controllano i risultati. Allora lo faccio con i thread. Poi lo faccio con thread e fork () nel mezzo. Quindi lo eseguo e uccido -9 dopo 2 secondi, quindi lo avvio e ne controllo la modalità di ripristino. Mi sono confuso. Lo torturo in tutti i modi.

Tutto ciò è ANCHE test, ma non ho un display rosso / verde piuttosto per i risultati. Succede, o scavare alcune migliaia di righe di codice di errore per scoprire perché.

È qui che si verifica il "codice reale".

E ho pensato a questo, ma forse non sai quando si suppone che tu abbia finito di scrivere dei test unitari. Hai finito di scrivere dei test unitari quando i tuoi test esercitano tutto ciò che hai specificato che dovrebbe fare. A volte puoi perdere la cognizione di questo tra tutti i casi di gestione degli errori e di margini, quindi potresti voler creare un bel gruppo di test di test di percorso che passino direttamente alle specifiche.

    
risposta data 27.07.2017 - 07:55
fonte
-6

In risposta al titolo della domanda: "Quando scrivi il codice" reale "in TDD?", la risposta è: "quasi mai" o "molto lentamente".

Sembri uno studente, quindi risponderò come se lo consigli a uno studente.

Imparerai molte codifiche di "teorie" e "tecniche". Sono perfetti per passare il tempo su corsi studenteschi troppo cari, ma con pochissimi benefici per te che non puoi leggere in un libro in metà tempo.

Il compito di un programmatore è esclusivamente quello di produrre codice. Codice che funziona davvero bene. Questo è il motivo per cui tu, il codificatore pianifica il codice nella tua mente, sulla carta, in un'applicazione adatta, ecc. E prevedi di aggirare eventuali difetti / buchi in anticipo pensando logicamente e lateralmente prima della codifica.

Ma devi sapere come rompere la tua domanda per essere in grado di progettare un codice decente. Ad esempio, se non sapessi su Little Bobby Table (xkcd 327), allora tu probabilmente non starebbe disinfettando i tuoi input prima di lavorare con il database, quindi non saresti in grado di proteggere i tuoi dati attorno a quel concetto.

TDD è solo un flusso di lavoro progettato per minimizzare i bug nel codice creando test di ciò che potrebbe andare storto prima di codificare la tua applicazione perché la codifica può diventare esponenzialmente difficile più codice introduci e dimentichi bug che una volta hai pensato di . Una volta che pensi di aver terminato la tua applicazione, esegui i test e fai boom, si spera che i bug vengano catturati dai test.

TDD non è - come alcuni credono - scrivere un test, farlo passare con un codice minimo, scrivere un altro test, ottenere quel passaggio con un codice minimo, ecc. Invece, è un modo per aiutarti a codificare con sicurezza. Questo ideale di codice di refactoring continuo per farlo funzionare con i test è idiota, ma è un concetto piacevole tra gli studenti perché li fa sentire bene quando aggiungono una nuova funzione e stanno ancora imparando come codificare ...

Per favore non cadere in questa trappola e vedere il tuo ruolo di codifica per quello che è - il compito di un programmatore è solo quello di produrre codice. Codice che funziona davvero bene. Ora, ricorda che sarai l'orologio come programmatore professionista e al tuo cliente non interesserà se hai scritto 100.000 asserzioni, o 0. Vogliono solo il codice che funzioni. Davvero bene, infatti.

    
risposta data 25.07.2017 - 22:33
fonte

Leggi altre domande sui tag