Ereditarietà nelle classi di test

5

Ho un'interfaccia Serializer con metodi serialize e isSerializerFor . Ho creato una prima implementazione di questo utilizzando TDD, e ho finito con un bel caso di test pulito che copriva completamente una buona implementazione pulita. Per avere un'idea più concreta, ecco un commit pertinente .

Ora qualcun altro (che non è abituato a fare TDD) ha iniziato a scrivere la seconda implementazione di questa interfaccia Serializer . Questa persona ha capito che il test necessario per la seconda implementazione avrebbe qualche sovrapposizione con il mio test iniziale. Quindi è stata creata una classe di base astratta per i test del serializzatore , tenendo i metodi che si sospetta siano comuni a tutti casi di test del serializzatore.

Non sono contento di questo per due motivi principali. Prima di tutto, l'unica ragione per cui l'ereditarietà è usata qui è per il riutilizzo del codice, che è un grande odore di codice. In secondo luogo, questo interrompe il ciclo TDD. Se ora voglio creare un'altra implementazione di Serializer e creare una derivata della classe di test di base, finisco per dover implementare tutto il codice di produzione in un solo passaggio.

D'altra parte, la semplice duplicazione del codice comune nelle classi di test sembra piuttosto strana. Spero che la composizione possa essere utilizzata qui in modo corretto per evitare questi problemi.

Sembra una situazione abbastanza comune. Come risolverebbe questo? Cosa faresti in modo diverso?

    
posta Jeroen De Dauw 20.12.2013 - 15:10
fonte

1 risposta

5

First of all, the only reason inheritance is used here is for code reuse, which is a big code smell.

Diverse implementazioni di serializzatori (come SerializerA , SerializerB , SerializerC ) portano a diverse classi SerializerTest ( SerializerTestA , SerializerTestB , SerializerTestC ), che sono tutti "tester di serializzazione", dando la tua relazione "è-a" con la comune classe di base SerializerTester , quindi l'ereditarietà è probabilmente lo strumento giusto qui.

If I now want to create another implementation of Serializer, and create a derivative of the base test class, I end up having to implement all production code in one step.

Io non la penso così Avvia TDD creando una classe di test SerializerTestA (derivata da SerializerTester , con nient'altro che creare un oggetto SerializerA . Questo non verrà compilato - il test è RED. Quindi implementerai SerializerA con nient'altro che un costruttore e implementazione del metodo vuoto per l'interfaccia Serializer - il test è VERDE. Successivamente, aggiungi il primo metodo di prova a SerializerTestA , che può delegare solo una chiamata di prova a un metodo corrispondente in SerializerTester . in SerializerA , il tuo test fallisce - ROSSO di nuovo, quindi implementa il primo metodo vuoto in SerializerA , finché il tuo primo non fallisce più - lo stato del test è VERDE di nuovo.

Quindi, se non chiami alcun metodo di prova da SerializerTester che richiede l'implementazione completa della classe Serializer , puoi continuare a farlo passo passo, rimanendo sul classico percorso TDD.

    
risposta data 20.12.2013 - 16:03
fonte

Leggi altre domande sui tag