Eu queria saber como testar as classes abstratas de unidade e classes que estendem classes abstratas.
Devo testar a classe abstrata estendendo-a, removendo os métodos abstratos e testando todos os métodos concretos? Em seguida, apenas teste os métodos que eu substituo e teste os métodos abstratos nos testes de unidade para objetos que estendem minha classe abstrata?
Devo ter um caso de teste abstrato que possa ser usado para testar os métodos da classe abstrata e estender essa classe no meu caso de teste para objetos que estendem a classe abstrata?
Observe que minha classe abstrata tem alguns métodos concretos.
fonte
Existem duas maneiras pelas quais as classes base abstratas são usadas.
Você está especializando seu objeto abstrato, mas todos os clientes usarão a classe derivada por meio de sua interface base.
Você está usando uma classe base abstrata para fatorar a duplicação nos objetos em seu design, e os clientes usam as implementações concretas por meio de suas próprias interfaces.
Solução para 1 - Padrão de Estratégia
Se você tiver a primeira situação, realmente terá uma interface definida pelos métodos virtuais na classe abstrata que suas classes derivadas estão implementando.
Você deve considerar fazer disso uma interface real, alterar sua classe abstrata para ser concreta e tomar uma instância dessa interface em seu construtor. Suas classes derivadas tornam-se implementações dessa nova interface.
Isso significa que agora você pode testar sua classe abstrata abstrata usando uma instância simulada da nova interface e cada nova implementação através da interface agora pública. Tudo é simples e testável.
Solução para 2
Se você tiver a segunda situação, sua classe abstrata estará trabalhando como uma classe auxiliar.
Dê uma olhada na funcionalidade que ela contém. Veja se alguma delas pode ser empurrada para os objetos que estão sendo manipulados para minimizar essa duplicação. Se você ainda tiver alguma coisa, tente torná-la uma classe auxiliar que sua implementação concreta leva no construtor e remova a classe base.
Isso novamente leva a classes concretas que são simples e facilmente testáveis.
Como uma regra
Favorecer redes complexas de objetos simples em detrimento de uma rede simples de objetos complexos.
A chave para código testável extensível são pequenos blocos de construção e fiação independente.
Atualizado: Como lidar com misturas de ambos?
É possível ter uma classe base executando essas duas funções ... ou seja: possui uma interface pública e métodos auxiliares protegidos. Se for esse o caso, você pode fatorar os métodos auxiliares em uma classe (cenário2) e converter a árvore de herança em um padrão de estratégia.
Se você achar que tem alguns métodos que sua classe base implementa diretamente e outros são virtuais, ainda é possível converter a árvore de herança em um padrão de estratégia, mas eu também consideraria um bom indicador de que as responsabilidades não estão alinhadas corretamente e podem precisa de refatoração.
Atualização 2: Classes abstratas como um trampolim (12/06/2014)
Eu tive uma situação outro dia em que usei abstract, então gostaria de explorar o porquê.
Temos um formato padrão para nossos arquivos de configuração. Esta ferramenta específica possui 3 arquivos de configuração, todos nesse formato. Eu queria uma classe fortemente tipada para cada arquivo de configuração, para que, através da injeção de dependência, uma classe pudesse solicitar as configurações de que se importava.
Eu implementei isso tendo uma classe base abstrata que sabe analisar os formatos dos arquivos de configurações e as classes derivadas que expunham esses mesmos métodos, mas encapsulavam o local do arquivo de configurações.
Eu poderia ter escrito um "SettingsFileParser" que as 3 classes agruparam e depois delegado à classe base para expor os métodos de acesso a dados. Eu escolhi não fazer isso ainda, pois isso levaria a 3 classes derivadas com mais delegação código de do que qualquer outra coisa.
No entanto ... conforme esse código evolui e os consumidores de cada uma dessas classes de configurações ficam mais claros. Cada usuário de configurações solicitará algumas configurações e as transformará de alguma maneira (como as configurações são texto, elas podem ser agrupadas em objetos para convertê-las em números etc.). Quando isso acontece, vou começar a extrair essa lógica para métodos de manipulação de dados e empurrá-los de volta para as classes de configurações fortemente tipadas. Isso levará a uma interface de nível superior para cada conjunto de configurações, que eventualmente não está mais ciente de que está lidando com 'configurações'.
Nesse ponto, as classes de configurações fortemente tipadas não precisarão mais dos métodos "getter" que expõem a implementação subjacente das "configurações".
Nesse ponto, eu não gostaria mais que sua interface pública incluísse os métodos do acessador de configurações; então vou mudar essa classe para encapsular uma classe de analisador de configurações em vez de derivar dela.
A classe Abstract é, portanto: uma maneira de evitar o código de delegação no momento e um marcador no código para me lembrar de mudar o design mais tarde. Talvez eu nunca chegue a isso, então pode demorar um pouco ... apenas o código pode dizer.
Acho que isso é verdade com qualquer regra ... como "sem métodos estáticos" ou "sem métodos privados". Eles indicam um cheiro no código ... e isso é bom. Mantém você procurando a abstração que você perdeu ... e permite que você continue agregando valor ao seu cliente nesse meio tempo.
Eu imagino regras como esta definindo uma paisagem, onde o código sustentável vive nos vales. À medida que você adiciona um novo comportamento, é como chover no seu código. Inicialmente, você o coloca onde quer que ele apareça. Depois, refatora para permitir que as forças do bom design promovam o comportamento até que tudo acabe nos vales.
fonte
O que faço para classes abstratas e interfaces é o seguinte: Escrevo um teste, que usa o objeto como ele é concreto. Mas a variável do tipo X (X é a classe abstrata) não está definida no teste. Essa classe de teste não é adicionada ao conjunto de testes, mas suas subclasses, que possuem um método de configuração que define a variável para uma implementação concreta do X. Dessa forma, não duplico o código de teste. As subclasses do teste não usado podem adicionar mais métodos de teste, se necessário.
fonte
Para fazer um teste de unidade especificamente na classe abstrata, você deve derivá-lo para fins de teste, teste resultados base.method () e comportamento pretendido ao herdar.
Você testa um método chamando-o, então testa uma classe abstrata implementando-a ...
fonte
Se a sua classe abstrata contiver uma funcionalidade concreta que tenha valor comercial, normalmente eu a testarei diretamente criando um duplo de teste que oculte os dados abstratos ou usando uma estrutura de simulação para fazer isso por mim. Qual eu escolho depende muito se eu preciso escrever implementações específicas para testes dos métodos abstratos ou não.
O cenário mais comum em que preciso fazer isso é quando estou usando o padrão de método de modelo , como quando estou criando algum tipo de estrutura extensível que será usada por terceiros. Nesse caso, a classe abstrata é o que define o algoritmo que eu quero testar, por isso faz mais sentido testar a base abstrata do que uma implementação específica.
No entanto, acho importante que esses testes se concentrem apenas nas implementações concretas da lógica real dos negócios ; você não deve realizar os detalhes da implementação do teste de unidade da classe abstrata, pois acabará com testes frágeis.
fonte
Uma maneira é escrever um caso de teste abstrato que corresponda à sua classe abstrata e, em seguida, escrever casos de teste concretos que subclasses seu caso de teste abstrato. faça isso para cada subclasse concreta de sua classe abstrata original (ou seja, sua hierarquia de casos de teste reflete sua hierarquia de classes). consulte Testar uma interface no livro de receitas de junit: http://safari.informit.com/9781932394238/ch02lev1sec6 .
consulte também Superclasse do Testcase nos padrões xUnit: http://xunitpatterns.com/Testcase%20Superclass.html
fonte
Eu argumentaria contra testes "abstratos". Eu acho que um teste é uma idéia concreta e não tem uma abstração. Se você tiver elementos comuns, coloque-os em métodos ou classes auxiliares para que todos possam usar.
Quanto ao teste de uma classe de teste abstrata, lembre-se de perguntar o que está testando. Existem várias abordagens e você deve descobrir o que funciona no seu cenário. Você está tentando testar um novo método na sua subclasse? Em seguida, faça com que seus testes interajam apenas com esse método. Você está testando os métodos em sua classe base? Em seguida, provavelmente tenha um equipamento separado apenas para essa classe e teste cada método individualmente com quantos testes forem necessários.
fonte
Este é o padrão que geralmente sigo ao configurar um chicote para testar uma classe abstrata:
E a versão que eu uso em teste:
Se os métodos abstratos são chamados quando eu não espero, os testes falham. Ao organizar os testes, posso facilmente esboçar os métodos abstratos com lambdas que executam afirmações, lançam exceções, retornam valores diferentes etc.
fonte
Se os métodos concretos invocam qualquer um dos métodos abstratos em que a estratégia não funcionará, e você deseja testar o comportamento de cada classe filho separadamente. Caso contrário, estendê-lo e remover os métodos abstratos, como você descreveu, deve ser bom, novamente desde que os métodos concretos da classe abstrata sejam dissociados das classes filho.
fonte
Suponho que você queira testar a funcionalidade básica de uma classe abstrata ... Mas você provavelmente seria melhor estendendo a classe sem substituir nenhum método, e zombando com o mínimo esforço dos métodos abstratos.
fonte
Uma das principais motivações para o uso de uma classe abstrata é ativar o polimorfismo em seu aplicativo - ou seja: você pode substituir uma versão diferente em tempo de execução. De fato, isso é quase o mesmo que usar uma interface, exceto que a classe abstrata fornece algum encanamento comum, geralmente chamado de padrão de modelo .
De uma perspectiva de teste de unidade, há duas coisas a considerar:
Interação da sua classe abstrata com as classes relacionadas . O uso de uma estrutura de teste simulada é ideal para esse cenário, pois mostra que sua classe abstrata funciona bem com outras pessoas.
Funcionalidade de classes derivadas . Se você tiver uma lógica personalizada que você escreveu para suas classes derivadas, teste essas classes isoladamente.
edit: RhinoMocks é uma incrível estrutura de testes simulados que pode gerar objetos simulados em tempo de execução, derivando dinamicamente de sua classe. Essa abordagem pode economizar inúmeras horas de aulas derivadas de codificação manual.
fonte
Primeiro, se a classe abstrata continha algum método concreto, acho que você deveria fazer isso, considerando este exemplo
fonte
Se uma classe abstrata for apropriada para sua implementação, teste (como sugerido acima) uma classe concreta derivada. Suas suposições estão corretas.
Para evitar futuras confusões, saiba que essa classe de teste concreta não é uma farsa , mas uma farsa .
Em termos estritos, um mock é definido pelas seguintes características:
fonte
Após a resposta de @ patrick-desjardins, implementei abstract e sua classe de implementação
@Test
, da seguinte forma:Classe abstrata - ABC.java
Como as classes Abstratas não podem ser instanciadas, mas podem ser subclassificadas , a classe concreta DEF.java é a seguinte:
Classe @Test para testar o método abstrato e não-abstrato:
fonte