Come verificare che non venga generata alcuna eccezione?


238

So che un modo per farlo sarebbe:

@Test
public void foo(){
   try{
      //execute code that you expect not to throw Exceptions.
   }
   catch(Exception e){
      fail("Should not have thrown any exception");
   }
}

C'è un modo più pulito per farlo. (Probabilmente stai usando Junit's @Rule?)


10
Si ritiene che un test JUnit abbia avuto esito negativo se genera un'eccezione diversa da un'eccezione prevista. Di solito non sono previste eccezioni.
Raedwald,

Non esiste una distinzione tra fallimento ed errore in JUnit? Il primo significa che il test ha avuto esito negativo, il secondo significa che è successo qualcosa di inaspettato.
Vituel,

Risposte:


198

Ti stai avvicinando nel modo sbagliato. Metti alla prova la tua funzionalità: se viene generata un'eccezione, il test fallirà automaticamente. Se non viene generata alcuna eccezione, i test diventeranno tutti verdi.

Ho notato che questa domanda attira di tanto in tanto interesse, quindi mi espanderò un po '.

Background per test unitari

Quando si esegue il test di unità è importante definire autonomamente ciò che si considera un'unità di lavoro. Fondamentalmente: un'estrazione del tuo codebase che può o meno includere più metodi o classi che rappresenta un singolo pezzo di funzionalità.

Oppure, come definito in The art of Unit Testing, 2nd Edition di Roy Osherove , pagina 11:

Un unit test è un pezzo di codice automatizzato che richiama l'unità di lavoro in fase di test, quindi verifica alcune ipotesi su un singolo risultato finale di tale unità. Un test unitario viene quasi sempre scritto utilizzando un framework di test unitari. Può essere scritto facilmente e funziona rapidamente. È affidabile, leggibile e mantenibile. È coerente nei risultati finché il codice di produzione non è cambiato.

Ciò che è importante rendersi conto è che un'unità di lavoro di solito non è solo un metodo, ma al livello di base è un metodo e successivamente viene incapsulato da un'altra unità di lavoro.

inserisci qui la descrizione dell'immagine

Idealmente dovresti avere un metodo di prova per ogni unità di lavoro separata in modo da poter sempre vedere immediatamente dove stanno andando le cose. In questo esempio c'è un metodo di base chiamato getUserById()che restituirà un utente e ci sono un totale di 3 unità di lavoro.

La prima unità di lavoro dovrebbe verificare se viene restituito un utente valido in caso di input valido e non valido.
Eventuali eccezioni che vengono generate dall'origine dati devono essere gestite qui: se non è presente alcun utente, dovrebbe esserci un test che dimostri che viene generata un'eccezione quando l'utente non può essere trovato. Un esempio di ciò potrebbe essere quello IllegalArgumentExceptioncatturato @Test(expected = IllegalArgumentException.class)dall'annotazione.

Dopo aver gestito tutti i casi d'uso per questa unità di lavoro di base, salirai di livello. Qui fai esattamente lo stesso, ma gestisci solo le eccezioni che provengono dal livello proprio sotto quello attuale. Ciò mantiene il codice di test ben strutturato e consente di scorrere rapidamente l'architettura per scoprire dove le cose vanno male, invece di dover saltare dappertutto.

Gestire un input valido e difettoso di un test

