Comment vérifier qu'une exception a été levée pas

Dans mon unité de test à l'aide de Mockito je veux vérifier que NullPointerException n'a pas été levée.

public void testNPENotThrown{
    Calling calling= Mock(Calling.class);
    testClass.setInner(calling);
    testClass.setThrow(true);

    testClass.testMethod();

    verify(calling, never()).method();
}

mon test a mis en place le testClass, le réglage de l' Calling l'objet et la propriété pour que la méthode lance un NullPointerException.

I vérifier que l'Appel.méthode() n'est jamais appelée.

public void testMethod(){
    if(throw) {
        throw new NullPointerException();
    }

    calling.method();
}

je veux avoir un test d'échec parce qu'il lance un NullPointerException, et puis je veux écrire un code pour corriger ça.

Ce que j'ai remarqué, c'est que le test passe toujours comme l'exception n'est jamais jeté la méthode de test.

15
demandé sur Ahmed KRAIEM 2013-07-02 16:59:09

4 réponses

tl;dr

  • pré-JDK8 : je vais recommander le bon vieux try -catch bloc.

  • post-JDK8: utiliser AssertJ ou custom lambdas pour affirmer exceptionnel comportement.

l'histoire

il est possible de vous écrire un le faire vous-mêmetry -catch bloquer ou utiliser les outils JUnit (@Test(expected = ...) ou @Rule ExpectedException JUnit fonction de règle).

mais ces façons ne sont pas si élégantes et ne se mélangent pas bien lisibilité sage avec d'autres outils.

  1. try -catch bloc, vous devez écrire le bloc autour de l'essai de comportement, et d'écrire l'affirmation dans le bloc catch, qui peut être bien, mais beaucoup trouvent que ce style interrompt la lecture d'un test. Aussi, vous devez écrire un Assert.fail à la fin du try bloquer sinon le test peut en manquer un du côté des assertions ; PMD, findbugs ou Sonar permettra de repérer ces problèmes.

  2. @Test(expected = ...) la fonctionnalité est intéressante car vous pouvez écrire moins de code et ensuite écrire ce test est supposé être moins sujet à des erreurs de codage. Mais cette approche fait défaut dans certains domaines.

    • si le test a besoin de vérifier des choses supplémentaires sur l'exception comme la cause ou le message (bons messages d'exception sont vraiment importants, avoir un type d'exception précis peut ne pas être suffisant).
    • aussi comme l'attente est placée autour de la méthode, en fonction de la façon dont le code testé est écrit, alors la mauvaise partie du code de test peut jeter l'exception, conduisant à un test faussement positif et je ne suis pas sûr que PMD, findbugs ou Sonar donnera des indices sur un tel code.

      @Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
      
  3. le ExpectedException la règle est aussi une tentative pour résoudre les précédentes mises en garde, mais il se sent un peu difficile à utiliser, car il utilise une attente de style, EasyMock les utilisateurs connaissent très bien ce style. Il peut être pratique pour certains, mais si vous suivez Comportement Du Développement Piloté Par (BDD) or Organiser Acte Assert (AAA) principes leExpectedException la règle ne conviendra pas à ce style d'écriture. Côté de qui il peut souffrir de la même question que le que le @Test moyen, selon l'endroit où vous placez l'attente.

    @Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }
    

    même l'exception attendue est placée avant l'énoncé du test, elle casse votre lecture si les tests suivent BDD ou AAA.

    voir Aussi problème sur JUnit de l'auteur de ExpectedException.

donc ces options ci-dessus ont toutes leur charge de mises en garde, et clairement pas à l'abri des erreurs de codeur.

  1. il y a un projet que j'ai connu après créer cette réponse qui semble prometteuse, c'est catch-exception.

    comme le dit la description du projet, il permet à un codeur d'écrire dans une ligne de code courante en saisissant l'exception et en offrant cette exception pour une assertion ultérieure. Et vous pouvez utiliser n'importe quelle bibliothèque d'assertions comme Hamcrest ou AssertJ.

    un exemple rapide tiré de la page d'accueil:

    // given: an empty list
    List myList = new ArrayList();
    
    // when: we try to get the first element of the list
    when(myList).get(1);
    
    // then: we expect an IndexOutOfBoundsException
    then(caughtException())
            .isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessage("Index: 1, Size: 0") 
            .hasNoCause();
    

    comme vous pouvez voir le code est vraiment simple, vous prenez exception sur une ligne spécifique, le then API est un alias qui va utiliser AssertJ APIs (similaire à utiliser assertThat(ex).hasNoCause()...). a un moment donné le projet s'est appuyé sur FEST-affirmer l'ancêtre de AssertJ. EDIT: il semble que le projet soit en train de brasser un support Java 8 Lambdas.

    cette bibliothèque présente actuellement deux lacunes:

    • Au moment d'écrire ces lignes, il convient de dire que cette bibliothèque est basé sur Mockito 1.x car elle crée un simulacre de l'objet testé derrière la scène. Comme Mockito n'est toujours pas mis à jour cette bibliothèque ne peut pas fonctionner avec les classes finales ou les méthodes finales. Et même si elle était basée sur mockito 2 dans la version actuelle, cela nécessiterait de déclarer un mock maker global (inline-mock-maker), quelque chose qui peut ne pas être ce que vous voulez, car ce mockmaker a différents inconvénients que le mockmaker régulier.

    • il faut encore un autre test dépendance.

    ces problèmes ne s'appliqueront pas une fois que la bibliothèque supportera lambdas, cependant la fonctionnalité sera dupliquée par AssertJ toolset.

    en tenant compte de tous si vous ne voulez pas utiliser l'outil catch-exception, je recommanderai l'ancienne bonne façon de la try -catch bloc, au moins jusqu'au JDK7. Et pour les utilisateurs de JDK 8 vous pourriez préférer utiliser AssertJ car il offre peut-être plus que simplement affirmer exception.

  2. avec le JDK8, lambdas entrent dans la scène de test, et ils se sont avérés être une façon intéressante d'affirmer un comportement exceptionnel. AssertJ a été mis à jour pour fournir une API agréable fluent pour affirmer un comportement exceptionnel.

    et un exemple de test avec AssertJ:

    @Test
    public void test_exception_approach_1() {
        ...
        assertThatExceptionOfType(IOException.class)
                .isThrownBy(() -> someBadIOOperation())
                .withMessage("boom!"); 
    }
    
    @Test
    public void test_exception_approach_2() {
        ...
        assertThatThrownBy(() -> someBadIOOperation())
                .isInstanceOf(Exception.class)
                .hasMessageContaining("boom");
    }
    
    @Test
    public void test_exception_approach_3() {
        ...
        // when
        Throwable thrown = catchThrowable(() -> someBadIOOperation());
    
        // then
        assertThat(thrown).isInstanceOf(Exception.class)
                          .hasMessageContaining("boom");
    }
    
  3. Avec un près de la réécriture complète de JUnit 5, affirmations ont été amélioration de l' un peu, ils peuvent s'avérer intéressant comme une façon out of the box d'affirmer correctement l'exception. Mais en réalité L'API assertion est toujours un peu pauvre, il n'y a rien à l'extérieur assertThrows.

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }
    

    Comme vous avez remarqué assertEquals est toujours de retour void, et en tant que tel ne permet pas de enchaîner des affirmations comme AssertJ.

    aussi si vous vous souvenez de nom clash avec Matcher ou Assert, être prêt à affronter le même choc avec Assertions.

