web-dev-qa-db-fra.com

Comment vérifier qu'aucune exception n'est levée?

Je sais qu'une façon de le faire serait:

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

Y a-t-il une manière plus propre de faire ceci. (Probablement en utilisant @Rule? De Junit)

189
Ankit Dhingra

Vous vous approchez de la mauvaise façon. Testez simplement votre fonctionnalité: si une exception est levée, le test échouera automatiquement. Si aucune exception n'est levée, vos tests deviendront tous verts.

J'ai remarqué que cette question suscitait de l'intérêt de temps en temps, alors je vais développer un peu.

Contexte des tests unitaires

Lorsque vous testez des unités, il est important de définir vous-même ce que vous considérez comme une unité de travail. Fondamentalement: une extraction de votre base de code pouvant inclure ou non plusieurs méthodes ou classes représentant un seul élément de fonctionnalité.

Ou, comme défini dans L'art des tests unitaires, 2e édition par Roy Osherove , page 11:

Un test unitaire est un morceau de code automatisé qui appelle l'unité de travail testée, puis vérifie certaines hypothèses relatives à un résultat final unique de cette unité. Un test unitaire est presque toujours écrit en utilisant un framework de tests unitaires. Il peut être écrit facilement et fonctionne rapidement. C'est digne de confiance, lisible et maintenable. Ses résultats sont cohérents tant que le code de production n'a pas changé.

Ce qui est important à comprendre est qu’une unité de travail n’est en général pas une méthode, mais qu’au niveau de base, c’est une méthode et ensuite est encapsulé par une autre unité d'œuvres.

enter image description here

Idéalement, vous devriez avoir une méthode de test pour chaque unité de travail afin de pouvoir toujours voir immédiatement où les choses vont mal. Dans cet exemple, il existe une méthode de base appelée getUserById() qui renvoie un utilisateur et un total de 3 unités de travail.

La première unité de travail doit vérifier si un utilisateur valide est renvoyé en cas de saisie valide et non valide.
Toutes les exceptions levées par la source de données doivent être gérées ici: en l’absence d’utilisateur, un test doit démontrer qu’une exception est levée lorsque l’utilisateur est introuvable. Un exemple de ceci pourrait être la IllegalArgumentException qui est interceptée avec l'annotation @Test(expected = IllegalArgumentException.class).

Une fois que vous avez géré tous vos cas d'utilisation pour cette unité de travail de base, vous montez d'un niveau. Ici, vous faites exactement la même chose, mais vous ne gérez que les exceptions qui proviennent du niveau situé juste en dessous du niveau actuel. Ceci maintient votre code de test bien structuré et vous permet de parcourir rapidement l’architecture pour rechercher les erreurs, au lieu de devoir sauter partout.

Traitement des entrées valides et défectueuses d'un test

À ce stade, il devrait être clair comment nous allons gérer ces exceptions. Il existe 2 types d’entrées: entrée valide et entrée défectueuse (le l’entrée est valide au sens strict, mais ce n’est pas correct).

Lorsque vous travaillez avec entrée valide , vous définissez l'attente implicite selon laquelle le test que vous écrivez fonctionnera.

Un tel appel de méthode peut ressembler à ceci: existingUserById_ShouldReturn_UserObject. Si cette méthode échoue (par exemple, une exception est levée), vous savez que quelque chose s'est mal passé et vous pouvez commencer à creuser.

En ajoutant un autre test (nonExistingUserById_ShouldThrow_IllegalArgumentException) qui utilise l'entrée défectueuse et attend une exception, vous pouvez voir si votre méthode fait ce qu'elle est supposée faire. faire avec une mauvaise entrée.

TL; DR

Vous essayiez de faire deux choses dans votre test: vérifiez si une entrée est valide et défectueuse. En divisant cette méthode en deux méthodes qui font chacune une chose, vous obtiendrez des tests beaucoup plus clairs et un meilleur aperçu des problèmes éventuels.

En gardant à l'esprit l'unité de travail en couches, vous pouvez également réduire le nombre de tests dont vous avez besoin pour une couche située plus haut dans la hiérarchie, car vous n'avez pas à prendre en compte tout ce qui pourrait s'être mal passé dans les couches inférieures: les couches situées en dessous de celle en cours sont une garantie virtuelle que vos dépendances fonctionnent et qu'en cas de problème, cela se trouve dans votre couche actuelle (en supposant que les couches inférieures ne génèrent pas d'erreurs elles-mêmes).

175
Jeroen Vannevel

Je suis tombé sur cela à cause de la règle de SonarQube "squid: S2699": "Ajoutez au moins une assertion à ce test."

J'ai eu un test simple dont le seul objectif était de passer sans exception.

Considérons ce code simple:

public class Printer {

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

Quel type d'assertion peut être ajouté pour tester cette méthode? Bien sûr, vous pouvez faire un essai-attrape autour de cela, mais ce n'est que du fardeau de code.

La solution vient de JUnit elle-même.

Si aucune exception n'est générée et que vous souhaitez illustrer explicitement ce problème, ajoutez simplement expected comme dans l'exemple suivant:

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

Test.None.class est la valeur par défaut pour la valeur attendue.

74
Sven Döring

Java 8 facilite beaucoup les choses, et Kotlin/Scala encore plus.

Nous pouvons écrire une petite classe d'utilitaire

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 }

et alors votre code devient simplement:

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

