Comment tester qu'aucune exception n'est levée?


238

Je sais qu'une façon de procéder 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");
   }
}

Existe-t-il un moyen plus propre de procéder? (Vous utilisez probablement Junit @Rule?)


10
Un test JUnit est considéré comme ayant échoué s'il lève une exception autre qu'une exception attendue. Habituellement, aucune exception n'est attendue.
Raedwald

N'y a-t-il pas une distinction entre l'échec et l'erreur dans JUnit? Le premier signifie que le test a échoué, le second signifie que quelque chose d'inattendu s'est produit.
Vituel

Réponses:


198

Vous vous en approchez mal. Testez simplement vos fonctionnalités: 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 suscite de temps en temps de l'intérêt, je vais donc m'étendre un peu.

Contexte des tests unitaires

Lorsque vous effectuez des tests unitaires, 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 qui peut ou non inclure plusieurs méthodes ou classes qui représente une seule fonctionnalité.

Ou, comme défini dans The art of Unit Testing, 2nd Edition by 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 concernant un seul résultat final de cette unité. Un test unitaire est presque toujours écrit à l'aide d'un cadre de tests unitaires. Il peut être écrit facilement et s'exécute rapidement. Il est fiable, lisible et maintenable. Il est cohérent dans ses résultats tant que le code de production n'a pas changé.

Ce qui est important à réaliser, c'est qu'une unité de travail n'est généralement pas seulement une méthode mais au niveau très basique c'est une méthode et ensuite elle est encapsulée par une autre unité de travail.

entrez la description de l'image ici

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

La première unité de travail doit tester si un utilisateur valide est retourné ou non dans le cas d'une entrée valide et invalide.
Toutes les exceptions levées par la source de données doivent être traitées ici: si aucun utilisateur n'est présent, un test doit démontrer qu'une exception est levée lorsque l'utilisateur est introuvable. Un exemple de ceci pourrait être celui IllegalArgumentExceptionqui est pris avec l' @Test(expected = IllegalArgumentException.class)annotation.

Une fois que vous avez géré toutes vos valises 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 viennent du niveau juste en dessous de l'actuel. Cela maintient votre code de test bien structuré et vous permet de parcourir rapidement l'architecture pour trouver où les choses tournent mal, au lieu d'avoir à sauter partout.

Gestion d'une entrée valide et défectueuse d'un test

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

Lorsque vous travaillez avec une entrée valide, vous définissez l'espérance implicite que 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 censée faire avec une mauvaise entrée.

TL; DR

Vous essayiez de faire deux choses dans votre test: vérifier une entrée valide et défectueuse. En divisant cela en deux méthodes qui font chacune une chose, vous aurez des tests beaucoup plus clairs et une bien meilleure vue d'ensemble des problèmes.

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 plus élevée dans la hiérarchie, car vous n'avez pas à tenir compte de tout ce qui pourrait avoir mal tourné dans les couches inférieures: le les couches en dessous de la couche actuelle sont une garantie virtuelle que vos dépendances fonctionnent et si quelque chose ne va pas, c'est dans votre couche actuelle (en supposant que les couches inférieures ne génèrent aucune erreur elles-mêmes).


2
Le fait est que j'essaie de TDD et l'un des collaborateurs que j'utilise lance une exception. Je dois donc tester le fait que je consomme l'exception levée par le collaborateur
Ankit Dhingra

6
Voulez-vous dire que votre fonctionnalité dépend de la gestion d'une exception? C'est une odeur de code: des exceptions sont là pour vous permettre élégamment de détecter les problèmes; ils ne sont pas utilisés pour le contrôle de flux. Si vous souhaitez tester un scénario dans lequel une exception doit être levée, vous devez utiliser l' expectedannotation. Si vous voulez tester un scénario où votre code échoue et que vous voulez voir si l'erreur est correctement gérée: utilisez expectedet peut-être utilisez asserts pour déterminer si elle a été résolue.
Jeroen Vannevel

Le fait est que je ne peux pas récupérer de l'exception qui s'est produite dans le collaborateur et tout ce que je fais, c'est simplement enregistrer le problème à l'aide d'un log.debug ("Message d'erreur"). Il n'y a donc aucun effet secondaire qui se produit dans le cadre du bloc de capture sur lequel je peux éventuellement affirmer.
Ankit Dhingra

5
@JeroenVannevel il est parfaitement valide de tester qu'une situation d'erreur qui provoque le déclenchement d'une exception est correctement gérée.
Thorbjørn Ravn Andersen

1
@dpk oui vous le pouvez. Vous ajoutez throws IllegalArgumentExceptionà votre test. Ce que vous voulez à la fin, c'est que votre test vire au rouge s'il y a une exception. Bien devinez quoi? Vous n'avez pas besoin d'écrire fail(). Comme l'a écrit @Jeroen Vannevel: "si une exception est levée, le test échouera automatiquement."
Amedee Van Gasse

132

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

J'ai eu un test simple dont le seul but était de passer sans lancer d'exceptions.

