2009-07-06 11 views
162

Vorrei testare una classe astratta. Certo, posso manually write a mock che eredita dalla classe.Utilizzo di Mockito per testare classi astratte

Posso farlo usando un sistema di simulazione (sto usando Mockito) invece di creare manualmente il mio simulato? Come?

+2

A partire da Mockito [1.10.12] (http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#30), Mockito supporta direttamente le classi astratte di derisione/derisione: 'SomeAbstract spy = spy (SomeAbstract.class); ' – pesche

risposta

267

Il seguente suggerimento consente di testare le classi astratte senza creare una sottoclasse "reale": la Mock è la sottoclasse.

utilizzare Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS), quindi simulare qualsiasi metodo astratto che viene invocato.

Esempio:

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: La bellezza di questa soluzione è che non hai per implementare i metodi astratti, fino a quando non vengono mai invocati.

Secondo la mia onesta opinione, questo è più ordinato che usare una spia, dal momento che una spia richiede un'istanza, il che significa che devi creare una sottoclasse di istanza della tua classe astratta.

+0

+1 per avermi aiutato a risolvere il mio problema che non era correlato alla domanda. Il CALLS_REAL_METHODS è stato utile per l'iniezione di un oggetto stub nel mio SUT. Uno stub aveva più senso di un mock poiché i valori di ritorno erano un po 'complessi. – Snekse

+10

Come indicato di seguito, questo non funziona quando la classe astratta chiama i metodi astratti per essere testata, il che è spesso il caso. –

+6

In realtà funziona quando la classe astratta chiama metodi astratti. Basta usare la sintassi doReturn o doNothing invece di Mockito.quando per eseguire lo stub dei metodi astratti e se si annullano le chiamate concrete, accertarsi che lo stubing riceva prima le chiamate astratte. –

2

Supponendo tuoi classi di test si trovano nello stesso package (sotto una radice fonte diversa) come le vostre classi in esame si può semplicemente creare il mock:

YourClass yourObject = mock(YourClass.class); 

e chiamare i metodi che si desidera testare proprio come si qualsiasi altro metodo.

È necessario fornire aspettative per ogni metodo chiamato con l'aspettativa su qualsiasi metodo concreto che chiama il metodo super - non sono sicuro di come lo si farebbe con Mockito, ma credo che sia possibile con EasyMock.

Tutto ciò che sta facendo è creare un'istanza concreta di YouClass e risparmiare l'impegno di fornire implementazioni vuote di ogni metodo astratto.

Per inciso, trovo spesso utile implementare la classe astratta nel mio test, dove serve come esempio di implementazione che collaudo tramite la sua interfaccia pubblica, sebbene ciò dipenda dalla funzionalità fornita dalla classe astratta.

+3

Ma usare la simulazione non testerà i metodi concreti di YourClass, o sbaglio? Questo non è quello che cerco. – ripper234

+1

Questo è corretto, il precedente non funzionerà se si desidera richiamare i metodi concreti nella classe astratta. –

+0

Mi scuso, modifico il bit sull'aspettativa, che sono richiesti per ogni metodo che chiamate non solo quelli astratti. –

13

I framework di simulazione sono progettati per semplificare la derisione delle dipendenze della classe che si sta testando. Quando si utilizza un framework di simulazione per deridere una classe, la maggior parte dei framework crea dinamicamente una sottoclasse e sostituisce l'implementazione del metodo con il codice per rilevare quando viene chiamato un metodo e restituire un valore falso.

Durante il test di una classe astratta, si desidera eseguire i metodi non astratti del soggetto sotto test (SUT), quindi una struttura di derisione non è ciò che si desidera.

Parte della confusione è che la risposta alla domanda che hai collegato diceva di creare una finzione che si estendesse dalla tua classe astratta. Non definirei una classe del genere una finta. Una simulazione è una classe che viene utilizzata come sostituto di una dipendenza, è programmata con aspettative e può essere interrogata per verificare se tali aspettative sono soddisfatte.

Invece, suggerisco di definire una sottoclasse non astratta della classe astratta nel test. Se ciò comporta troppo codice, potrebbe essere un segnale che la tua classe è difficile da estendere.

Una soluzione alternativa sarebbe quella di rendere astratto il caso di test, con un metodo astratto per creare il SUT (in altre parole, il caso di test userebbe il modello di progettazione Template Method).

6

Provare a utilizzare una risposta personalizzata.

Ad esempio:

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); 
    } 
} 

verrà restituito il finto per i metodi astratti e chiamerà il metodo vero e proprio per i metodi concreti.

15

È possibile ottenere ciò utilizzando una spia (utilizzare però l'ultima versione di Mockito 1.8+).

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())); 
5

Quello che mi fa davvero sentire in colpa per beffardo classi astratte è il fatto che né il costruttore di default YourAbstractClass() viene chiamata (manca super() in finto), né sembra esserci alcun modo Mockito a default inizializza le proprietà fittizie (ad es. Elenca le proprietà con ArrayList o LinkedList vuoti).

La mia classe astratta (in pratica il codice sorgente della classe viene generato) NON fornisce un'iniezione di dipendenza per gli elementi dell'elenco, né un costruttore dove inizializza gli elementi dell'elenco (che ho tentato di aggiungere manualmente).

Solo gli attributi di classe utilizzano l'inizializzazione predefinita: Elenco privato dep1 = new ArrayList; Lista privato dep2 = new ArrayList

Quindi non v'è alcun modo per prendere in giro una classe astratta senza utilizzare un'implementazione oggetto reale (ad esempio definizione di classe interna in classe unit test, imperative metodi astratti) e spiare l'oggetto reale (che fa una corretta inizializzazione del campo).

Peccato che solo PowerMock possa aiutare ulteriormente.

66

Se avete solo bisogno di testare alcuni dei metodi concreti senza toccare nessuna delle abstracts, è possibile utilizzare CALLS_REAL_METHODS (vedi Morten's answer), ma se il metodo concreto in prova chiama alcuni degli abstract o metodi di interfaccia non implementati, questo non funzionerà - Mockito si lamenterà "Impossibile chiamare il metodo reale sull'interfaccia java".

(Sì, è un disegno schifoso, ma alcuni quadri, per esempio Tapestry 4, tipo di forza su di voi.)

La soluzione è quello di invertire questo approccio - utilizzare il comportamento finto ordinaria (vale a dire, tutto è deriso/stoppato) e utilizzare doCallRealMethod() per richiamare esplicitamente il metodo concreto sotto test. Per esempio.

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(); 
    } 
} 

aggiornato per aggiungere:

Per i metodi non-vuoto, è necessario utilizzare thenCallRealMethod() invece, ad esempio:

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

Altrimenti Mockito si lamenta "Unfinished stub rilevato"

+6

Questo funzionerà in alcuni casi, tuttavia Mockito non chiama il costruttore della classe astratta sottostante con questo metodo. Ciò potrebbe causare il "metodo reale" non riuscire a causa di uno scenario imprevisto in fase di creazione. Pertanto, questo metodo non funzionerà in tutti i casi. –

+2

Sì, non puoi contare sullo stato dell'oggetto, solo il codice nel metodo che viene chiamato. –

1

è possibile estendere la classe astratta con una classe anonima nel test Per esempio (usando Junit 4):.

private AbstractClassName classToTest; 

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

// Test the AbstractClassName methods. 
0

è possibile creare un'istanza di una classe anonima, iniettare la deride e quindi verificare che la 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); 
    } 
} 

Tenete a mente che la visibilità deve essere protected per la proprietà myDependencyService della classe astratta ClassUnderTest.