it-swarm.com.de

Wie teste ich, dass keine Ausnahme geworfen wird?

Ich weiß, dass ein Weg dies zu tun wäre:

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

Gibt es eine sauberere Möglichkeit, dies zu tun? (Wahrscheinlich mit Junits @Rule?)

189
Ankit Dhingra

Du gehst das falsch an. Testen Sie einfach Ihre Funktionalität: Wenn eine Ausnahme ausgelöst wird, schlägt der Test automatisch fehl. Wenn keine Ausnahme ausgelöst wird, werden Ihre Tests alle grün angezeigt.

Ich habe bemerkt, dass diese Frage von Zeit zu Zeit Interesse findet, daher werde ich etwas näher darauf eingehen.

Hintergrund zu Unit-Tests

Wenn Sie Unit-Tests durchführen, ist es wichtig, dass Sie selbst definieren, was Sie als Arbeitseinheit betrachten. Grundsätzlich gilt: eine Extraktion Ihrer Codebasis, die mehrere Methoden oder Klassen enthalten kann oder nicht, die eine einzelne Funktionalität darstellen.

Oder, wie in Die Kunst des Einheitentests, 2. Auflage von Roy Osherove , Seite 11 definiert:

Ein Einheitentest ist ein automatisierter Code, der die zu testende Arbeitseinheit aufruft und dann einige Annahmen über ein einzelnes Endergebnis dieser Einheit überprüft. Ein Unit-Test wird fast immer mit einem Unit-Test-Framework geschrieben. Es ist einfach zu schreiben und läuft schnell. Es ist vertrauenswürdig, lesbar und wartbar. Es ist in seinen Ergebnissen konsistent, solange sich der Produktionscode nicht geändert hat.

Es ist wichtig zu erkennen, dass eine Arbeitseinheit normalerweise nicht nur eine Methode ist, sondern im Grunde eine Methode und danach eine wird von einer anderen Werkeinheit gekapselt.

enter image description here

Idealerweise sollten Sie für jede Arbeitseinheit eine Testmethode haben, damit Sie immer sofort sehen können, wo etwas schief geht. In diesem Beispiel gibt es eine grundlegende Methode namens getUserById(), die einen Benutzer zurückgibt, und es gibt insgesamt 3 Arbeitseinheiten.

In der ersten Arbeitseinheit sollte geprüft werden, ob bei einer gültigen und ungültigen Eingabe ein gültiger Benutzer zurückgegeben wird.
Alle Ausnahmen, die von der Datenquelle ausgelöst werden, müssen hier behandelt werden: Wenn kein Benutzer vorhanden ist, sollte ein Test durchgeführt werden, der zeigt, dass eine Ausnahme ausgelöst wird, wenn der Benutzer nicht gefunden werden kann. Ein Beispiel hierfür könnte das IllegalArgumentException sein, das mit der Annotation @Test(expected = IllegalArgumentException.class) abgefangen wird.

Sobald Sie alle Ihre Verwendungszwecke für diese grundlegende Arbeitseinheit behandelt haben, rücken Sie eine Ebene höher. Hier machen Sie genau dasselbe, aber Sie behandeln nur die Ausnahmen, die von der Ebene direkt unter der aktuellen kommen. So bleibt Ihr Testcode gut strukturiert und Sie können schnell durch die Architektur laufen, um herauszufinden, wo etwas schief geht, anstatt überall hüpfen zu müssen.

Behandlung der gültigen und fehlerhaften Eingaben eines Tests

An dieser Stelle sollte klar sein, wie wir mit diesen Ausnahmen umgehen werden. Es gibt 2 Arten von Eingaben: gültige Eingaben und fehlerhafte Eingaben (die Eingaben sind im engeren Sinne gültig, aber nicht korrekt).

Wenn Sie mit gültigen Eingaben arbeiten, stellen Sie die implizite Erwartung ein, dass jeder Test, den Sie schreiben, funktioniert.

Ein solcher Methodenaufruf kann folgendermaßen aussehen: existingUserById_ShouldReturn_UserObject. Wenn diese Methode fehlschlägt (z. B. wird eine Ausnahme ausgelöst), wissen Sie, dass ein Fehler aufgetreten ist, und können mit dem Graben beginnen.