Considérez ce code simple:

public class Printer {

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

Quel type d'affirmation peut être ajouté pour tester cette méthode? Bien sûr, vous pouvez essayer de le contourner, mais ce n'est que du ballonnement de code.

La solution vient de JUnit lui-même.

Dans le cas où aucune exception n'est levée et que vous souhaitez illustrer explicitement ce comportement, ajoutez simplement expectedcomme 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.


30
Je pense que c'est la meilleure réponse. La réponse acceptée est excellente et l'auteur a raison de souligner l'odeur du code. Mais il n'a pas vraiment répondu à la question précise.
HellishHeat

4
il est intéressant de noter que la valeur par défaut de la valeur attendue est None, il suffit donc d'annoter la méthode avec @Test.
oziomajnr


41

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

assertAll​()

Affirme que tous les éléments fournis executables
  ne lèvent pas d'exceptions.

assertDoesNotThrow​()

Affirme que l'exécution du /
  fourni ne génère aucune exception . executablesupplier

  Cette fonction est disponible
  depuis JUnit 5.2.0 (29 avril 2018).

assertThrows​()

Affirme que l'exécution du fourni executable
lève une exception de expectedType
  et renvoie l' 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));
    }

}

1
C'est la meilleure réponse maintenant. D'autres réponses discutent d'anciennes versions de JUnit
Tejesh Raut

29

Java 8 rend cela beaucoup plus facile, et Kotlin / Scala doublement.

On peut écrire une petite classe 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 }

puis 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 java douloureusement ancienne: 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é amoureux:

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 exactement avec la façon dont vous voulez exprimer cela, j'ai toujours été un fan d' affirmations fluides .


En ce qui concerne

Vous vous en approchez mal. Testez simplement vos fonctionnalités: 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. Ceci est fait par le runtime JRE lui-même dans les API comme Double.parseDoublevia a NumberFormatExceptionet Paths.getvia a InvalidPathException.

Étant donné que vous avez écrit un composant qui valide les chaînes numériques Double.ParseDouble, peut-être en utilisant une expression régulière, peut-être un analyseur manuscrit, ou peut-être quelque chose qui incorpore d'autres règles de domaine qui restreignent la plage d'un double à quelque chose de spécifique, la meilleure façon de tester cela composant? Je pense qu'un test évident serait d'affirmer que, lorsque la chaîne résultante est analysée, aucune exception n'est levée. J'écrirais ce test à l'aide de ce qui précède assertDoesNotThrowou du /*comment*/{code}bloc. 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 en inpututilisant Theoriesou Parameterizedpour 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 test (et ce ). TestNG prend mieux en charge les tests paramétrés.

Ce que je trouve particulièrement désagréable, c'est la recommandation d'utiliser @Test(expectedException=IllegalArgumentException.class), cette exception est dangereusement large . Si votre code change de manière à ce que le composant sous le constructeur du test ait if(constructorArgument <= 0) throw IllegalArgumentException(), et que votre test fournisse 0 pour cet argument parce qu'il était pratique - et c'est très courant, parce que la bonne génération de données de test est un problème étonnamment difficile -, alors votre test sera une barre verte même si elle ne teste rien. Un tel test est pire qu'inutile.


2
(concernant l'utilisation de l'exception attendue) Depuis JUnit 4.13, vous pouvez utiliser Assert.assertThrowspour vérifier que du code lève une exception.
MageWind

22

Si vous n'avez pas la chance d'attraper toutes les erreurs dans votre code. Vous pouvez faire bêtement

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

1
Juste une petite suggestion, Exception exdevrait être = null;avant de pouvoir la tester.
Denees

4
Ce n'est pas une excellente solution. Si la méthode qui ne doit pas lever d'exception lance, vous n'obtiendrez pas de message d'erreur utile. Appelez simplement la méthode qui ne doit pas lever d'exception et testez sa valeur de retour (ou ses effets secondaires, comme la journalisation de l'exception). S'il lève ultérieurement une exception de façon inattendue, le test échouera.
NamshubWriter

3
Ou mettez simplement Assert.fail () dans la capture, IMO plus facile et plus joli.
isaac.hazan

Oui je suis d'accord avec toi. Une autre façon consiste à ajouter une annotation au-dessus de la méthode @Test (attendu = InvalidRequestException.class)
Ben Tennyson

Votre mauvaise orthographe est source de confusion: thisShouldThroughError -> thisShouldThrowError .
Oscar Bravo


7

Bien que ce poste ait maintenant 6 ans, beaucoup de choses ont changé dans le monde Junit. Avec Junit5, vous pouvez désormais utiliser

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

J'espère que cela aidera les gens qui utilisent la nouvelle version de Junit5


Je souhaite qu'il y ait un moyen de spécifier une classe d'exception concrète ici. Je dois faire ça à Awaitilityl' intérieur untilAsserted(ThrowingRunnable assertion). Le système testé teste actuellement une exception spécifique sur le ThrowingRunnable que je fournis, mais je veux lui donner un peu de temps jusqu'à ce qu'il cesse de le faire. Cependant, si cela provoquait une exception différente, j'aimerais que le test échoue instantanément.
Ubeogesh