j'aimerais conclure qu'aujourd'hui (2017-03-03) AssertJ de facto la dépendance de test est la meilleure solution avec JDK8 quel que soit le framework de test (JUnit ou non), les jdks précédents devraient plutôt s'appuyer sur try -catch bloquent même s'ils se sentent clunky.

14
répondu Brice 2017-03-10 16:17:58

si Je ne vous comprends pas mal, vous avez besoin de quelque chose comme ceci:

@Test(expected = NullPointerException.class)
public void testNPENotThrown {
    Calling calling= Mock(Calling .class);
    testClass.setInner(calling);
    testClass.setThrow(true);

    testClass.testMethod();

    verify(calling, never()).method();
    Assert.fail("No NPE");
}

mais en nommant le test "NPENotThrown" Je m'attendrais à un test comme ceci:

public void testNPENotThrown {
    Calling calling= Mock(Calling .class);
    testClass.setInner(calling);
    testClass.setThrow(true);

    testClass.testMethod();
    try {
        verify(calling, never()).method();
        Assert.assertTrue(Boolean.TRUE);
    } catch(NullPointerException ex) {
        Assert.fail(ex.getMessage());
    }
}
4
répondu Stefan Beike 2016-09-06 09:39:25

une Autre approche pourrait être d'utiliser try/catch à la place. C'est un peu en désordre, mais ce que je comprends de ce test va être de courte durée, de toute façon tant que c'est pour le TDD:

@Test
public void testNPENotThrown{
  Calling calling= Mock(Calling.class);
  testClass.setInner(calling);
  testClass.setThrow(true);

  try{
    testClass.testMethod();
    fail("NPE not thrown");
  }catch (NullPointerException e){
    //expected behaviour
  }
}

EDIT: j'étais pressé quand j'ai écrit ça. Ce que je veux dire par 'ce test va être de courte durée de toute façon comme il est pour TDD' est que vous dites que vous allez écrire quelque code pour corriger ce test tout de suite, de sorte qu'il ne sera jamais jeter une NullPointerException dans le futur. Alors autant supprimer le test. Par conséquent, il est probablement pas la peine de passer beaucoup de temps à écrire un beau test (d'où ma suggestion :-))

plus généralement:

commencer par un test pour affirmer que (par exemple) la valeur de retour d'une méthode n'est pas nulle est un principe TDD établi, et vérifier une NullPointerException (NPE) est une façon possible de procéder. Cependant, votre code de production ne va probablement pas avoir un flux où un NPE est lancé. Vous allez vérifier null Et puis faire quelque chose de sensé, j'imagine. Cela rendrait ce test particulier redondant à ce moment-là, car il vérifiera qu'un NPE n'est pas lancé, alors qu'en fait, il ne peut jamais se produire. Vous pouvez alors le remplacer par un test qui vérifie ce qui se passe quand un null est rencontré: renvoie un NullObject par exemple, ou jette un autre type d'exception, ce qui est approprié.

il n'y a bien sûr aucune exigence que vous supprimiez le test redondant, mais si vous ne le faites pas, il sera asseyez-vous là faisant chaque construire légèrement plus lentement, et provoquant chaque développeur qui lit le test de se demander; "Hmm, un NPE? Ce code ne peut pas lancer un NPE?". J'ai vu beaucoup de code TDD où les classes de test ont beaucoup de tests redondants comme celui-ci. Si le temps le permet, il vaut la peine de revoir vos tests de temps en temps.

2
répondu Mark Chorley 2013-07-02 23:16:17

généralement chaque cas de test s'exécute avec une nouvelle instance, donc définir une variable d'instance n'aidera pas. Donc, assurez -'throw' variable statique dans le cas contraire.

0
répondu Juned Ahsan 2013-07-02 13:06:18