Durch Hinzufügen eines weiteren Tests (nonExistingUserById_ShouldThrow_IllegalArgumentException), der die fehlerhafte Eingabe verwendet und eine Ausnahme erwartet, können Sie sehen, ob Ihre Methode das tut, was sie mit falscher Eingabe tun soll.

TL; DR

Sie haben versucht, zwei Dinge in Ihrem Test zu tun: Auf gültige und fehlerhafte Eingaben prüfen. Wenn Sie dies in zwei Methoden aufteilen, die jeweils eine Aufgabe erfüllen, erhalten Sie klarere Tests und einen besseren Überblick darüber, wo Fehler auftreten.

Wenn Sie die geschichtete Arbeitseinheit im Auge behalten, können Sie auch die Anzahl der Tests reduzieren, die Sie für eine Schicht benötigen, die in der Hierarchie höher liegt, da Sie nicht alles berücksichtigen müssen, was in den unteren Schichten möglicherweise schief gelaufen ist: die Ebenen unterhalb der aktuellen Ebene sind eine virtuelle Garantie dafür, dass Ihre Abhängigkeiten funktionieren, und wenn etwas schief geht, befindet sich diese in Ihrer aktuellen Ebene (vorausgesetzt, die unteren Ebenen werfen selbst keine Fehler).

175
Jeroen Vannevel

Ich bin darauf gestoßen, weil SonarQubes Regel "squid: S2699" lautet: "Fügen Sie diesem Testfall mindestens eine Behauptung hinzu."

Ich hatte einen einfachen Test, dessen einziges Ziel es war, ohne Ausnahmen durchzukommen.

Betrachten Sie diesen einfachen Code:

public class Printer {

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

Welche Art von Behauptung kann hinzugefügt werden, um diese Methode zu testen? Sicher, Sie können es versuchen, aber das ist nur Code Bloat.

Die Lösung kommt von JUnit selbst.

Wenn keine Ausnahme ausgelöst wird und Sie dieses Verhalten explizit veranschaulichen möchten, fügen Sie einfach expected wie im folgenden Beispiel hinzu:

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

Test.None.class ist die Standardeinstellung für den erwarteten Wert.

74
Sven Döring

Java 8 macht dies viel einfacher und Kotlin/Scala doppelt so.

Wir können eine kleine Utility-Klasse schreiben

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 }

und dann wird Ihr Code einfach:

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

Wenn Sie keinen Zugriff auf Java-8 haben, würde ich eine schmerzhaft alte Java) -Funktion verwenden: beliebige Codeblöcke und einen einfachen Kommentar

//setup
Component component = new Component();

//act
configure(component);

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

Und schließlich mit kotlin, einer Sprache, in die ich mich kürzlich verliebt habe:

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

Obwohl es viel Raum gibt, um genau zu fummeln, wie Sie das ausdrücken wollen, war ich immer ein Fan von fließende Behauptungen .


Hinsichtlich

Du gehst das falsch an. Testen Sie einfach Ihre Funktionalität: Wenn eine Ausnahme ausgelöst wird, schlägt der Test automatisch fehl. Wenn keine Ausnahme ausgelöst wird, werden Ihre Tests alle grün angezeigt.

Dies ist im Prinzip richtig, aber im Ergebnis falsch.

Java erlaubt Ausnahmen für den Kontrollfluss. Dies erledigt die JRE-Laufzeit selbst in APIs wie Double.parseDouble Über ein NumberFormatException und Paths.get Über ein InvalidPathException.

Vorausgesetzt, Sie haben eine Komponente geschrieben, die Zahlenzeichenfolgen für Double.ParseDouble Validiert, z. B. mit einem Regex, einem handgeschriebenen Parser oder mit anderen Domain-Regeln, die den Bereich eines Double auf etwas Bestimmtes einschränken , wie teste ich diese Komponente am besten? Ich denke, ein offensichtlicher Test wäre zu behaupten, dass, wenn die resultierende Zeichenfolge analysiert wird, keine Ausnahme ausgelöst wird. Ich würde diesen Test entweder mit dem obigen Block assertDoesNotThrow oder /*comment*/{code} Schreiben. So etwas wie

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

Ich würde Sie auch ermutigen, diesen Test auf input mit Theories oder Parameterized zu parametrisieren, damit Sie es einfacher können Verwenden Sie diesen Test für andere Eingaben erneut. Alternativ, wenn Sie exotisch werden möchten, können Sie sich für ein Tool zur Testgenerierung (und this ) entscheiden. TestNG bietet eine bessere Unterstützung für parametrisierte Tests.

