Pergunta

Eu queria saber como testar unidade de classes abstratas e classes que estendem classes abstratas.

Devo testar a classe abstrata, estendendo-o, apagando os métodos abstratos, e, em seguida, testar todos os métodos concretos? Só então testar os métodos que substituam, e testar os métodos abstratos nos testes de unidade para objetos que se estendem minha classe abstrata?

Eu deveria ter um caso de teste abstrato que pode ser usado para testar os métodos da classe abstrata, e estender esta classe no meu caso de teste para objetos que se estendem a classe abstrata?

Note que a minha classe abstrata tem alguns métodos concretos.

Foi útil?

Solução

Escrever um objeto Mock e usá-los apenas para testar. Eles geralmente são muito muito muito mínima (herdada da classe abstrata) e não more.Then, em sua unidade de teste você pode chamar o método abstrato que você deseja testar.

Você deve testar a classe abstrata que contêm alguma lógica, como todas as outras classes que você tem.

Outras dicas

Há duas maneiras em que as classes base abstratas são usados.

  1. Você está especializando seu objeto abstrato, mas todos os clientes usarão a classe derivada através de sua interface base.

  2. Você está usando uma classe base abstrata para fator a duplicação dentro de objetos 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ê tem a primeira situação, então você realmente tem uma interface definida pelos métodos virtuais na classe abstrata que suas classes derivadas estão a implementar.

Você deve considerar fazer isso de uma interface real, mudar sua classe abstrata para ser concreto, e tomar uma instância desta interface em seu construtor. Suas classes derivadas, em seguida, tornar-se implementações desta nova interface.

IMotor

Isto significa que agora você pode testar sua classe anteriormente abstrata usando uma instância simulada da nova interface, e cada nova implementação através da interface agora público. Tudo é simples e testável.


Solução Para 2

Se você tem a segunda situação, em seguida, sua classe abstrata está trabalhando como uma classe auxiliar.

AbstractHelper

Dê uma olhada na funcionalidade que ele contém. Veja se algum pode ser empurrado para os objetos que estão sendo manipulados para minimizar esta duplicação. Se você ainda tiver qualquer coisa à esquerda, olhar para tornando-se uma classe auxiliar que sua implementação take concreto em seu construtor e remover sua classe base.

Motor Helper

Isto novamente leva a classes concretas que são simples e facilmente testável.


Como regra

Favor complexa rede de objetos simples através de uma simples rede de objetos complexos.

A chave para o código testável extensível é pequenos blocos e fiação independente.


Atualizado:? Como lidar com misturas de ambos

É possível ter uma classe base executando ambos os papéis ... ou seja: tem uma interface pública, e tem protegido métodos auxiliares. Se este for o caso, então você pode fatorar os métodos auxiliares para uma classe (cenario2) e converter a árvore de herança em um padrão de estratégia.

Se você achar que você tem alguns métodos de seus implementos classe base direta e outra são virtuais, em seguida, você ainda pode converter a árvore de herança em um padrão de estratégia, mas também gostaria de tomá-lo como um bom indicador de que as responsabilidades não estão corretamente alinhados , e pode precisar de refatoração.


Update 2: Classes abstratas como um trampolim (2014/06/12)

Eu tive uma situação no outro dia onde eu costumava abstrato, então eu gostaria de explorar o porquê.

Temos um formato padrão para os nossos arquivos de configuração. Esta ferramenta especial tem 3 arquivos de configuração todos nesse formato. Eu queria uma classe fortemente tipado para cada arquivo de configuração para, por meio de injeção de dependência, uma classe poderia pedir as definições que se preocupava.

Eu implementei isso por ter uma classe base abstrata que sabe como analisar os formatos de arquivos de configurações e classes derivadas que expuseram esses mesmos métodos, mas encapsulado a localização do arquivo de configurações.

Eu poderia ter escrito uma "SettingsFileParser" que as 3 classes embrulhado, e depois delegada até a classe base para expor os métodos de acesso de dados. Eu não escolhi fazer este ainda , uma vez que levaria a 3 classes derivadas com mais delegação código neles do que qualquer outra coisa.