Si vous n'avez pas accès à Java-8, j'utiliserais une installation extrêmement ancienne Java: des blocs de code aribitrary et un simple commentaire.

//setup
Component component = new Component();

//act
configure(component);

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

Et enfin, avec kotlin, une langue dont je suis récemment tombée amoureuse:

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

Bien qu'il y ait beaucoup de place pour jouer avec exactement comment vous voulez exprimer cela, j'ai toujours été fan de affirmations fluides .


En ce qui concerne

Vous vous approchez de la mauvaise façon. Testez simplement votre fonctionnalité: si une exception est levée, le test échouera automatiquement. Si aucune exception n'est levée, vos tests deviendront tous verts.

Ceci est correct en principe mais incorrect en conclusion.

Java autorise des exceptions pour le flux de contrôle. Cette opération est effectuée par le moteur d'exécution JRE lui-même dans des API telles que Double.parseDouble via NumberFormatException et Paths.get via InvalidPathException.

Étant donné que vous avez écrit un composant qui valide les chaînes numériques pour Double.ParseDouble, peut-être en utilisant une expression régulière, peut-être un analyseur manuscrit, ou peut-être quelque chose qui intègre d'autres règles de domaine qui limitent la plage d'un double à quelque chose de spécifique, Quelle est la meilleure façon de tester ce composant? Je pense qu'un test évident consisterait à affirmer que, lorsque la chaîne résultante est analysée, aucune exception n'est levée. J'écrirais ce test en utilisant le bloc assertDoesNotThrow ou /*comment*/{code} ci-dessus. Quelque chose comme

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

Je vous encourage également à paramétrer ce test sur input en utilisant Theories ou Parameterized afin que vous puissiez plus facilement réutiliser ce test pour d'autres entrées. Alternativement, si vous voulez devenir exotique, vous pouvez opter pour un outil de génération de tests (et this ). TestNG supporte mieux les tests paramétrés.

Ce que je trouve particulièrement déplaisant, c'est la recommandation d'utiliser @Test(expectedException=IllegalArgumentException.class), , cette exception est dangereusement large . Si votre code change de telle sorte que le constructeur du composant testé ait if(constructorArgument <= 0) throw IllegalArgumentException(), et que votre test fournisse 0 pour cet argument car il était pratique - et cela est très courant, car générer de bonnes données de test est un problème étonnamment difficile- -, votre test sera alors vert alors qu'il ne teste rien. Un tel test est pire qu'inutile.

29
Groostav

Avec AssertJ fluent assertions 3.7. :

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

Si vous avez la malchance d'attraper toutes les erreurs de votre code. Tu peux faire bêtement

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

JUnit 5 (Jupiter) fournit trois fonctions pour vérifier l'absence/la présence d'une exception:

assertAll​()

Assure que tout fourni executables
Ne jette pas d'exceptions.

assertDoesNotThrow​()

Assure que l'exécution du
Fourni executable/supplier
ne renvoie aucune sorte de exception .

Cette fonction est disponible
Depuis JUnit 5.2. (29 avril 2018).

assertThrows​()

Assure que l'exécution du executable fourni
lève une exception de la expectedType
Et renvoie le exception .

Exemple

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

JUnit5 ajoute la méthode assertAll () dans ce but précis.

assertAll( () -> foo() )

source: API JUnit 5

5
razalghul

Utilisez assertNull (...)

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

Si vous voulez vérifier si votre cible de test utilise l'exception. Laissez simplement le test en tant que (simulacre de collaborateur utilisant jMock2):

@Test
public void consumesAndLogsExceptions() throws Exception {

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

    target.doSth();
 }

Le test réussirait si votre cible utilisait l'exception déclenchée, sinon le test échouerait.

Si vous souhaitez tester votre logique de consommation d'exception, les choses deviennent plus complexes. Je suggère de déléguer la consommation à un collaborateur dont on pourrait se moquer. Par conséquent, le test pourrait être:

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

Mais parfois, il est trop conçu si vous voulez simplement le connecter. Dans ce cas, cet article ( http://Java.dzone.com/articles/monitoring-declarative-transac , http://blog.novoj.net/2008/09/ 20/testing-aspect-pointcuts-is-there-a-easy-way / ) peut vous aider si vous insistez dans ce cas.

1
Yugang Zhou

Vous pouvez le faire en utilisant un @Rule, puis appelez la méthode reportMissingExceptionWithMessage comme indiqué ci-dessous: Il s'agit du code Scala.

enter image description here

1
Crenguta S

Vous pouvez vous attendre à ce que cette exception ne soit pas générée par la création d'une règle.

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

Ce n'est peut-être pas la meilleure solution, mais cela permet de s'assurer que cette exception ne soit pas émise par le bloc de code testé.

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

Vous pouvez créer n'importe quel type de vos propres assertions à partir d'assertions de 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);
    }
}

Et testez:

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

En règle générale, il est possible d’échouer instantanément le test ("bla bla bla") dans n’importe quel scénario, quel que soit le lieu où cela se justifie. Par exemple, utilisez-le dans un bloc try/catch pour échouer si quelque chose est renvoyé dans le cas de test:

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

Ceci est un exemple de la méthode que nous testons, en supposant que nous ayons une telle méthode qui ne doit pas échouer dans des circonstances spécifiques, mais elle peut échouer:

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

La méthode ci-dessus est un exemple simple. Mais cela fonctionne pour des situations complexes, où l'échec n'est pas si évident. Il y a les importations:

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