Como injeto objetos de teste quando os objetos reais são criados dinamicamente?

8

Quero tornar uma classe testável usando injeção de dependência. Mas a classe cria vários objetos em tempo de execução e passa valores diferentes para o construtor. Aqui está um exemplo simplificado:

public abstract class Validator {
    private ErrorList errors;

    public abstract void validate();

    public void addError(String text) {
        errors.add(
            new ValidationError(text));
    }

    public int getNumErrors() {
        return errors.count()
    }
}

public class AgeValidator extends Validator {
    public void validate() {
        addError("first name invalid");
        addError("last name invalid");
    }
}

(Existem muitas outras subclasses do Validator.)

Qual é a melhor maneira de mudar isso, para que eu possa injetar um objeto falso em vez de ValidationError?

Eu posso criar um AbstractValidationErrorFactory e injetar a fábrica. Isso funcionaria, mas parece que vou acabar criando toneladas de pequenas fábricas e interfaces de fábrica, para todas as dependências desse tipo. Existe uma maneira melhor?

JW01
fonte
O que você está tentando testar? Que o Validator cria ValidationErrors? Falso ValidationError não vai ajudar. Como o código está, o Validador não é muito útil porque você pode chamar validar, mas não pode obter os erros gerados.
Michael Brown
Deixei de fora, mas existem outros métodos que fornecem acesso ao objeto ErrorList. Para que eu possa testar usando esses.
JW01
Veja stackoverflow.com/questions/4859523/… para informações relacionadas
blueberryfields

Respostas:

4

A mudança mais simples para tornar sua unidade de classe testável é

  1. (extraia o código de criação em um novo método (virtual) - você já o possui)
  2. subclasse a classe testada, substituindo o método de criação para criar um objeto simulado adequado em vez do real
  3. escreva seus testes de unidade :-)

Claro, isso não é muito bom. Mas permite que você cubra a classe com testes de unidade, após o qual você pode começar a refatorar para o design ideal, sem medo de quebrar o código com alterações mais profundas.

Referência obrigatória: trabalhando efetivamente com o código legado .

Sobre esse design ideal, é difícil dizer muito, pois seu exemplo carece de detalhes e contexto. Se você nos contar mais sobre o que você pretende testar e qual é o papel da classe testada, poderemos ajudar mais.

Péter Török
fonte
Essa tem sido minha abordagem geral, mas nesse caso existem várias subclasses do Validator e há muita configuração envolvida no teste. Se eu criar uma subclasse de teste, também preciso criar uma subclasse de teste para todas as subclasses reais do Validator. Então, acabarei duplicando muito do meu trabalho de configuração. Mas talvez isso seja necessário neste caso.
JW01
2

Normalmente, a solução para esse problema é o padrão do provedor:

public class Validator {
    public Provider<ValidationError> errorProvider;

    public void addError(String text) {
        errors.add(errorProvider.get(text));
    }
}

Provedor é algo semelhante à fábrica. Nesse caso, ele retornará novo ValidationErrorsempre que getfor chamado.

Mais informações podem ser encontradas no livro Injeção de Dependências na seção 3.3.2 (Reinjeção com o padrão Provedor).

Guice , por exemplo, tem um provedor para isso. Se você quiser algo mais sofisticado, use o AssistedInject .

Anjo Fácil
fonte