No entanto ... como este evolui de código e os consumidores de cada uma destas classes de configurações se tornam mais claras. Cada usuários definiçõespedir algumas configurações e transformá-los de alguma forma (como as configurações são textos que podem envolvê-los em objetos de convertê-los para os números etc.). Quando isso acontece eu vou começar a extrair esta lógica em métodos de manipulação de dados e empurrá-los de volta para as classes de configurações rigidez. Isto levará a uma interface de nível superior para cada conjunto de configurações, isto é, eventualmente, não está mais ciente de que está lidando com 'Configurações'.

Neste ponto, as classes configurações de rigidez não será mais necessário os métodos "getter" que expõem a implementação 'configurações' subjacente.

Nesse ponto eu já não querem a sua interface pública para incluir as configurações métodos de acesso; por isso vou mudar esta classe para encapsular uma classe de configurações analisador em vez de tirar dela.

A classe abstrata é, portanto: uma maneira de me para evitar código delegação no momento, e um marcador no código para me lembrar de alterar o design mais tarde. Eu nunca pode chegar a ele, para que ele possa viver um bom tempo ... apenas o código pode dizer.

Eu acho que isso é verdade com qualquer regra ... como "há métodos estáticos" ou "não métodos privados". Eles indicam um cheiro no código ... e isso é bom. Ele mantém você olhar para a abstração que você perdeu ... e permite-lhe continuar a fornecer o valor para o seu cliente no tempo médio.

Eu imagino regras como este definindo uma paisagem, onde o código sustentável vive nos vales. Como você adicionar um novo comportamento, é como pouso chuva em seu código. Inicialmente você colocá-lo onde quer que ele cair .. então você refatorar para permitir que as forças de um bom design para empurrar o comportamento ao redor até que tudo acaba nos vales.

O que eu faço para classes abstratas e interfaces é o seguinte: eu escrevo um teste, que utiliza o objeto como ele é concreto. Mas a variável do tipo X (X é a classe abstrata) não está definido no teste. Este teste de classe não é adicionada ao teste-suite, mas subclasses dela, que têm uma configuração de método que definir a variável para uma implementação concreta de X. Dessa forma, eu não duplicar o código de teste. As subclasses do teste não utilizado pode adicionar mais métodos de ensaio, se necessário.

Para fazer um teste de unidade especificamente sobre a classe abstrata, você deve derivar-lo para testar propósito, base.method test () resultados e comportamento desejado quando herdar.

Você testar um método chamando-o de modo testar uma classe abstrata, implementando-o ...

Se a sua classe abstrata contém funcionalidade de concreto que tem valor para o negócio, então eu normalmente irá testá-lo diretamente através da criação de um teste duplo que topos os dados abstratos, ou usando uma plataforma de simulacros para fazer isso por mim. Qual deles eu escolher depende muito se eu preciso para implementações específicas-teste de gravação dos métodos abstratos ou não.

O cenário mais comum em que eu preciso para fazer isso é quando eu estou usando o padrão Template Method , como quando estou construindo algum tipo de estrutura extensível que será usado por um 3o partido. Neste caso, a classe abstrata é o que define o algoritmo que eu quero testar, por isso faz mais sentido para testar a base abstrata de uma implementação específica.

No entanto, eu acho que é importante que estes testes deve incidir sobre as implementações concretos de lógica de negócio real só ; você não deveria teste de unidade detalhes da implementação da classe abstrata, porque você vai acabar com testes frágeis.

é uma maneira de escrever um caso de teste abstrato que corresponde a sua classe abstrata, em seguida, escrever casos de teste de concreto dessa subclasse seu caso de teste abstrato. fazer isso para cada subclasse concreta de sua classe abstrata original (ou seja, sua hierarquia caso de teste espelhos sua hierarquia de classes). consulte Test uma interface no livro recipies JUnit: http://safari.informit.com/9781932394238/ch02lev1sec6.

Também ver Testcase Superclasse nos 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ê tem elementos comuns, colocá-los em métodos auxiliares ou classes para uso de todos.

