Che cosa fare quando i test TDD rivelano nuove funzionalità necessarie che richiedono anche test?

12

Che cosa fai quando stai scrivendo un test e arrivi al punto in cui devi passare il test e ti rendi conto che hai bisogno di un ulteriore elemento di funzionalità che dovrebbe essere separato nella sua funzione? Anche questa nuova funzione deve essere testata, ma il ciclo TDD dice di fare fallire un test, farlo passare poi refactoring. Se sono sul punto in cui sto cercando di fare passare il test, non dovrei andare a dare il via a un altro test in errore per testare la nuova funzionalità che devo implementare.

Ad esempio, sto scrivendo una classe di punti che ha una funzione WillCollideWith ( LineSegment ) :

public class Point {
    // Point data and constructor ...

    public bool CollidesWithLine(LineSegment lineSegment) {
        Vector PointEndOfMovement = new Vector(Position.X + Velocity.X,
                                               Position.Y + Velocity.Y);
        LineSegment pointPath = new LineSegment(Position, PointEndOfMovement);
        if (lineSegment.Intersects(pointPath)) return true;
        return false;
    }
}

Stavo scrivendo un test per CollidesWithLine quando ho capito che avrei bisogno di una funzione LineSegment.Intersects ( LineSegment ) . Ma dovrei semplicemente interrompere ciò che sto facendo nel mio ciclo di test per creare questa nuova funzionalità? Questo sembra infrangere il principio "Red, Green, Refactor".

Devo solo scrivere il codice che rileva che lineSegments Intersect si trova all'interno della funzione CollidesWithLine e rifattalo dopo che ha funzionato? Funzionerebbe in questo caso poiché posso accedere ai dati da LineSegment , ma nei casi in cui quel tipo di dati è privato?

    
posta Joshua Harris 09.11.2012 - 18:39
fonte

4 risposte

13

Basta commentare il tuo test e il codice recente (o metterlo in uno stash) in modo da aver effettivamente riportato indietro l'orologio all'inizio del ciclo. Quindi inizia con LineSegment.Intersects(LineSegment) test / code / refactor. Al termine, rimuovi il tuo test / codice precedente (o estrai dalla scorta) e mantieni il ciclo.

    
risposta data 09.11.2012 - 19:01
fonte
6

Nel ciclo TDD:

Nella fase "make the test pass", dovresti scrivere l'implementazione più semplice che farà passare il test . Per far passare il test, hai deciso di creare un nuovo collaboratore per gestire la logica mancante, perché forse era troppo lavoro per inserire la tua point-class per far passare il test. Ecco dove si trova il problema. Suppongo che il test che stavi cercando di superare fosse un troppo grande passo . Quindi penso che il problema risieda nel tuo stesso test, dovresti eliminare / commentare quel test e trovare un test più semplice che ti permetta di fare un passaggio del bambino senza introdurre LineSegment.Intersects (LineSegment) parte. Una volta superato il test, puoi refactare il tuo codice (qui applicherai il principio SRP) spostando questa nuova logica in un metodo LineSegment.Intersects (LineSegment). I tuoi test continueranno a passare perché non avresti modificato alcun comportamento ma spostato solo un po 'di codice.

Sulla tua soluzione di design attuale

Ma per me, hai un problema di design più profondo qui è che stai violando il Principio di Responsabilità Unico . Il ruolo di un punto è .... essere un punto, questo è tutto. Non c'è intelligenza nell'essere un punto, è solo un valore xey. I punti sono tipi di valore . Questo è lo stesso per i segmenti, i segmenti sono tipi di valore composti da due punti. Possono contenere un po 'di "intelligenza" per esempio per calcolare la loro lunghezza in base alla loro posizione in punti. Ma questo è tutto.

Ora decidere se un punto e un segmento sono in collisione, è di per sé una completa responsabilità. Ed è certamente troppo lavoro per un punto o segmento da gestire da solo. Non può appartenere alla classe Point, perché altrimenti i Punti sapranno dei Segmenti. E non può appartenere ai segmenti perché i segmenti hanno già la responsabilità di occuparsi dei punti all'interno del segmento e magari anche calcolare la lunghezza del segmento stesso.

Quindi questa responsabilità dovrebbe essere proprio di un'altra classe come ad esempio un "PointSegmentCollisionDetector" che avrebbe un metodo come:

bool AreInCollision (punto p, segmento s)

E questo è qualcosa che testerai separatamente da Punti e Segmenti.

La cosa carina di quel design è che ora potresti avere un'implementazione diversa del tuo rilevatore di collisione. Quindi sarebbe facile per esempio confrontare il tuo motore di gioco (presumo tu stia scrivendo un gioco: p) cambiando il metodo di rilevamento delle collisioni in fase di runtime. O per fare alcuni controlli visivi / esperimenti in fase di runtime tra diverse strategie di rilevamento delle collisioni.

Al momento, mettendo questa logica nella tua classe di punti, stai bloccando le cose e spingendo troppe responsabilità sulla classe Point.

Spero che abbia senso,

    
risposta data 10.11.2012 - 03:24
fonte
2

La cosa più semplice da fare in un modo TDD sarebbe estrarre un'interfaccia per LineSegment e modificare il parametro del metodo per acquisire l'interfaccia. Quindi puoi prendere in giro il segmento della linea di input e codificare / testare il metodo Intersect in modo indipendente.

    
risposta data 09.11.2012 - 18:55
fonte
0

Con jUnit4 puoi usare l'annotazione @Ignore per i test che vuoi rimandare.

Aggiungi l'annotazione a ciascun metodo che vuoi rimandare e continua a scrivere test per la funzionalità richiesta. Cerchia indietro per rifattorizzare i precedenti casi di test più tardi.

    
risposta data 15.07.2015 - 16:30
fonte

Leggi altre domande sui tag