Cosa succede se non riesco a far fallire il mio test unitario in "Red, Green, Refactor" di TDD?

4

Quindi diciamo che ho un test:

@Test
public void MoveY_MoveZero_DoesNotMove() {
    Point p = new Point(50.0, 50.0);
    p.MoveY(0.0);
    Assert.assertAreEqual(50.0, p.Y);
}

Questo test quindi mi consente di creare la classe Punto :

public class Point {
    double X; double Y;
    public void MoveY(double yDisplace) {
        throw new NotYetImplementedException();
    }
}

Ok. Fallisce Buono. Quindi rimuovo l'eccezione e ottengo il verde. Grande, ma ovviamente ho bisogno di testare se cambia valore. Quindi scrivo un test che chiama p.MoveY (10.0) e controlla se p.Y è uguale a 60.0. Non funziona, quindi cambio la funzione in modo che appaia così:

public void MoveY(double yDisplace) {
    Y += yDisplace;
}

Grande, ora ho di nuovo verde e posso andare avanti. Ho provato a non muovermi e muovermi nella direzione positiva, quindi naturalmente dovrei testare un valore negativo. L'unico problema con questo test è che se ho scritto il test correttamente, allora non fallisce all'inizio. Ciò significa che non ho adattato il principio di "Red, Green, Refactor."

Ovviamente, questo è un problema di primo mondo di TDD, ma ottenere un errore all'inizio è utile in quanto dimostra che il test può fallire. Altrimenti questo test apparentemente innocente che sta passando per ragioni errate potrebbe fallire in seguito perché è stato scritto male. Questo potrebbe non essere un problema se accadesse 5 minuti più tardi, ma cosa succederebbe se accadesse al povero-linfa che ha indotto il codice due anni dopo. Quello che sa è che MoveY non funziona con valori negativi perché è quello che il test gli sta dicendo. Ma, potrebbe davvero funzionare e solo essere un bug nel test.

Non penso che ciò accada in questo caso particolare perché l'esempio di codice è così semplice, ma se fosse un sistema complicato di grandi dimensioni potrebbe non essere il caso. Sembra pazzesco dire che voglio fallire i miei test, ma questo è un passo importante in TDD, per buone ragioni.

    
posta Joshua Harris 11.11.2012 - 08:23
fonte

4 risposte

9

Quindi non scrivere.

Sei già arrivato al punto in cui eri

  • Avere il codice più semplice che supera tutti i test.
  • Non riesci a pensare ai casi limite che non sono coperti dal codice.

È ora di smettere di preoccuparsi di questo metodo.

Se stai scrivendo test per edge case che conosci già coperti, ti preoccupi di più della parte test di TDD rispetto alla parte di progettazione. Sfortunatamente, questa non è una cosa molto produttiva da fare.

Perché hai bisogno di sapere che il tuo metodo funziona con i negativi? Quanto lontano lo prendi? Quindi testare le frazioni? Metti alla prova numeri dispari e pari? Ogni fattore di dieci? Ogni possibile valore di double?

Se no, allora perché testare i negativi? Non hanno intenzione di agire in modo diverso dai lati positivi.

    
risposta data 11.11.2012 - 08:57
fonte
2

Innanzitutto non causerei il fallimento del mio codice di produzione generando un'eccezione. Che porta al secondo punto. Il mio primo codice fallito sarebbe quello di usare un numero magico, zero o qualcos'altro. Il test per il non movimento avrebbe quindi un piccolo, ma non zero, di passaggio quando non era previsto - che va bene, è in parte il motivo per cui hai più di un test.

Quindi, se la tua implementazione era stata

public void MoveY(double yDisplace){
  Y=0;
}

Il tuo test sarebbe fallito. Ulteriori test dipenderanno dal modo in cui vedete un modo per violare il codice esistente: identificate un problema non coperto dal test o dal codice esistente. In questo caso, in realtà non si muove, quindi il prossimo test, Move_Moves, sarebbe per vedere se lo fa, ottenere il semaforo rosso e quindi correggere il codice. Il passaggio successivo sarebbe di rendersi conto che il tuo test iniziale è davvero superfluo, non è nemmeno un caso speciale del tuo test Move_Moves: verrebbe provato che beforeY + yDistance = afterY, che copre lo zero proprio come efficacemente come 1.2. Quindi cancelli il test.

Solo se hai una funzione più complessa, dove zero potrebbe essere trattato diversamente da 1.2 o 42, dovresti testare entrambi per vedere se sposta la giusta quantità e che non si muove per zero. Il test non produttivo dovrebbe essere rimosso come qualsiasi altro codice.

Qual è il mio punto finale: i test sono modi per identificare come le cose possono andare storte e assicurarti che il tuo codice di produzione sia coperto in questo modo sia ora che in futuro. La ragione per scrivere prima il test è aiutarti a identificare i casi limite prima di mettere in produzione il codice e verificare che la tua soluzione li gestisca correttamente, tieni il test in giro per evitare l'aggressività. Se un test non aiuta con uno di questi, non scrivere un test.

    
risposta data 11.11.2012 - 09:29
fonte
1

Il tuo primo caso di prova è venuto dalla tua specifica (che era probabilmente solo nella tua testa). Ora, cosa dice la tua specifica sui valori negativi? Li include esplicitamente? Se è così, scrivi un test. In caso contrario, non.

    
risposta data 11.11.2012 - 10:05
fonte
0

La parte Red-Green del ciclo mette alla prova il test. Se hai la sensazione che un caso limite particolare debba essere preso in considerazione, o solo per dimostrare che è stato preso in considerazione, direi che è legittimo forzare artificialmente un errore a dimostrare che un test blocca l'implementazione in un corretto comportamento.

Nel caso dell'esempio, è probabilmente non necessario. Ma, se hai bisogno di un'implementazione MoveY più complicata che tiene conto di ... strane regole, barriere, limiti e così via, è potenzialmente utile assicurare che tutte le regole aggiuntive e il controllo dei limiti funzionino su 0 come previsto (non del tutto).

Quindi ... scrivi il codice in modo errato:

public void MoveY(double yDisplace) {
    Y += 100;  // to test the test
    Y += yDisplace;
}

Eseguire il test per verificare che i test MoveY pertinenti non abbiano esito positivo. E poi assicurati che la rimozione del codice errato causi il test da superare:

public void MoveY(double yDisplace) {
    Y += yDisplace;
}

Se il tuo progetto è stato rigidamente TDD dall'inizio; di solito non avrai bisogno di fare questo genere di cose. Ma, anche in gran parte del codice TDD, ho riscontrato bug transitori che non potevo essere sicuro di aver risolto senza simulare temporaneamente un errore nel codice down-the-line in modo simile.

Allo stesso modo, nel processo di porting di codice da un vecchio sistema a uno nuovo, ho scritto test che avrebbero fallito sul codice OLD ma non sul codice NEW semplicemente perché il nuovo codice era molto più semplice. Quindi, fallire manualmente questi test mi dà la certezza che le complessità future non reintrodurranno bug che la complessità OLD inizialmente ha prodotto: il mio test è stato testato per proteggerli!

    
risposta data 08.05.2015 - 21:59
fonte

Leggi altre domande sui tag