Pergunta

Eu gostaria de testar uma classe abstrata. Claro, eu posso escrever manualmente um simulada que herda da classe.

Posso fazer isso usando um quadro zombando (estou usando Mockito) em vez de mão-elaborar a minha simulação? Como?

Foi útil?

Solução

A seguir vamos sugestão é você testar classes abstratas, sem criar uma subclasse "real." - a Mock é a subclasse

uso Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS), em seguida, simular nenhum métodos abstratos que são invocados.

Exemplo:

public abstract class My {
  public Result methodUnderTest() { ... }
  protected abstract void methodIDontCareAbout();
}

public class MyTest {
    @Test
    public void shouldFailOnNullIdentifiers() {
        My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

Nota:. A beleza desta solução é que você não Have para implementar os métodos abstratos, contanto que eles nunca são invocados

Na minha opinião honesta, esta é mais puro do que usando um espião, uma vez que um espião requer uma instância, o que significa que você tem que criar uma subclasse Instantiatable de sua classe abstrata.

Outras dicas

Se você só precisa testar alguns dos métodos concretos sem tocar em nenhum dos resumos, você pode usar CALLS_REAL_METHODS (veja Morten resposta ), mas se o método concreto sob teste chama alguns dos resumos, ou métodos de interface não implementadas, isto não vai funcionar - Mockito vai reclamar "não pode chamar método real na interface Java ".

(Sim, é um projeto ruim, mas algumas estruturas, por exemplo, tapeçaria 4, espécie de força-lo em você.)

A solução é inverter esta abordagem - utilizar o comportamento simulado normal (ou seja, tudo é ridicularizado / apagou) e uso doCallRealMethod() explicitamente chamar o método concreto sob teste. Por exemplo.

public abstract class MyClass {
    @SomeDependencyInjectionOrSomething
    public abstract MyDependency getDependency();

    public void myMethod() {
        MyDependency dep = getDependency();
        dep.doSomething();
    }
}

public class MyClassTest {
    @Test
    public void myMethodDoesSomethingWithDependency() {
        MyDependency theDependency = mock(MyDependency.class);

        MyClass myInstance = mock(MyClass.class);

        // can't do this with CALLS_REAL_METHODS
        when(myInstance.getDependency()).thenReturn(theDependency);

        doCallRealMethod().when(myInstance).myMethod();
        myInstance.myMethod();

        verify(theDependency, times(1)).doSomething();
    }
}

Atualizado para adicionar:

Para métodos não-vazio, você vai precisar usar thenCallRealMethod() em vez disso, por exemplo:

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();

Caso contrário Mockito vai reclamar "stubbing Unfinished detectado."

Você pode conseguir isso usando um espião (utilize a última versão do Mockito 1.8+ embora).

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));

quadros zombaria são projetados para tornar mais fácil para zombar fora dependências da classe que você está testando. Quando você usa um quadro zombando zombar uma classe, a maioria dos frameworks criar dinamicamente uma subclasse, e substituir a implementação do método com código para detectar quando um método é chamado e retornar um valor falso.

Ao testar uma classe abstrata, você quer executar os métodos não-abstratos do assunto em Test (SUT), então um quadro de zombaria não é o que você quer.

Parte da confusão é que a resposta à pergunta é ligada ao dito mão-craft um mock que se estende desde a sua classe abstrata. Eu não chamaria essa classe a um mock. A simulação é uma classe que é usado como um substituto para uma dependência, é programado com as expectativas, e pode ser consultado para ver se essas expectativas sejam atendidas.

Em vez disso, sugiro a definição de uma subclasse não-abstrata de sua classe resumo em seu teste. Se isso resulta em muito código, que isso pode ser um sinal de que sua classe é difícil estender.

Uma solução alternativa seria fazer o seu caso de teste em si abstrato, com um método abstrato para criar o SUT (em outras palavras, o caso de teste usaria o Template Method padrão de design).

Tente usar uma resposta personalizada.

Por exemplo:

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class CustomAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> answer = null;

        if (isAbstract(invocation.getMethod().getModifiers())) {

            answer = Mockito.RETURNS_DEFAULTS;

        } else {

            answer = Mockito.CALLS_REAL_METHODS;
        }

        return answer.answer(invocation);
    }
}

Ele irá retornar o mock para métodos abstratos e irá chamar o método real para métodos concretos.

O que realmente me faz sentir mal sobre zombando classes abstratas é o fato de que, nem o construtor padrão YourAbstractClass () é chamado (faltando super () em simulação), nem parece que haja qualquer forma Mockito para o padrão propriedades de inicialização simulada ( por exemplo Lista propriedades com ArrayList vazio ou ListaLigada).

Meu classe abstrata (basicamente o código-fonte da classe é gerada) não fornecer uma injeção de dependência setter para os elementos da lista, nem um construtor onde ele inicializa os elementos da lista (que eu tentei adicionar manualmente).

atributos Somente a classe de inicialização uso padrão: dep1 private List = new ArrayList; DEP2 private List = new ArrayList

Portanto, não há maneira de zombar uma classe abstrata sem usar uma implementação objeto real (por exemplo, definição de classe interna na classe de teste de unidade, substituindo métodos abstratos) e espiar o objeto real (que faz a inicialização do campo apropriado).

É uma pena que só PowerMock ajudaria aqui ainda mais.

Assumindo as suas classes de teste estão no mesmo pacote (sob uma raiz de origem diferente) como suas classes em teste você pode simplesmente criar o mock:

YourClass yourObject = mock(YourClass.class);

e chamar os métodos que pretende testar tal como faria qualquer outro método.

Você precisa fornecer expectativas para cada método que é chamado com a expectativa de quaisquer métodos concretos de chamar o método de super -. Não sei como você faria isso com Mockito, mas eu acredito que é possível com EasyMock

Tudo isso está fazendo é criar uma instância concreta de YouClass e poupando-lhe o esforço de fornecer implementações vazias de cada método abstrato.

Como um aparte, muitas vezes eu achar que é útil para implementar a classe abstrata no meu teste, onde serve como um exemplo de implementação que eu teste através de sua interface pública, embora isso não depende da funcionalidade fornecida pela classe abstrata.

Você pode estender a classe abstrata com uma classe anônima em seu teste. Por exemplo (usando JUnit4):

private AbstractClassName classToTest;

@Before
public void preTestSetup()
{
    classToTest = new AbstractClassName() { };
}

// Test the AbstractClassName methods.

Você pode instanciar uma classe anônima, injetar suas simulações e testar essa classe.

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    @Mock
    MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {

            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

Tenha em mente que a visibilidade deve ser protected para o myDependencyService propriedade do ClassUnderTest classe abstrata.

Whitebox.invokeMethod (..) pode ser útil neste caso.

Mockito permite zombando classes abstratas por meio da anotação @Mock:

public abstract class My {

    public abstract boolean myAbstractMethod();

    public void myNonAbstractMethod() {
        // ...
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock(answer = Answers.CALLS_REAL_METHODS)
    private My my;

    @Test
    private void shouldPass() {
        BDDMockito.given(my.myAbstractMethod()).willReturn(true);
        my.myNonAbstractMethod();
        // ...
    }
}

A desvantagem é que ele não pode ser usado se você precisar de parâmetros do construtor.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top