Supondo que eu tenha a seguinte estrutura de classe (simplificada):
class Base
{
public:
Base(int valueForFoo) : foo(valueForFoo) { };
virtual ~Base() = 0;
int doThings() { return foo; };
int doOtherThings() { return 42; };
protected:
int foo;
}
class BarDerived : public Base
{
public:
BarDerived() : Base(12) { };
~BarDerived() { };
int doBarThings() { return foo + 1; };
}
class BazDerived : public Base
{
public:
BazDerived() : Base(25) { };
~BazDerived() { };
int doBazThings() { return 2 * foo; };
}
Como você pode ver, a doThings
função na classe Base retorna resultados diferentes em cada classe Derived devido aos diferentes valores de foo
, enquanto a doOtherThings
função se comporta de forma idêntica em todas as classes .
Quando eu quiser implementar testes de unidade para essas classes, a manipulação de doThings
, doBarThings
/ doBazThings
é claro para mim - eles precisam ser cobertas para cada classe derivada. Mas como deve doOtherThings
ser tratado? É uma boa prática duplicar essencialmente o caso de teste nas duas classes derivadas? O problema piora se houver meia dúzia de funções como doOtherThings
e mais classes derivadas .
c++
unit-testing
testing
CharonX
fonte
fonte
virtual
?BarDerived
eBase
pode ter sido uma vez a mesma classe. Quando uma funcionalidade semelhante era adicionada, a parte comum era movida para a classe Base, com diferentes especializações implementadas em cada classe Derivada.Respostas:
Nos seus testes,
BarDerived
você deseja provar que todos os métodos (públicos)BarDerived
funcionam corretamente (para as situações que você testou). Da mesma forma paraBazDerived
.O fato de alguns dos métodos serem implementados em uma classe base não altera esse objetivo de teste para
BarDerived
eBazDerived
. Que leva à conclusão de queBase::doOtherThings
devem ser testados tanto no contexto deBarDerived
eBazDerived
e que você começa testes muito semelhantes para essa função.A vantagem do teste
doOtherThings
para cada classe derivada é que, se os requisitos deBarDerived
mudançaBarDerived::doOtherThings
devem retornar 24, a falha doBazDerived
teste no testcase indica que você pode estar quebrando os requisitos de outra classe.fonte
Eu normalmente esperaria que o Base tivesse sua própria especificação, que você pode verificar para qualquer implementação em conformidade, incluindo as classes derivadas.
fonte
Você tem um conflito aqui.
Você deseja testar o valor de retorno de
doThings()
, que depende de um literal (valor const).Qualquer teste que você escrever para isso será inerentemente resumido ao testar um valor const , o que não faz sentido.
Para mostrar um exemplo mais sensato (sou mais rápido com C #, mas o princípio é o mesmo)
Esta classe pode ser testada significativamente:
Isso faz mais sentido para testar. Sua saída é baseada na entrada que você escolheu para fornecer. Nesse caso, você pode dar a uma classe uma entrada escolhida arbitrariamente, observar sua saída e testar se ela corresponde às suas expectativas.
Alguns exemplos deste princípio de teste. Observe que meus exemplos sempre têm controle direto sobre qual é a entrada do método testável.
Todos esses testes podem inserir o valor que quiserem , desde que suas expectativas estejam alinhadas com o que estão inserindo.
Mas se sua saída for decidida por um valor constante, você precisará criar uma expectativa constante e testar se o primeiro corresponde ao segundo. O que é bobagem, isso sempre ou nunca vai passar; nenhum dos quais é um resultado significativo.
Alguns exemplos deste princípio de teste. Observe que esses exemplos não têm controle sobre pelo menos um dos valores que estão sendo comparados.
Esses testes para um valor específico. Se você alterar esse valor, seus testes falharão. Em essência, você não está testando se sua lógica funciona para qualquer entrada válida, está simplesmente testando se alguém é capaz de prever com precisão um resultado sobre o qual não tem controle.
Isso é essencialmente testar o conhecimento do escritor do teste sobre a lógica de negócios. Não está testando o código, mas o próprio escritor.
Revertendo para o seu exemplo:
Por que
BarDerived
sempre tem umfoo
igual a12
? Qual o significado disso?E, como você já decidiu isso, o que você está tentando ganhar escrevendo um teste que confirma que
BarDerived
sempre éfoo
igual a12
?Isso fica ainda pior se você começar a considerar que
doThings()
pode ser substituído em uma classe derivada. Imagine se vocêAnotherDerived
deseja substituirdoThings()
para que ele sempre retornefoo * 2
. Agora, você terá uma classe que é codificada permanentementeBase(12)
, cujodoThings()
valor é 24. Embora tecnicamente testável, é desprovida de qualquer significado contextual. O teste não é compreensível.Eu realmente não consigo pensar em uma razão para usar essa abordagem de valor codificado. Mesmo se houver um caso de uso válido, não entendo por que você está tentando escrever um teste para confirmar esse valor codificado . Não há nada a ganhar testando se um valor constante é igual ao mesmo valor constante.
Qualquer falha no teste prova inerentemente que o teste está errado . Não há resultado em que uma falha no teste comprove que a lógica de negócios está errada. Você é efetivamente incapaz de confirmar quais testes são criados para confirmar em primeiro lugar.
A questão não tem nada a ver com herança, caso você esteja se perguntando. Você acabou de acontecer de ter usado um valor const no construtor da classe base, mas você poderia ter usado este valor const em qualquer outro lugar e então ele não estaria relacionada a uma classe herdada.
Editar
Há casos em que valores codificados permanentemente não são um problema. (novamente, desculpe pela sintaxe do C #, mas o princípio ainda é o mesmo)
Enquanto o valor constante (
2
/3
) ainda está influenciando o valor de saídaGetMultipliedValue()
, o consumidor da sua classe ainda tem controle sobre ele também!Neste exemplo, testes significativos ainda podem ser gravados:
base(value, 2)
corresponde à const ininputValue * 2
.O primeiro ponto não é relevante para o teste. O segundo é!
fonte
<
e>
chaves que encapsulam as tags HTML. Infelizmente (devido à insanidade), um navegador "especializado" usa![{
e}]!
, em vez disso, a Intitech decide que você precisa oferecer suporte a esse navegador no seu gravador de HTML. Por exemplo, você tem a funçãogetHeaderStart()
egetHeaderEnd()
que - até agora - retornou<HEADER>
e<\HEADER>
.<
e a outra com![{
. Mas isso seria muito ruim. Assim, cada função deve inserir o (s) caractere (s) definido (s) em uma variável nos locais<
e>
iria e criar classes derivadas que - dependendo se são de padrão ou insanas, fornecem a função apropriada<HEADER>
ou![{HEADER}]!
Nenhuma das duasgetHeaderStart()
depende da entrada e depende no conjunto constante durante a construção da classe derivada. Ainda assim, eu me sentiria desconfortável se você me disse que testá-las não faz sentido ...getHeaderStart()
faz sentido ou não?