2014-12-07 14 views
5

Spesso lavoro con metodi che accettano callback e le richiamate sembrano essere difficili da testare. Consideriamo il seguente scenario, se esiste un metodo che accetta un callback con un singolo metodo (per semplicità suppongo che il metodo di test sia sincrono), il seguente testo può essere scritto solo per garantire che venga richiamato un metodo di callback:Test unità Java: il modo più semplice per verificare se viene richiamata una richiamata

@Test 
public void testMethod() { 
    final boolean[] passed = {false}; 
    method(new Callback() { 
     @Override 
     public void handle(boolean isSuccessful) { 
      passed[0] = isSuccessful; 
     } 
    }); 
    assertTrue(passed[0]); 
} 

Sembra un surrogato. Mi piacerebbe sapere: c'è un modo più elegante per testare tale codice per far apparire il codice sopra simile allo pseudo-codice qui sotto?

@Test 
public void testMethod() { 
    // nothing explicit here, implicit boolean state provided by a test-runner 
    method(new Callback() { 
     @Override 
     public void handle(boolean isSuccessful) { 
      if (isSuccessful) { 
       pass(); // not sure how it should look like: 
         // * an inherited method that sets the state to "true" 
         // * or an object with the pass method 
         // * whatever 
         // but doesn't exit testMethod(), just sets the state 
      } 
     } 
    }); 
    // nothing explicit here too: 
    // test runner might check if the state is changed to true 
    // otherwise an AssertionError might be thrown at the end of the method implicitly 
} 

Un po 'più pulito. È possibile in JUnit, TestNG o in qualsiasi altro framework di test? Grazie!


UPDATE

dispiace, mi sembra di aver fatto una domanda vaga che in realtà non incontra quello che volevo chiedere. Fondamentalmente intendevo qualsiasi codice (non necessariamente un callback) che potrebbe essere invocato se determinate condizioni sono soddisfatte solo per impostare lo stato del risultato su true. In parole povere, voglio solo eliminare l'iniziale boolean[] passed e l'assertTrue(passed[0]) finale assumendo che siano rispettivamente una sorta di prologo ed epilogo e assumendo che lo stato iniziale sia impostato su false in modo che lo pass() debba essere invocato per impostare lo stato su true . passed[0] è impostato su true, non importa da dove proviene. Ma sfortunatamente ho fatto questa domanda usando il contesto dei callback, tuttavia questa è solo un'opzione, non un requisito. Quindi il titolo della domanda non riflette ciò che volevo veramente chiedere, ma prima dell'aggiornamento sono state pubblicate alcune risposte.

risposta

8

Questo è in genere ciò che un quadro di derisione può fare per voi.

Con Mockito per esempio:

// imports ommited for brevity 
@Test 
public void callbackIsCalled() 
{ 
    final CallBack callBack = mock(CallBack.class); 
    method(callBack); 

    verify(callBack, only()).handle(any()); 
} 

Naturalmente, questo è un esempio di modalità di verifica (only()) e il valore matcher (any()). Si può fare di più ...

(esistono altri quadri di scherno, ma io personalmente trovare Mockito il più facile da usare, oltre ad essere uno dei più potenti)

+0

Grazie per la risposta! Sì, Mockito è un lib molto carino. Ad essere onesti, non ho preso in considerazione le librerie di derisione: volevo solo sbarazzarmi della bandiera surrogata e degli asserzioni finali. Con Mockito, anche 'verify' deve essere invocato correttamente. Ho solo pensato che l'ultima affermazione poteva essere implicita e "fornita" dal corridore di prova. –

+0

Bene, come ho detto, è solo un valore corrispondente; se lo desideri, puoi sostituire l'argomento con 'true', o anche con un' ArgumentCaptor '. – fge

+0

Perché il downvote? – fge

7

Dato che questo è il genere di cosa che si 'ri probabilmente bisogno in diversi luoghi, vorrei solo creare una classe di nome da utilizzare per i test:

public class FakeCallback implements Callback { 
    private boolean wasSuccessful; 
    private boolean handleCalled; 

    @Override public void handle(boolean isSuccessful) { 
     this.wasSuccessful = isSuccessful; 
     handleCalled = true; 
    } 

    // Getters for fields above 
} 

È quindi possibile usare qualcosa come:

// Arrange... 
FakeCallback callback = new FakeCallback(); 

// Act... 
method(callback); 

// Assert 
assertTrue(callback.wasHandleCalled()); 
assertTrue(callback.wasSuccessful()); 

Si potrebbe Assolutamente, invece, uso una struttura di derisione per questo, ma personalmente trovo che spesso sia più semplice creare una singola implementazione falsa piuttosto che configurare i mock ripetutamente. Entrambe le strade funzioneranno comunque.

+0

Grazie per la risposta. Sto anche pensando all'idea di sbarazzarmi degli assalti finali (anche questa è una parte della domanda probabilmente vaga), ma non sono sicuro che sia una buona idea. –

+0

@LyubomyrShaydariv: Bene, cosa stai provando a testare? Se non ti interessa se il callback è stato chiamato o meno, o quale argomento era, puoi stublo facilmente ... –

1
lista

Dare :: aggiungere come callback

Quando il compito è quello di testare un callback che è un'interfaccia funzionale che accetta un parametro (qui un valore booleano, potrebbe anche essere stringa o qualsiasi tipo casuale), sembra più conciso per redigere una lista, passare il metodo List.add(e) come callback e quindi controllare il contenuto dell'elenco:

List<Boolean> callbackArgs = new ArrayList<>(); 
methodUnderTest(callbackArgs::add); 
// assert that the callback was called exactly once and with a "true" value: 
assertEquals(Arrays.asList(true), callbackArgs); 

caso alternativa a un callback che accetta corde:

List<String> callbackArgs = new ArrayList<>(); 
methodUnderTest(callbackArgs::add); 
// assert that the callback was called twice with "foo" and "bar" values respectively: 
assertEquals(Arrays.asList("foo", "bar"), callbackArgs); 

Analogamente, una classe contatore può servire per testare una richiamata che non accetta parametri. Qui utilizzando AtomicInteger, come che sembra essere la classe unica contro-simile disponibile nelle librerie standard, - la proprietà atomicità Non è necessario qui:

AtomicInteger callbackCounter = new AtomicInteger(); 
methodUnderTest(callbackCounter::incrementAndGet); 
// assert that the callback was called 5 times: 
assertEquals(5, callbackCounter.get()); 
+0

Proprio il titolo mi ha fatto capire quanto sia facile testare i callback con questo. Ho una classe che accetta un parametro Consumer . Semplicemente passando in lista :: aggiungi come il consumatore ha reso il test molto più semplice, come ora posso solo verificare che la dimensione della lista è quello che mi aspettavo, piuttosto che fare alcune cose strane con i mock. Quindi +1. – Chewtoy

Problemi correlati