Como unidade de teste classs abstratas: estender com stubs?

Eu queria saber como unidades de classs abstratas de teste e classs que estendem classs abstratas.

Devo testar a class abstrata estendendo-a, esboçando os methods abstratos e, em seguida, testando todos os methods concretos? Em seguida, teste apenas os methods que eu sobreponho e teste os methods abstratos nos testes de unidade para objects que estendem minha class abstrata?

Devo ter um caso de teste abstrato que possa ser usado para testar os methods da class abstrata e estender essa class no meu caso de teste para objects que estendem a class abstrata?

Note que minha class abstrata tem alguns methods concretos.

Escreva um object Mock e use-os apenas para testes. Eles geralmente são muito, muito, muito mínimos (herdados da class abstrata) e não mais.Então, no seu Teste de Unidade, você pode chamar o método abstrato que deseja testar.

Você deve testar uma class abstrata que contenha alguma lógica como todas as outras classs que você possui.

Existem duas maneiras em que classs base abstratas são usadas.

  1. Você está especializando seu object abstrato, mas todos os clientes usarão a class derivada por meio de sua interface base.

  2. Você está usando uma class base abstrata para fatorar a duplicação dentro de objects em seu projeto, e os clientes usam as implementações concretas através de suas próprias interfaces.


Solução para 1 – padrão de estratégia

Opção 1

Se você tiver a primeira situação, então você realmente tem uma interface definida pelos methods virtuais na class abstrata que suas classs derivadas estão implementando.

Você deve considerar fazer disso uma interface real, alterando sua class abstrata para ser concreta e obter uma instância dessa interface em seu construtor. Suas classs derivadas tornam-se implementações dessa nova interface.

IMotor

Isso significa que agora você pode testar sua class abstrata anterior 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 class abstrata estará funcionando como uma class auxiliar.

ResumoHelper

Dê uma olhada na funcionalidade que ela contém. Veja se algum deles pode ser colocado nos objects que estão sendo manipulados para minimizar essa duplicação. Se você ainda tem alguma coisa, olhe para torná-la uma class auxiliar que sua implementação concreta tenha em seu construtor e remova sua class base.

Ajudante de motor

Isso leva novamente a classs concretas que são simples e facilmente testáveis.


Como uma regra

Favorecer a rede complexa de objects simples em uma rede simples de objects complexos.

A chave para o código testável extensível são pequenos blocos de construção e cabeamento independente.


Atualizado: Como lidar com misturas de ambos?

É possível ter uma class base executando ambas as funções … isto é: ela tem uma interface pública e protegeu methods auxiliares. Se esse for o caso, você poderá fatorar os methods auxiliares em uma class (scenario2) e converter a tree de inheritance em um padrão de estratégia.

Se você acha que alguns methods implementados diretamente pela sua class base e outros são virtuais, então você ainda pode converter a tree de inheritance em um padrão de estratégia, mas eu também considero um bom indicador de que as responsabilidades não estão alinhadas corretamente e podem precisa de refatoração.


Atualização 2: Abstract Classes como um trampolim (2014/06/12)

Eu tive uma situação no outro dia em que eu usei resumo, então eu gostaria de explorar o porquê.

Nós temos um formato padrão para nossos arquivos de configuração. Esta ferramenta particular tem 3 arquivos de configuração, todos nesse formato. Eu queria uma class fortemente tipada para cada arquivo de configuração, então, através da injeção de dependência, uma class poderia pedir as configurações com as quais se importava.

Implementei isso tendo uma class base abstrata que sabe como analisar os formatos de arquivos de configuração e as classs derivadas que expuseram esses mesmos methods, mas encapsulou o local do arquivo de configurações.

Eu poderia ter escrito um “SettingsFileParser” que as 3 classs envolvidas e, em seguida, delegado através da class base para expor os methods de access a dados. Eu escolhi não fazer isso ainda, pois isso levaria a 3 classs derivadas com mais código de delegação nelas do que qualquer outra coisa.

No entanto … como este código evolui e os consumidores de cada uma dessas classs de configurações se tornam mais claros. A cada configuração, os usuários solicitarão algumas configurações e as transformarão de alguma forma (como configurações, o texto pode envolvê-las em objects de conversão em números etc.). Quando isso acontecer, vou começar a extrair essa lógica em methods de manipulação de dados e empurrá-los de volta para as classs de configurações fortemente tipadas. Isso levará a uma interface de nível mais alto para cada conjunto de configurações, que eventualmente não está mais ciente de que está lidando com ‘configurações’.