1

Si vous voulez tester si votre cible de test consomme l'exception. Laissez simplement le test comme (simulateur 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 consomme l'exception levée, sinon le test échouerait.

Si vous voulez 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 journaliser. 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 -an-easy-way / ) peut aider si vous insistez sur tdd dans ce cas.


1

Utilisez assertNull (...)

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

6
Je dirais que c'est trompeur. Le bloc catch n'est jamais atteint, donc le assertNulln'est jamais exécuté non plus. Cependant, le lecteur rapide a l'impression qu'une affirmation est faite qui vérifie vraiment le cas de non-lancement. En d'autres termes: si le bloc catch est atteint, l'exception est toujours non nulle - elle peut donc être remplacée par un simple fail.
Andreas

trompeur en effet, ..... mais attendez, ... oh je vois ... assertNull(e)rapportera que le test a échoué, car comme indiqué ene peut pas être nulldans le catchbloc ... Mike c'est juste une programmation bizarre: - /. .. oui au moins utiliser fail()comme le dit Andreas
Julien

1

Vous pouvez vous attendre à ce que cette exception ne soit pas levée en créant une règle.

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

Les ExpectedExceptions sont utilisées pour affirmer les exceptions levées. Le code que vous fournissez est juste pour initialiser la règle afin que vous puissiez ajouter vos exigences pour les assertions. Ce code lui-même n'ajoute aucune valeur. Le javadoc indique également ceci: "/ ** * Renvoie une {@linkplain TestRule rule} qui ne s'attend à ce qu'aucune exception * ne soit levée (identique au comportement sans cette règle). * /" Il aura donc le même résultat exact que sans lui .
Pim Hazebroek

Je suis d'accord avec vous et je ne l'utiliserais pas de cette façon, mais il est possible d'affirmer qu'aucune exception n'a été levée. Si le test réussit, il devrait suffire de dire que l'exception n'a pas été levée, mais d'autre part, s'il y a une question, elle doit être nécessaire. Et rarement mais encore parfois, c'est bien de le rendre visible. Que se passe-t-il si le code et les circonstances changent et que nous n'avons aucun test pour un cas de bord particulier?
LazerBanana

Je suis curieux de voir comment vous affirmeriez cela à l'exception attendue. Et oui, si les exigences changent et que vous n'avez aucun test pour un boîtier de bord particulier, vous êtes vissé ;-) couvre toujours tous les boîtiers d'angle.
Pim Hazebroek

Que voulez-vous dire? vous ne l'affirmez pas, vous vous y attendez. Dans ce cas, vous ne vous attendez à aucune exception. Je ne sais pas de quoi vous parlez.
LazerBanana

0

Ce n'est peut-être pas le meilleur moyen, mais cela garantit définitivement que l'exception n'est pas levée du bloc de code en cours de 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

Vous pouvez le faire en utilisant un @Rule puis appelez la méthode reportMissingExceptionWithMessage comme indiqué ci-dessous: Ceci est du code Scala.

entrez la description de l'image ici


1
private val? Quel est ce langage? Clairement pas Java; p Et s'il vous plaît, ne fournissez pas de code comme capture d'écran, ce n'est pas le bienvenu.
Andremoniy

Je vois que vous avez mentionné qu'il s'agit de Scala, mais dire que cela peut être facilement fait en Java n'est pas un argument solide, je suis désolé
Andremoniy

J'ai enlevé la partie qui te dérangeait. J'essaierai également de remplacer l'image. Je n'ai pas encore compris comment ajouter du code ..
Crenguta S

-1

Les éléments suivants échouent au test pour toutes les exceptions, cochées ou décochées:

@Test
public void testMyCode() {

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

-1

Vous pouvez créer n'importe quel type de vos propres assertions basées sur les 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 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");

D'une manière générale, il est possible d'échouer instantanément ("bla bla bla") le test dans tous les scénarios, à tout endroit où cela a du sens. Par exemple, utilisez-le dans un bloc try / catch pour échouer si quelque chose est levé 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");}

Voici l'exemple de la méthode que nous testons, en supposant que nous avons 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 simple échantillon. 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.*;

Il existe une meilleure option pour vérifier qu'aucune assertion n'a été émise, ce qui n'implique pas la création de code personnalisé. @Rule est l'un d'entre eux
Vargan

@Vargan J'ai indiqué la méthode pour créer vos propres assertions de la façon dont il est conçu par JUnit spécialement pour créer vos propres assertions. JUnit prévoit que par conception, en particulier à cette fin, pour créer vos propres règles, étendez le comportement de JUnit avec des assertions qui ne sont pas encore implémentées. Parce que tout n'est pas implémenté dans ce monde Ces assertions fonctionnent de la même manière que l'assertion JUnit fonctionne en termes de réussite ou d'échec ainsi que de signalement des échecs.
armagedescu
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.