Besonders unangenehm finde ich die Empfehlung, @Test(expectedException=IllegalArgumentException.class) zu verwenden, diese Ausnahme ist gefährlich weit . Wenn sich Ihr Code so ändert, dass der Konstruktor der getesteten Komponente if(constructorArgument <= 0) throw IllegalArgumentException() enthält, und Ihr Test 0 für dieses Argument lieferte, weil es praktisch war - und dies ist sehr verbreitet, da das gute Generieren von Testdaten eine überraschend schwierige Aufgabe ist problem--, dann ist dein Test grüner Balken, obwohl er nichts testet. Ein solcher Test ist schlimmer als nutzlos.

29
Groostav

Mit AssertJ fließende Aussagen 3.7. :

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

Wenn Sie das Pech haben, alle Fehler in Ihrem Code abzufangen. Das kannst du dumm machen

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

JUnit 5 (Jupiter) bietet drei Funktionen zum Überprüfen der Abwesenheit/Anwesenheit von Ausnahmen:

assertAll​()

Asserts dass all geliefert executables
Wirf keine Ausnahmen.

assertDoesNotThrow​()

Asserts die Ausführung des
Geliefert executable/supplier
wirft nicht jede Art von Ausnahme .

Diese Funktion ist verfügbar
Seit JUnit 5.2. (29. April 2018).

assertThrows​()

Asserts die Ausführung des gelieferten executable
Würfe eine Ausnahme vom expectedType
Und gibt die Ausnahme zurück.

Beispiel

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

}
13
olibre

Genau zu diesem Zweck fügt JUnit5 die assertAll () -Methode hinzu.

assertAll( () -> foo() )

Quelle: JUnit 5 API

5
razalghul

Verwenden Sie assertNull (...)

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

Wenn Sie testen möchten, ob Ihr Testziel die Ausnahme verwendet. Verlassen Sie den Test einfach als (gespielter Mitarbeiter mit jMock2):

@Test
public void consumesAndLogsExceptions() throws Exception {

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

    target.doSth();
 }

Der Test würde bestanden, wenn Ihr Ziel die ausgelöste Ausnahme verbraucht, andernfalls würde der Test fehlschlagen.

Wenn Sie Ihre Ausnahmeverbrauchslogik testen möchten, werden die Dinge komplexer. Ich schlage vor, den Verbrauch an einen Mitarbeiter zu delegieren, der verspottet werden könnte. Daher könnte der Test sein:

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

Aber manchmal ist es überarbeitet, wenn Sie es nur protokollieren möchten. In diesem Fall ist dieser Artikel ( http://Java.dzone.com/articles/monitoring-declarative-transac , http://blog.novoj.net/2008/09/ 20/Testen-Aspekt-Punkteschnitte-ist-da-ein-einfacher-Weg / ) kann hilfreich sein, wenn Sie in diesem Fall auf tdd bestehen.

1
Yugang Zhou

Sie können dies tun, indem Sie eine @Rule verwenden und dann die unten gezeigte Methode reportMissingExceptionWithMessage aufrufen: Dies ist Scala code.

enter image description here

1
Crenguta S

Sie können davon ausgehen, dass beim Erstellen einer Regel keine Ausnahme ausgelöst wird.

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

Dies ist möglicherweise nicht der beste Weg, stellt jedoch sicher, dass keine Ausnahme vom getesteten Codeblock ausgelöst wird.

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
MLS

Sie können jede Art von eigenen Assertions erstellen, die auf Assertions von junit basieren:

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

Und teste:

//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");

Im Allgemeinen besteht die Möglichkeit, den Test in jedem Szenario und an jedem Ort, an dem es sinnvoll ist, sofort zu bestehen ("bla bla bla"). Verwenden Sie es beispielsweise in einem try/catch-Block, um einen Fehler zu verursachen, wenn im Testfall etwas ausgelöst wird:

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

Dies ist das Beispiel für die Methode, die wir testen. Angenommen, wir haben eine solche Methode, die unter bestimmten Umständen nicht fehlschlagen darf, die jedoch fehlschlagen kann:

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

Die obige Methode ist ein einfaches Beispiel. Dies funktioniert jedoch in komplexen Situationen, in denen der Fehler nicht so offensichtlich ist. Es gibt die Importe:

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.*;
0
armagedescu