Neste ponto, as classs de configurações fortemente tipificadas não precisarão mais dos methods “getter” que expõem a implementação das ‘configurações’ subjacentes.

Nesse ponto, eu não desejaria mais que sua interface pública incluísse os methods de access às configurações; Por isso, alterarei essa class para encapsular uma class de analisador de configurações em vez de derivar dela.

A class Abstract é, portanto, uma forma de evitar o código de delegação no momento e um marcador no código para lembrar-me de alterar o design posteriormente. Eu posso nunca chegar a isso, então pode viver um bom tempo … só o código pode dizer.

Acho que isso é verdade com qualquer regra … como “sem methods estáticos” ou “sem methods privados”. Eles indicam um cheiro no código … e isso é bom. Ele mantém você procurando a abstração que você perdeu … e permite que você continue fornecendo valor ao seu cliente nesse meio tempo.

Eu imagino regras como esta definindo uma paisagem, onde o código de manutenção vive nos vales. À medida que você adiciona um novo comportamento, é como a chuva no seu código. Inicialmente, você o coloca onde quer que ele aterre. Então, você refatora para permitir que as forças do bom design empurrem o comportamento até que tudo acabe nos vales.

O que eu faço para classs abstratas e interfaces é o seguinte: Eu escrevo um teste, que usa o object como ele é concreto. Mas a variável do tipo X (X é a class abstrata) não está definida no teste. Esta class de teste não é adicionada à suite de testes, mas subclasss dela, que possuem um método de configuração que define a variável para uma implementação concreta do X. Dessa forma eu não duplico o código de teste. As subclasss do teste não usado podem adicionar mais methods de teste, se necessário.

Para fazer um teste de unidade especificamente na class abstrata, você deve derivá-lo para fins de teste, resultados do teste base.method () e comportamento pretendido ao herdar.

Você testa um método chamando-o para testar uma class abstrata implementando-a …

Se a sua class abstrata contiver funcionalidades concretas com valor de negócio, então, normalmente, testarei isso diretamente, criando um teste duplo que destrua os dados abstratos ou usando uma estrutura de simulação para fazer isso para mim. Qual deles eu escolho depende muito se eu preciso escrever implementações específicas do teste dos methods abstratos ou não.

O cenário mais comum no qual eu preciso fazer isso é quando eu estou usando o padrão Template Method , como quando eu estou construindo algum tipo de framework extensível que será usado por um terceiro. Neste caso, a class abstrata é o que define o algoritmo que eu quero testar, então faz mais sentido testar a base abstrata do que uma implementação específica.

No entanto, penso que é importante que estes testes se concentrem apenas nas implementações concretas da lógica comercial real ; você não deve testar detalhes de implementação de unidade da class abstrata porque você vai acabar com testes frágeis.

Uma maneira é escrever um caso de teste abstrato que corresponda à sua class abstrata e, em seguida, escrever casos de teste concretos que subclassificam seu caso de teste abstrato. faça isso para cada subclass concreta de sua class abstrata original (ou seja, sua hierarquia de casos de teste espelha sua hierarquia de classs). consulte Testar uma interface no livro de receitas do junit: http://safari.informit.com/9781932394238/ch02lev1sec6 .

veja também Superclass de Testcase em padrões xUnit: http://xunitpatterns.com/Testcase%20Superclass.html

Eu argumentaria contra testes “abstratos”. Eu acho que um teste é uma ideia concreta e não tem uma abstração. Se você tiver elementos comuns, coloque-os em methods ou classs auxiliares para todos usarem.

Quanto a testar uma class de teste abstrata, certifique-se de perguntar a si mesmo o que está sendo testado. 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 subclass? Em seguida, faça seus testes interagirem apenas com esse método. Você está testando os methods em sua class base? Então, provavelmente, tenha um acessório separado apenas para essa class e teste cada método individualmente com tantos testes quantos forem necessários.

Este é o padrão que costumo seguir ao configurar um arnês para testar uma class abstrata:

public abstract class MyBase{ /*...*/ public abstract void VoidMethod(object param1); public abstract object MethodWithReturn(object param1); /*,,,*/ } 

E a versão que eu uso em teste:

 public class MyBaseHarness : MyBase{ /*...*/ public Action VoidMethodFunction; public override void VoidMethod(object param1){ VoidMethodFunction(param1); } public Func MethodWithReturnFunction; public override object MethodWithReturn(object param1){ return MethodWihtReturnFunction(param1); } /*,,,*/ } 

