Jak sprawdzić, że żaden wyjątek nie jest wyrzucany?

Wiem, że jednym ze sposobów na to byłoby:

@Test
public void foo() {
   try {
      // execute code that you expect not to throw Exceptions.
   } catch(Exception e) {
      fail("Should not have thrown any exception");
   }
}
Czy jest na to jakiś czystszy sposób? (Prawdopodobnie używając Junita @Rule?)
Author: Andrii Abramov, 2013-07-18

17 answers

Podchodzisz do tego w złą stronę. Po prostu przetestuj swoją funkcjonalność: jeśli zostanie wyrzucony wyjątek, test automatycznie się nie powiedzie. Jeśli żaden wyjątek nie zostanie wyrzucony, wszystkie testy zmienią kolor na zielony.

Od czasu do czasu zauważyłem, że to pytanie wzbudza zainteresowanie, więc trochę się poszerzę.

Tło do testów jednostkowych

Kiedy testujesz jednostki, ważne jest, aby zdefiniować dla siebie to, co uważasz za jednostkę pracy. Zasadniczo: ekstrakcja kodu, który może lub nie może zawierać wielu metod lub klas, które reprezentują jedną funkcjonalność.

Lub, zgodnie z definicją w the art of Unit Testing, 2nd Edition by Roy Osherove, Strona 11:

A test jednostkowy to zautomatyzowany fragment kodu, który wywołuje badaną jednostkę pracy, a następnie sprawdza pewne założenia dotyczące pojedynczego wyniku końcowego tej jednostki. Test jednostkowy jest prawie zawsze pisany przy użyciu frameworka testowania jednostkowego. Można go łatwo zapisać i uruchomić szybko. Jest godny zaufania, czytelny i możliwy do utrzymania. Jest spójny w swoich wynikach, o ile kod produkcji nie uległ zmianie.

Ważne jest, aby uświadomić sobie, że jedna jednostka pracy zwykle nie jest tylko jedną metodą, ale na poziomie podstawowym jest jedną metodą, a następnie jest zamknięta przez inną jednostkę pracy.

Tutaj wpisz opis obrazka

Idealnie powinieneś mieć metodę testową dla każdej oddzielnej jednostki pracy, abyś zawsze mógł natychmiast zobaczyć, gdzie rzeczy są źle idzie. W tym przykładzie istnieje podstawowa metoda o nazwie getUserById(), która zwróci użytkownika i jest w sumie 3 Jednostka prac.

Pierwsza jednostka pracy powinna sprawdzić, czy zwracany jest prawidłowy użytkownik w przypadku poprawnego i nieprawidłowego wprowadzania danych.
Wszelkie wyjątki wyrzucane przez datasource muszą być tutaj obsługiwane: jeśli nie ma użytkownika, powinien być test, który wykaże, że wyjątek jest wyrzucany, gdy użytkownik nie może zostać znaleziony. Próbka tego może być IllegalArgumentException, który jest przechwytywany z adnotacją @Test(expected = IllegalArgumentException.class).

Gdy już obsłużysz wszystkie swoje przypadki użycia tej podstawowej jednostki pracy, awansujesz o jeden poziom. Tutaj robisz dokładnie to samo, ale obsługujesz tylko wyjątki, które pochodzą z poziomu tuż poniżej obecnego. Dzięki temu Twój kod testowy jest dobrze ustrukturyzowany i pozwala szybko przejść przez architekturę, aby znaleźć, gdzie coś pójdzie nie tak, zamiast skakać po całym miejscu.

Obsługa testów " poprawnych i wadliwych input

W tym momencie powinno być jasne, jak będziemy radzić sobie z tymi wyjątkami. Istnieją 2 typy danych wejściowych: valid input i faulty input (dane wejściowe są ważne w ścisłym znaczeniu, ale nie są poprawne).

Podczas pracy z valid input ustawiasz domyślne oczekiwanie, że jakikolwiek test, który napiszesz, zadziała.

Takie wywołanie metody może wyglądać tak: existingUserById_ShouldReturn_UserObject. Jeśli ta metoda się nie powiedzie (np.: zostanie wyrzucony wyjątek) to wiesz coś poszło nie tak i możesz zacząć kopać.

Dodając kolejny test (nonExistingUserById_ShouldThrow_IllegalArgumentException), który używa błędnego wejścia i oczekuje wyjątku, możesz sprawdzić, czy Twoja metoda robi to, co powinna zrobić z błędnym wejściem.

TL;DR

Próbowałeś zrobić dwie rzeczy w swoim teście: sprawdzić poprawne i błędne dane wejściowe. Dzieląc to na dwie metody, z których każda robi jedną rzecz, będziesz miał o wiele jaśniejsze testy i znacznie lepszy przegląd tego, gdzie rzeczy iść źle.