Como para testar uma classe de teste abstrato, certifique-se de perguntar o que é que você está testando. Há várias abordagens, e você deve descobrir o que funciona no seu cenário. Você está tentando testar um novo método em sua subclasse? Então, os testes só interagem com esse método. Você está testando os métodos em sua classe base? Então provavelmente tem um elemento separado apenas para essa classe, e testar cada método individualmente com tantos testes como necessário.

Este é o padrão que eu geralmente seguem quando a criação de um arnês para testar uma classe 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<object> VoidMethodFunction;
  public override void VoidMethod(object param1){
    VoidMethodFunction(param1);
  }
  public Func<object, object> MethodWithReturnFunction;
  public override object MethodWithReturn(object param1){
    return MethodWihtReturnFunction(param1);
  }
  /*,,,*/
}

Se os métodos abstratos são chamados quando eu não espere que ele, os testes falham. Ao organizar os testes, eu posso facilmente stub os métodos abstratos com lambdas que realizam afirma, lançam exceções, retornam valores diferentes, etc.

Se os métodos concretos invocar qualquer dos métodos abstratos que a estratégia não vai funcionar, e que você gostaria de testar cada comportamento classe criança separadamente. Caso contrário, estendendo-o e arrancando os métodos abstratos como você descreveu deve ser fino, mais uma vez, desde que os métodos de classe concretas abstratas são dissociados da classes filhas.

Eu suponho que você poderia querer testar a funcionalidade básica de uma classe abstrata ... Mas você provavelmente estaria melhor fora estendendo a classe sem substituir quaisquer métodos, e fazer do menor esforço de zombaria para os métodos abstratos.

Uma das principais motivações para a utilização de uma classe abstrata é permitir polimorfismo dentro de sua aplicação - ou seja: você pode substituir uma versão diferente em tempo de execução. Na verdade, esta é praticamente a mesma coisa que usar uma interface exceto a classe abstrata fornece algum encanamento comum, muitas vezes referido como um configuração padrão .

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

  1. Interação de sua classe abstrata com ele classes relacionadas . Usando uma estrutura de teste simulado é ideal para este cenário, pois mostra que a sua classe abstrata joga bem com os outros.

  2. Funcionalidade de classes derivadas . Se você tem lógica personalizada que você escreveu para as suas classes derivadas, você deve testar essas classes isoladamente.

edit: RhinoMocks é um framework de testes de simulação impressionante que pode gerar objetos mock em tempo de execução derivando dinamicamente a partir de sua classe. Esta abordagem pode poupar incontáveis ??horas de classes derivadas de codificação mão.

Em primeiro lugar, se a classe abstrata continha algum método concreto eu acho que você deve fazer isso considerou 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.

       }

   }

A seguir @ patrick-desjardins resposta, eu implementado abstrato e da classe de implementação juntamente com @Test da seguinte forma:

Classe abstrata - ABC.java

import java.util.ArrayList;
import java.util.List;

public abstract class ABC {

    abstract String sayHello();

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

As Classes abstratas não pode ser instanciado, mas eles podem ser uma subclasse , classe concreta DEF.java , é o seguinte:

public class DEF extends ABC {

    @Override
    public String sayHello() {
        return "Hello!";
    }
}

@Test classe para testar tanto abstrato, bem como método não-abstrata:

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<String> result = def.getList();
        assertThat((Collection<String>) result, is(not(empty())));
        assertThat(result, contains("abstract class"));
    }
}

Se uma classe abstrata é apropriado para a sua implementação, teste (como sugerido acima) uma classe concreta derivada. Suas suposições estão corretas.

Para evitar futuras confusões, estar ciente de que esta classe de teste de concreto não é uma simulação, mas um falso .

Em termos estritos, um simulada é definido pelas seguintes características:

  • A simulação é usado no lugar de cada dependência da classe assunto que está sendo testado.
  • A simulação é uma pseudo-implementação de uma interface (você pode recordar que, como regra geral, as dependências devem ser declaradas como interfaces; testability é uma principal razão para isso)
  • Comportamentos de membros de interface da simulação - se métodos ou propriedades - são fornecidos pelo teste do tempo (mais uma vez, pelo uso de uma plataforma de simulacros). Desta forma, você evita o acoplamento da implementação está sendo testado com a implementação de suas dependências (que todos devem ter seus próprios testes discretos).
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top