Se os methods abstratos forem chamados quando eu não esperar, os testes falharão. Ao organizar os testes, eu posso facilmente eliminar os methods abstratos com lambdas que executam afirmações, lançam exceções, retornam valores diferentes, etc.

Se os methods concretos invocarem algum dos methods abstratos que a estratégia não funcionará, você desejará testar cada comportamento de class filho separadamente. Caso contrário, estendê-lo e stubbing os methods abstratos como você descreveu deve ser bom, novamente, desde que os methods concretos de class abstrata são dissociados de classs filho.

Eu suponho que você poderia querer testar a funcionalidade básica de uma class abstrata … Mas você provavelmente estaria em melhor situação estendendo a class sem sobrescrever quaisquer methods, e fazendo um mínimo de esforço para os methods abstratos.

Uma das principais motivações para usar uma class abstrata é ativar o polymorphism em seu aplicativo – por exemplo: você pode replace uma versão diferente em tempo de execução. Na verdade, isso é basicamente a mesma coisa que usar uma interface, exceto que a class abstrata fornece algum encanamento comum, muitas vezes chamado de padrão de modelo .

De uma perspectiva de teste de unidade, há duas coisas a serem consideradas:

  1. Interação da sua aula abstrata com as classs relacionadas . Usar uma estrutura de teste simulada é ideal para esse cenário, pois mostra que sua class abstrata funciona bem com os outros.

  2. Funcionalidade de classs derivadas . Se você tiver uma lógica personalizada que tenha escrito para suas classs derivadas, deverá testar essas classs isoladamente.

edit: RhinoMocks é um framework de teste simulado que pode gerar objects simulados em tempo de execução, derivando dinamicamente de sua class. Essa abordagem pode economizar inúmeras horas de aulas derivadas de codificação manual.

Primeiro, se a class abstrata contivesse algum método concreto, acho que você deveria fazer isso, considerando este exemplo

  public abstract class A { public boolean method 1 { // concrete method which we have to test. } } class B extends class A { @override public boolean method 1 { // override same method as above. } } class Test_A { private static B b; // reference object of the class B @Before public void init() { b = new B (); } @Test public void Test_method 1 { b.method 1; // use some assertion statements. } } 

Seguindo a resposta @ patrick-desjardins, implementei abstract e sua class de implementação junto com @Test seguinte forma:

Aula de Abstarct – ABC.java

 import java.util.ArrayList; import java.util.List; public abstract class ABC { abstract String sayHello(); public List getList() { final List defaultList = new ArrayList<>(); defaultList.add("abstract class"); return defaultList; } } 

Como as classs abstratas não podem ser instanciadas, mas podem ser subclassificadas , a class concreta DEF.java é a seguinte:

 public class DEF extends ABC { @Override public String sayHello() { return "Hello!"; } } 

@Test class para testar methods abstratos e não abstratos:

 import org.junit.Before; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.contains; import java.util.Collection; import java.util.List; import static org.hamcrest.Matchers.equalTo; import org.junit.Test; public class DEFTest { private DEF def; @Before public void setup() { def = new DEF(); } @Test public void add(){ String result = def.sayHello(); assertThat(result, is(equalTo("Hello!"))); } @Test public void getList(){ List result = def.getList(); assertThat((Collection) result, is(not(empty()))); assertThat(result, contains("abstract class")); } } 

Se uma class abstrata for apropriada para sua implementação, teste (como sugerido acima) uma class concreta derivada. Suas suposições estão corretas.

Para evitar futuras confusões, esteja ciente de que esta class de testes concretos não é uma simulação, mas sim uma farsa .

Em termos estritos, uma simulação é definida pelas seguintes características:

  • Uma simulação é usada no lugar de toda e qualquer dependência da class de assunto que está sendo testada.
  • Uma simulação é uma pseudo-implementação de uma interface (você pode lembrar que, como regra geral, as dependencies devem ser declaradas como interfaces; testabilidade é uma das principais razões para isso)
  • Comportamentos dos membros da interface do mock – sejam methods ou propriedades – são fornecidos no tempo de teste (novamente, pelo uso de um framework de mocking). Dessa forma, você evita o acoplamento da implementação que está sendo testada com a implementação de suas dependencies (que devem ter seus próprios testes discretos).
Intereting Posts