Mając na uwadze warstwową jednostkę prac, można również zmniejszyć ilość testów potrzebnych dla warstwy, która jest wyżej w hierarchii, ponieważ nie trzeba brać pod uwagę wszystkich rzeczy, które mogły pójść źle w niższych warstwach: warstwy poniżej bieżącej są wirtualną gwarancją, że zależności działają i jeśli coś pójdzie nie tak, to jest w bieżącej warstwie(zakładając, że niższe warstwy same nie powodują błędów).

 228
Author: Jeroen Vannevel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-13 17:43:28

Natknąłem się na to z powodu Zasady SonarQube "squid: S2699": "Dodaj przynajmniej jedno twierdzenie do tego przypadku testowego."

Miałem prosty test, którego jedynym celem było przejść bez rzucania WYJĄTKÓW.

Rozważ ten prosty kod:

public class Printer {

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

Jaki rodzaj twierdzenia można dodać, aby przetestować tę metodę? Jasne, można spróbować złapać wokół niego, ale to jest tylko kod nadęty.

Rozwiązanie pochodzi z samego JUnit.

W przypadku, gdy żaden wyjątek nie zostanie wyrzucony, a Ty aby wyraźnie zobrazować to zachowanie, po prostu dodaj expected jak w poniższym przykładzie:

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

Test.None.class jest wartością domyślną dla wartości oczekiwanej.

 169
Author: Sven Döring,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-25 15:00:24

JUnit 5 (Jupiter) udostępnia trzy funkcje do sprawdzania nieobecności/obecności WYJĄTKÓW:

assertAll​()

twierdzenia , że Wszystkie dostarczone executables
  nie rzucaj WYJĄTKÓW.

assertDoesNotThrow​()

zapewnia , że wykonanie
  dostarczony executable/supplier
Nie Rzuca żadnego rodzaju wyjątku .

Ta funkcja jest dostępne
  od JUnit 5.2.0 (29 kwietnia 2018).

assertThrows​()

twierdzenia , że wykonanie dostarczonego executable
wyjątek od expectedType
  i zwraca wyjątek .

Przykład

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

}
 73
Author: oHo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-09 13:40:36

Z twierdzeniami 3.7.0:

Assertions.assertThatCode(() -> toTest.method())
    .doesNotThrowAnyException();
 62
Author: denu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-04 07:38:35

Java 8 sprawia, że jest to dużo łatwiejsze, a Kotlin / Scala podwójnie.

Możemy napisać małą klasę użyteczności

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 }

I wtedy twój kod staje się po prostu:

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

Jeśli nie masz dostępu do Java-8, użyłbym boleśnie starego obiektu java: aribitrary bloków kodu i prosty komentarz

//setup
Component component = new Component();

//act
configure(component);

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

I wreszcie, z kotlinem, językiem, w którym ostatnio się zakochałam:

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

Choć jest dużo miejsca, aby bawić się dokładnie tak, jak chcesz wyrażaj to, zawsze byłem fanem płynnych twierdzeń.


Odnośnie

Podchodzisz do tego w złą stronę. Po prostu przetestuj swoją funkcjonalność: jeśli zostanie wyrzucony wyjątek, test automatycznie się nie powiedzie. Jeśli żaden wyjątek nie zostanie wyrzucony, wszystkie testy zmienią kolor na zielony.

Jest to zasadniczo poprawne, ale błędne w konkluzji.

Java dopuszcza wyjątki dla przepływu sterowania. Odbywa się to przez samo środowisko uruchomieniowe JRE w interfejsach API, takich jak Double.parseDouble przez a NumberFormatException i Paths.get przez a InvalidPathException.

Biorąc pod uwagę, że napisałeś komponent, który waliduje ciągi liczb dla Double.ParseDouble, może używając Regex, może ręcznie pisanego parsera, a może czegoś, co osadza inne reguły domeny, które ograniczają zakres podwójnego do czegoś konkretnego, jak najlepiej przetestować ten komponent? Myślę, że oczywistym testem byłoby stwierdzenie, że gdy wynikowy ciąg jest parsowany, żaden wyjątek nie jest wyrzucany. Napisałbym ten test używając albo powyższego assertDoesNotThrow albo /*comment*/{code} blok. Coś jak

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

Zachęcam również do parametryzacji tego testu na input za pomocą Theories lub Parameterized tak, że można łatwiej ponownie użyć tego testu dla innych wejść. Alternatywnie, jeśli chcesz przejść egzotyczne, możesz wybrać narzędzie do generowania testów (i to ). TestNG ma lepsze wsparcie dla testów parametryzowanych.

To, co uważam za szczególnie nieprzyjemne, to zalecenie użycia @Test(expectedException=IllegalArgumentException.class), to wyjątek jest niebezpiecznie szeroki . Jeśli twój kod zmieni się tak, że komponent testowy ma if(constructorArgument <= 0) throw IllegalArgumentException(), a twój test dostarczał 0 dla tego argumentu, ponieważ był wygodny-i jest to bardzo powszechne, ponieważ dobre generowanie danych testowych jest zaskakująco trudnym problemem -, wtedy twój test będzie zielony pasek, nawet jeśli nie testuje niczego. Taki test jest gorszy niż bezużyteczny.

 30
