Herança em classes de teste

5

Eu tenho uma interface Serializer com os métodos serialize e isSerializerFor . Eu criei uma primeira implementação deste usando o TDD, e acabei com um bom caso de teste limpo cobrindo completamente uma implementação limpa. Para ter uma ideia mais concreta, aqui está um commit relevante .

Agora, outra pessoa (que não está acostumada a fazer o TDD) começou a escrever a segunda implementação dessa interface Serializer . Essa pessoa imaginou que o teste necessário para a segunda implementação teria alguma sobreposição com o meu teste inicial. Assim, uma classe base abstrata para testes de serializador foi criada, mantendo os métodos que são suspeitos de serem comuns a todos casos de teste de serializador.

Não estou feliz com isso por dois motivos principais. Primeiro de tudo, a única razão pela qual a herança é usada aqui é para reutilização de código, que é um grande cheiro de código. Em segundo lugar, isso quebra o ciclo TDD. Se agora eu quiser criar outra implementação de Serializer e criar uma derivada da classe de teste de base, acabarei tendo que implementar todo o código de produção em uma única etapa.

Por outro lado, simplesmente duplicar o código comum nas classes de teste também parece bastante estranho. Espero que a composição possa ser usada aqui de maneira sadia para evitar esses problemas.

Esta parece ser uma situação razoavelmente comum. Como você resolveria isso? O que você faria diferente?

    
por Jeroen De Dauw 20.12.2013 / 15:10
fonte

1 resposta

5

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

Diferentes implementações de serializador (como SerializerA , SerializerB , SerializerC ) levam a diferentes SerializerTest classes ( SerializerTestA , SerializerTestB , SerializerTestC ), que são todas "Testadores de serializadores", dando seu relacionamento "é-um" com a classe base SerializerTester base, então a herança é provavelmente a ferramenta certa aqui.

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.

Eu não penso assim. Você inicia o TDD criando uma classe de teste SerializerTestA (derivada de SerializerTester , com nada mais do que criar um objeto SerializerA . Isso não compilará - o teste é RED. Então você implementa SerializerA com nada mais do que um construtor e implementações de método vazio para a interface Serializer - o teste é VERDE Em seguida, você adiciona seu primeiro método de teste a SerializerTestA , que pode apenas delegar uma chamada de teste a um método correspondente em SerializerTester . em SerializerA , seu teste falha - RED novamente. Em seguida, você implementa o primeiro método vazio em SerializerA , até que o primeiro não falhe mais - o status do teste é VERDE novamente.

Portanto, desde que você não chame nenhum método de teste de SerializerTester precisando da implementação completa da classe Serializer , você ainda poderá fazer isso passo a passo, permanecendo no caminho clássico do TDD.

    
por 20.12.2013 / 16:03
fonte