A questo punto dovrebbe essere chiaro come gestiremo queste eccezioni. Esistono 2 tipi di input: input valido e input difettoso (l'input è valido in senso stretto, ma non è corretto).

Quando lavori con input validi stai impostando l'aspettativa implicita che qualunque test tu scriva, funzionerà.

Tale metodo una chiamata può apparire come segue: existingUserById_ShouldReturn_UserObject. Se questo metodo fallisce (es: viene generata un'eccezione) allora sai che qualcosa è andato storto e puoi iniziare a scavare.

Aggiungendo un altro test ( nonExistingUserById_ShouldThrow_IllegalArgumentException) che utilizza l' input errato e si aspetta un'eccezione, puoi vedere se il tuo metodo fa quello che dovrebbe fare con un input errato.

TL; DR

Stavi provando a fare due cose nel tuo test: controlla se l'input è valido e difettoso. Dividendo questo in due metodi che fanno ciascuno una cosa, avrai test molto più chiari e una visione molto migliore di dove le cose vanno male.

Tenendo presente l'unità di lavoro a più livelli, puoi anche ridurre la quantità di test necessari per un livello superiore nella gerarchia perché non devi tenere conto di tutto ciò che potrebbe essere andato storto nei livelli inferiori: il i livelli inferiori a quello attuale sono una garanzia virtuale del funzionamento delle dipendenze e, se qualcosa va storto, si trova nel livello corrente (supponendo che i livelli inferiori non generino errori da soli).


2
Il fatto è che sto cercando di TDD e uno dei collaboratori che utilizzo sta gettando un'eccezione. Quindi devo provare il fatto che sto consumando l'eccezione lanciata dal collaboratore
Ankit Dhingra,

6
Stai dicendo che la tua funzionalità dipende dalla gestione di un'eccezione? Questo è un odore di codice: ci sono eccezioni per farti capire elegantemente i problemi; non sono usati per il controllo del flusso. Se si desidera verificare uno scenario in cui deve essere generata un'eccezione, è necessario utilizzare l' expectedannotazione. Se vuoi testare uno scenario in cui il tuo codice fallisce e vuoi vedere se l'errore è gestito correttamente: usa expectede forse usa gli assert per determinare se è stato risolto.
Jeroen Vannevel,

Il fatto è che non riesco a recuperare dall'eccezione che si è verificata nel collaboratore e tutto ciò che faccio è semplicemente registrare il problema utilizzando un log.debug ("Messaggio di errore"). Quindi non ci sono effetti collaterali che si verificano come parte del blocco catch che posso eventualmente affermare.
Ankit Dhingra,

5
@JeroenVannevel è perfettamente valido verificare che una situazione di errore che provochi un'eccezione sia gestita correttamente.
Thorbjørn Ravn Andersen,

1
@dpk sì, puoi. Aggiungi throws IllegalArgumentExceptional tuo test. Quello che vuoi alla fine è che il tuo test diventi rosso se c'è un'eccezione. Bene, indovina un po '? Non è necessario scrivere fail(). Come ha scritto @Jeroen Vannevel: "se viene generata un'eccezione, il test fallirà automaticamente."
Amedee Van Gasse,

132

Mi sono imbattuto in questo a causa della regola di SonarQube "calamari: S2699": "Aggiungi almeno un'asserzione a questo caso di test."

Ho fatto un semplice test il cui unico obiettivo era passare senza gettare eccezioni.

Considera questo semplice codice:

public class Printer {

    public static void printLine(final String line) {
        System.out.println(line);
    }
}

Che tipo di asserzione può essere aggiunta per testare questo metodo? Certo, puoi provarci, ma è solo un eccesso di codice.

La soluzione proviene dalla stessa JUnit.

Nel caso in cui non venga generata alcuna eccezione e si desideri illustrare esplicitamente questo comportamento, è sufficiente aggiungere expectedcome nell'esempio seguente:

@Test(expected = Test.None.class /* no exception expected */)
public void test_printLine() {
    Printer.printLine("line");
}

Test.None.class è il valore predefinito per il valore atteso.


30
Penso che questa sia la risposta migliore. La risposta accettata è ottima e l'autore ha ragione a sottolineare l'odore del codice. Tuttavia non ha veramente risposto alla domanda specifica.
HellishHeat,

4
è interessante notare che il valore predefinito per l'atteso è Nessuno, quindi farebbe solo annotare il metodo con @Test.
oziomajnr,

42

Con le asserzioni fluide AssertJ 3.7.0 :

Assertions.assertThatCode(() -> toTest.method())
    .doesNotThrowAnyException();

1
@emeraldhieu La domanda non riguarda nemmeno assertJ, quindi come fa questo "direttamente" a rispondere alla domanda di OP?
MauriceNino,

41

JUnit 5 (Jupiter) fornisce tre funzioni per verificare l'assenza / presenza di eccezioni:

assertAll​()

Afferma che tutto ciò che viene fornito executables
  non genera eccezioni.

assertDoesNotThrow​()

Afferma che l'esecuzione del
  fornito executable/ supplier
non genera alcun tipo di eccezione .

  Questa funzione è disponibile
  da JUnit 5.2.0 (29 aprile 2018).

assertThrows​()

Asserisce che l'esecuzione del fornito executable
genera un'eccezione expectedType
  e restituisce l' eccezione .

Esempio

package test.mycompany.myapp.mymodule;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class MyClassTest {

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw() {
        String myString = "this string has been constructed";
        assertAll(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw__junit_v520() {
        String myString = "this string has been constructed";
        assertDoesNotThrow(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_is_null_then_myFunction_throws_IllegalArgumentException() {
        String myString = null;
        assertThrows(
            IllegalArgumentException.class,
            () -> MyClass.myFunction(myString));
    }

}

1
Questa è la migliore risposta ora. Altre risposte stanno discutendo le versioni precedenti di JUnit
Tejesh Raut il

29

Java 8 rende tutto molto più semplice e Kotlin / Scala lo sono doppiamente.

Possiamo scrivere una piccola classe di utilità

class MyAssertions{
  public static void assertDoesNotThrow(FailingRunnable action){
    try{
      action.run()
    }
    catch(Exception ex){
      throw new Error("expected action not to throw, but it did!", ex)
    }
  }
}

@FunctionalInterface interface FailingRunnable { void run() throws Exception }

e quindi il tuo codice diventa semplicemente:

@Test
public void foo(){
  MyAssertions.assertDoesNotThrow(() -> {
    //execute code that you expect not to throw Exceptions.
  }
}

Se non hai accesso a Java-8, utilizzerei una funzione java dolorosamente vecchia: blocchi di codice aribitrary e un semplice commento

//setup
Component component = new Component();

//act
configure(component);

//assert 
/*assert does not throw*/{
  component.doSomething();
}

E infine, con Kotlin, una lingua di cui mi sono innamorato di recente:

fun (() -> Any?).shouldNotThrow() 
    = try { invoke() } catch (ex : Exception){ throw Error("expected not to throw!", ex) }

@Test fun `when foo happens should not throw`(){

  //...

  { /*code that shouldn't throw*/ }.shouldNotThrow()
}

Sebbene ci sia un sacco di spazio per giocherellare con il modo in cui vuoi esprimere questo, sono sempre stato un fan delle asserzioni fluide .


per quanto riguarda

Ti stai avvicinando nel modo sbagliato. Metti alla prova la tua funzionalità: se viene generata un'eccezione, il test fallirà automaticamente. Se non viene generata alcuna eccezione, i test diventeranno tutti verdi.

Ciò è corretto in linea di principio, ma in conclusione errato.

Java consente eccezioni per il flusso di controllo. Ciò viene eseguito dal runtime JRE stesso nelle API come Double.parseDoubletramite a NumberFormatExceptione Paths.gettramite a InvalidPathException.

Dato che hai scritto un componente che convalida le stringhe numeriche per Double.ParseDouble, magari usando un Regex, forse un parser scritto a mano o forse qualcosa che incorpora alcune altre regole di dominio che limita l'intervallo di un doppio a qualcosa di specifico, il modo migliore per testarlo componente? Penso che un ovvio test sarebbe quello di affermare che, quando viene analizzata la stringa risultante, non viene generata alcuna eccezione. Vorrei scrivere quel test utilizzando il sopra assertDoesNotThrowo il /*comment*/{code}blocco. Qualcosa di simile a

@Test public void given_validator_accepts_string_result_should_be_interpretable_by_doubleParseDouble(){
  //setup
  String input = "12.34E+26" //a string double with domain significance

  //act
  boolean isValid = component.validate(input)

  //assert -- using the library 'assertJ', my personal favourite 
  assertThat(isValid).describedAs(input + " was considered valid by component").isTrue();
  assertDoesNotThrow(() -> Double.parseDouble(input));
}

Vorrei anche incoraggiarvi a parametrizzare questo test inputsull'uso Theorieso in Parameterizedmodo da poter riutilizzare più facilmente questo test per altri input. In alternativa, se vuoi diventare esotico, potresti scegliere uno strumento di generazione di test (e questo ). TestNG ha un supporto migliore per i test con parametri.

Ciò che trovo particolarmente spiacevole è la raccomandazione di utilizzare @Test(expectedException=IllegalArgumentException.class), questa eccezione è pericolosamente ampia . Se il tuo codice cambia in modo tale che il componente sotto il costruttore del test ha if(constructorArgument <= 0) throw IllegalArgumentException(), e il tuo test ha fornito 0 per quell'argomento perché era conveniente - e questo è molto comune, perché la buona generazione di dati di test è un problema sorprendentemente difficile -, quindi il tuo test sarà una barra verde anche se non verifica nulla. Tale test è peggio che inutile.


2
(per quanto riguarda l'utilizzo dell'eccezione prevista) A partire da JUnit 4.13, è possibile utilizzare Assert.assertThrowsper verificare che alcuni codici generino un'eccezione.
MageWind

22

Se sei abbastanza sfortunato da rilevare tutti gli errori nel tuo codice. Puoi fare stupidamente

class DumpTest {
    Exception ex;
    @Test
    public void testWhatEver() {
        try {
            thisShouldThrowError();
        } catch (Exception e) {
            ex = e;
        }
        assertEquals(null,ex);
    }
}

1
Solo un piccolo suggerimento, Exception exdovrebbe essere = null;prima di poterlo testare.
Denees,

4
Questa non è un'ottima soluzione. Se viene emesso il metodo che non deve generare un'eccezione, non verrà visualizzato un messaggio di errore utile. Basta chiamare il metodo che non dovrebbe generare un'eccezione e testare il valore restituito (o gli effetti collaterali, come la registrazione dell'eccezione). Se in seguito inaspettatamente genera un'eccezione, il test fallirà.
NamshubWriter

3
O semplicemente metti Assert.fail () nella cattura, IMO più facile e più bello.
isaac.hazan,

Si sono d'accordo con te. Un altro modo è aggiungere un'annotazione in cima al metodo @Test (previsto = InvalidRequestException.class)
Ben Tennyson,

L'ortografia è confusa: thisShouldThroughError -> thisShouldThrowError .
Oscar Bravo,


7

Anche se questo post ha 6 anni ormai, molto è cambiato nel mondo Junit. Con Junit5, ora puoi usare

org.junit.jupiter.api.Assertions.assertDoesNotThrow()

Ex:

public void thisMethodDoesNotThrowException(){
   System.out.println("Hello There");
}

@Test
public void test_thisMethodDoesNotThrowException(){
  org.junit.jupiter.api.Assertions.assertDoesNotThrow(
      ()-> thisMethodDoesNotThrowException()
    );
}

Spero che possa aiutare le persone che stanno utilizzando la versione più recente di Junit5


Vorrei che ci fosse un modo per specificare una classe di eccezione concreta qui. Devo fare questo all'interno Awaitilitys' untilAsserted(ThrowingRunnable assertion). Il sistema in prova sta attualmente generando un'eccezione specifica sul ThrowingRunnable che fornisco, ma voglio concedergli un po 'di tempo fino a quando non smette di farlo. Tuttavia, se si generasse un'eccezione diversa, vorrei che il test fallisse immediatamente.
Ubeogesh,

1

Se si desidera verificare se il target di test consuma l'eccezione. Lascia il test come (finto collaboratore usando jMock2):

@Test
public void consumesAndLogsExceptions() throws Exception {

    context.checking(new Expectations() {
        {
            oneOf(collaborator).doSth();
            will(throwException(new NullPointerException()));
        }
    });

    target.doSth();
 }

Il test passerebbe se il tuo target consuma l'eccezione generata, altrimenti il ​​test fallirebbe.

Se vuoi testare la tua logica di consumo delle eccezioni, le cose diventano più complesse. Suggerisco di delegare il consumo a un collaboratore che potrebbe essere deriso. Pertanto il test potrebbe essere:

@Test
public void consumesAndLogsExceptions() throws Exception {
    Exception e = new NullPointerException();
    context.checking(new Expectations() {
        {
            allowing(collaborator).doSth();
            will(throwException(e));

            oneOf(consumer).consume(e);
        }
    });

    target.doSth();
 }

Ma a volte è sovra-progettato se si desidera solo registrarlo. In questo caso, questo articolo ( http://java.dzone.com/articles/monitoring-declarative-transac , http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there -an-easy-way / ) può essere d'aiuto se insisti in questo caso.


1

Usa assertNull (...)

@Test
public void foo() {
    try {
        //execute code that you expect not to throw Exceptions.
    } catch (Exception e){
        assertNull(e);
    }
}

6
Direi che questo è fuorviante. Il blocco catch non viene mai raggiunto, quindi assertNullnon viene nemmeno eseguito. Tuttavia, il lettore rapido ha l'impressione che sia stata fatta un'affermazione che verifica davvero il caso di non lancio. In altre parole: se viene raggiunto il blocco catch, l'eccezione è sempre non nulla, quindi può essere sostituita da una semplice fail.
Andreas,

fuorviante davvero, ..... ma aspetta, ... oh vedo ... assertNull(e)segnalerà il test come fallito, come dichiarato enon può essere nullnel catchblocco ... Mike questa è solo una programmazione strana: - /. .. si almeno usare fail()come dice Andreas
Julien

1

Puoi aspettarti che l'eccezione non venga generata creando una regola.

@Rule
public ExpectedException expectedException = ExpectedException.none();

Le ExpectedExceptions vengono utilizzate per affermare le eccezioni generate. Il codice fornito è solo per inizializzare la regola in modo da poter aggiungere i requisiti per le asserzioni. Questo codice stesso non aggiunge alcun valore. Il javadoc afferma anche questo: "/ ** * Restituisce una {@linkplain TestRule rule} che non si aspetta che venga generata alcuna eccezione * (identico al comportamento senza questa regola). * /" Quindi avrà lo stesso esatto risultato che senza di essa .
Pim Hazebroek,

Sono d'accordo con te e non lo userei in questo modo, ma è possibile affermare che non è stata emessa alcuna eccezione. Se il test passa, dovrebbe esserci abbastanza per dire che l'eccezione non è stata lanciata, ma d'altra parte se c'è una domanda deve esserne necessario. E raramente, ma a volte è bene averlo visibile. Cosa succede se il codice e le circostanze cambiano e non abbiamo test per alcuni casi limite particolari?
LazerBanana,

Sono curioso di vedere come lo affermeresti con un'eccezione prevista. E sì, se i requisiti cambiano e non hai test per una custodia per bordi particolare, sei avvitato ;-) copri sempre tutte le custodie angolari.
Pim Hazebroek,

cosa intendi? non te lo asserisci, te lo aspetti. In questo caso, non ti aspetti alcuna eccezione. Non sono sicuro di cosa stai parlando.
LazerBanana,

0

Questo potrebbe non essere il modo migliore, ma garantisce sicuramente che non venga generata un'eccezione dal blocco di codice che viene testato.

import org.assertj.core.api.Assertions;
import org.junit.Test;

public class AssertionExample {

    @Test
    public void testNoException(){
        assertNoException();
    }    

    private void assertException(){
        Assertions.assertThatThrownBy(this::doNotThrowException).isInstanceOf(Exception.class);
    }

    private void assertNoException(){
        Assertions.assertThatThrownBy(() -> assertException()).isInstanceOf(AssertionError.class);
    }

    private void doNotThrowException(){
        //This method will never throw exception
    }
}

0

È possibile farlo utilizzando una @Rule e quindi chiamare il metodo reportMissingExceptionWithMessage come mostrato di seguito: questo è il codice Scala.

inserisci qui la descrizione dell'immagine


1
private val? Che lingua è? Chiaramente non Java; p E per favore, non fornire il codice come screenshot, non è il benvenuto.
Andremoniy,

Vedo che hai menzionato che è Scala, ma dire che può essere facilmente fatto in Java non è un argomento forte, mi dispiace
Andremoniy,

Ho rimosso la parte che ti dava fastidio. Proverò a sostituire anche l'immagine. Non ho ancora capito come aggiungere il codice ..
Crenguta S

-1

Quanto segue non supera il test per tutte le eccezioni, selezionate o deselezionate:

@Test
public void testMyCode() {

    try {
        runMyTestCode();
    } catch (Throwable t) {
        throw new Error("fail!");
    }
}

-1

Puoi creare qualsiasi tipo di asserzioni personali basate sulle asserzioni di junit:

static void assertDoesNotThrow(Executable executable) {
    assertDoesNotThrow(executable, "must not throw");
}
static void assertDoesNotThrow(Executable executable, String message) {
    try {
        executable.execute();
    } catch (Throwable err) {
        fail(message);
    }
}

E prova:

//the following will succeed
assertDoesNotThrow(()->methodMustNotThrow(1));
assertDoesNotThrow(()->methodMustNotThrow(1), "fail with specific message: facepalm");
//the following will fail
assertDoesNotThrow(()->methodMustNotThrow(2));
assertDoesNotThrow(()-> {throw new Exception("Hello world");}, "Fail: must not trow");

In generale, esiste la possibilità di fallire istantaneamente ("bla bla bla") il test in qualsiasi scenario, in qualsiasi luogo in cui abbia senso. Ad esempio, usalo in un blocco try / catch per fallire se qualcosa viene lanciato nel test case:

try{methodMustNotThrow(1);}catch(Throwable e){fail("must not throw");}
//or
try{methodMustNotThrow(1);}catch(Throwable e){Assertions.fail("must not throw");}

Questo è l'esempio del metodo che testiamo, supponendo che abbiamo un tale metodo che non deve fallire in circostanze specifiche, ma può fallire:

void methodMustNotThrow(int x) throws Exception{
    if (x == 1) return;
    throw new Exception();
}

Il metodo sopra è un semplice esempio. Ma questo funziona per situazioni complesse, in cui il fallimento non è così evidente. Ci sono le importazioni:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.*;

Esistono opzioni abbastanza migliori per verificare che non siano state lanciate asserzioni che non comportano la creazione di codice personalizzato. @Rule è uno di questi
Vargan,

@Vargan Ho indicato il metodo per creare le tue asserzioni nel modo in cui è stato progettato da JUnit in particolare allo scopo di creare le tue asserzioni. JUnit prevede che, in base alla progettazione, in particolare a tale scopo, creare le proprie regole estenda il comportamento di JUnit con affermazioni che non sono ancora state implementate. Perché non tutto è implementato in questo mondo Queste asserzioni funzionano in modo identico a quelle di JUnit in termini di passaggio o fallimento e di segnalazione di guasti.
Armagedescu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.