Author: Groostav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-13 07:17:52

Jeśli masz pecha wychwycić wszystkie błędy w kodzie. Możesz głupio zrobić

class DumpTest {
    Exception ex;
    @Test
    public void testWhatEver() {
        try {
            thisShouldThrowError();
        } catch (Exception e) {
            ex = e;
        }
        assertEquals(null,ex);
    }
}
 25
Author: Ben Tennyson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-30 18:03:30

Chociaż ten post ma już 6 lat, jednak wiele się zmieniło w świecie Junit. Z Junit5 możesz teraz użyć

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

Mam nadzieję, że pomoże to osobom używającym nowszej wersji Junit5

 19
Author: Dinesh Arora,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-24 22:00:21

JUnit5 dodaje do tego celu metodę assertAll ().

assertAll( () -> foo() )

Źródło: JUnit 5 API

 8
Author: razalghul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-04 17:48:50

Aby przetestować scenariusz za pomocą metody void, takiej jak

void testMeWell() throws SomeException {..}

To not throw an exception:

Junit5

assertDoesNotThrow(() -> {
    testMeWell();
});
 3
Author: pirho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-05 16:34:42

Use assertNull(...)

@Test
public void foo() {
    try {
        //execute code that you expect not to throw Exceptions.
    } catch (Exception e){
        assertNull(e);
    }
}
 2
Author: Mike Rapadas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-02 18:49:23

Jeśli chcesz sprawdzić, czy twój cel testowy zużywa wyjątek. Po prostu zostaw test jako (mock collaborator using jMock2):

@Test
public void consumesAndLogsExceptions() throws Exception {

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

    target.doSth();
 }

Test przejdzie, jeśli twój cel pochłonie wyrzucony wyjątek, w przeciwnym razie test się nie powiedzie.

Jeśli chcesz przetestować swoją logikę konsumpcji WYJĄTKÓW, sprawy stają się bardziej złożone. Proponuję powierzyć konsumpcję kolaborantowi, który mógłby być wyśmiany. Zatem test może być:

@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();
 }
Ale czasami jest przerobiony, jeśli tylko chcesz go zapisać. W tym przypadku ten artykuł ( http://java.dzone.com/articles/monitoring-declarative-transac, http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there-an-easy-way/) może pomóc, jeśli nalegasz tdd w tym przypadku.
 1
Author: Yugang Zhou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-19 01:34:18

Możesz oczekiwać, że wyjątek nie zostanie wyrzucony przez utworzenie reguły.

@Rule
public ExpectedException expectedException = ExpectedException.none();
 1
Author: LazerBanana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-03 14:45:24

To może nie być najlepszy sposób, ale zdecydowanie upewnia się, że wyjątek nie jest wyrzucany z testowanego bloku kodu.

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
    }
}
 1
Author: MLS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-13 05:30:57

Możesz to zrobić za pomocą reguły@, a następnie wywołać metodę reportMissingExceptionWithMessage, jak pokazano poniżej: To jest kod Scala.

Tutaj wpisz opis obrazka

 0
Author: Crenguta S,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-30 11:46:44

Stanąłem w tej samej sytuacji, musiałem sprawdzić, czy wyjątek jest wyrzucany wtedy, kiedy powinien i tylko wtedy, gdy powinien. Skończyło się na użyciu obsługi wyjątków na moją korzyść z następującym kodem:

    try {
        functionThatMightThrowException()
    }catch (Exception e){
        Assert.fail("should not throw exception");
    }
    RestOfAssertions();

Główną korzyścią dla mnie było to, że jest to całkiem proste i sprawdzić drugą stronę "jeśli i tylko jeśli" jest naprawdę łatwe w tej samej strukturze

 0
Author: Yair Landmann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-16 13:08:47

Możesz tworzyć dowolne własne twierdzenia na podstawie twierdzeń z 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);
    }
}

I test:

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

Ogólnie rzecz biorąc, istnieje możliwość natychmiastowego niepowodzenia("bla bla bla") testu w dowolnym scenariuszu, w dowolnym miejscu, w którym ma to sens. Na przykład użyj go w bloku try/catch, aby zawieść, jeśli coś zostanie rzucone w przypadku testu:

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

To jest próbka metody, którą testujemy, przypuśćmy, że mamy taką metodę, która nie może zawieść w określonych okolicznościach, ale może się nie udać:

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

Powyższa metoda jest prostą próbką. Ale to działa w złożonych sytuacjach, gdzie awaria nie jest tak oczywiste. Są importowane:

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.*;
 -1
Author: armagedescu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-06-21 15:01:49

Poniższy test nie sprawdza wszystkich wyjątków, zaznaczonych lub niezaznaczonych:

@Test
public void testMyCode() {

    try {
        runMyTestCode();
    } catch (Throwable t) {
        throw new Error("fail!");
    }
}
 -2
Author: Rocky Inde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-14 06:20:48