Question Comment affirmez-vous qu'une certaine exception est levée dans JUnit 4 tests?


Comment puis-je utiliser idiomatiquement JUnit4 pour tester qu'un code lève une exception?

Alors que je peux certainement faire quelque chose comme ça:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  boolean thrown = false;

  try {
    foo.doStuff();
  } catch (IndexOutOfBoundsException e) {
    thrown = true;
  }

  assertTrue(thrown);
}

Je rappelle qu'il y a une annotation ou un Assert.xyz ou quelque chose c'est beaucoup moins chic et beaucoup plus dans l'esprit de JUnit pour ce genre de situations.


1621
2017-10-01 06:56


origine


Réponses:


JUnit 4 a le support pour ceci:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

Référence: https://junit.org/junit4/faq.html#atests_7


1942
2017-10-01 07:12



modifier Maintenant que JUnit5 est sorti, la meilleure option serait d'utiliser Assertions.assertThrows() (voir mon autre réponse).

Si vous n'avez pas migré vers JUnit 5, mais que vous pouvez utiliser JUnit 4.7, vous pouvez utiliser ExpectedException Règle:

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

C'est beaucoup mieux que @Test(expected=IndexOutOfBoundsException.class) parce que le test échouera si IndexOutOfBoundsException est jeté avant foo.doStuff()

Voir Cet article pour plus de détails


1165
2018-05-29 17:16



Soyez prudent en utilisant l'exception attendue, car il affirme seulement que le méthode jeté cette exception, pas un ligne de code particulière dans le test.

J'ai tendance à l'utiliser pour tester la validation des paramètres, car de telles méthodes sont généralement très simples, mais des tests plus complexes pourraient être mieux servis avec:

try {
    methodThatShouldThrow();
    fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}

Appliquer le jugement.


408
2017-10-01 09:31



Comme il a déjà été répondu, il existe de nombreuses manières de traiter les exceptions dans JUnit. Mais avec Java 8 il y en a un autre: utiliser des expressions Lambda. Avec les expressions Lambda, nous pouvons obtenir une syntaxe comme celle-ci:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

assertThrown accepte une interface fonctionnelle, dont les instances peuvent être créées avec des expressions lambda, des références de méthode ou des références de constructeurs. assertThrown acceptant cette interface attendra et sera prêt à gérer une exception.

C'est une technique relativement simple mais puissante.

Jetez un oeil à ce billet de blog décrivant cette technique: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

Le code source peut être trouvé ici: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Divulgation: Je suis l'auteur du blog et du projet.


187
2017-07-07 22:35



dans junit, il existe quatre façons de tester l'exception.

  • Pour junit4.x, utilisez l'attribut facultatif 'expected' de Test

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • pour junit4.x, utilisez la règle ExpectedException

    public class XxxTest {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
    
        @Test
        public void testFooThrowsIndexOutOfBoundsException() {
            thrown.expect(IndexOutOfBoundsException.class)
            //you can test the exception message like
            thrown.expectMessage("expected messages");
            foo.doStuff();
        }
    }
    
  • vous pouvez également utiliser la méthode classique try / catch largement utilisée dans le cadre de junit 3

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        try {
            foo.doStuff();
            fail("expected exception was not occured.");
        } catch(IndexOutOfBoundsException e) {
            //if execution reaches here, 
            //it indicates this exception was occured.
            //so we need not handle it.
        }
    }
    
  • Enfin, pour junit5.x, vous pouvez également utiliser assertThrows comme suit

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
        assertEquals("expected messages", exception.getMessage());
    }
    
  • alors

    • la 1ère façon est utilisée lorsque vous voulez seulement tester le type d'exception
    • les trois autres façons sont utilisées lorsque vous voulez un message d'exception de test plus loin
    • si vous utilisez junit 3, alors le troisième est préféré
    • si vous aimez junit 5, alors vous devriez aimer le 4ème
  • pour plus d'informations, vous pouvez lire ce document et junit5 guide de l'utilisateur pour plus de détails.


84
2017-08-05 08:05



tl; dr

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

  • post-JDK8: utilisez AssertJ ou lambda personnalisé pour affirmer exceptionnel comportement.

Indépendamment de Junit 4 ou JUnit 5.

la longue histoire

Il est possible de vous écrire fais le toi-même  try-catch bloquer ou utiliser les outils JUnit (@Test(expected = ...) ou la @Rule ExpectedException Fonction de règle JUnit).

Mais ces voies ne sont pas si élégantes et ne se mélangent pas bien lisibilité sage avec d'autres outils.

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

  2. le @Test(expected = ...) La fonctionnalité est intéressante car vous pouvez écrire moins de code et écrire ce test est censé être moins sujet aux erreurs de codage. Mais Cette approche fait défaut à certains domaines.

    • Si le test doit vérifier des éléments supplémentaires sur l'exception comme la cause ou le message (les bons messages d'exception sont vraiment importants, avoir un type d'exception précis peut ne pas suffire).
    • De même que l'attente est placée autour de la méthode, en fonction de la façon dont le code testé est écrit, la mauvaise partie du code de test peut déclencher l'exception, conduisant à un test faussement positif et je ne suis pas sûr que PMD, findbugs ou Sonar donnera des conseils 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 également une tentative de corriger les mises en garde précédentes, mais elle est un peu difficile à utiliser car elle utilise un style d'attente, EasyMock les utilisateurs connaissent très bien ce style. Cela pourrait convenir à certains, mais si vous suivez Développement basé sur le comportement (BDD) ou Arrangement Acte Assert (AAA) les principes ExpectedException règle ne rentrera pas dans ces styles d'écriture. A part cela, il peut souffrir du même problème que le @Test façon, en fonction de 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'instruction de test, elle casse votre flux de lecture si les tests suivent BDD ou AAA.

    Voir aussi ceci commentaire question sur JUnit de l'auteur de ExpectedException.

Donc, ces options ci-dessus ont toutes leurs charges de caveats, et clairement pas immunisé contre les erreurs de codeur.

  1. Il y a un projet que j'ai pris conscience après avoir créé cette réponse qui semble prometteuse, c'est catch-exception.

    Comme l'indique la description du projet, il permet à un codeur d'écrire dans une ligne de code fluide en attrapant l'exception et d'offrir cette exception pour une assertion ultérieure. Et vous pouvez utiliser n'importe quelle bibliothèque d'assertion 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 le voir le code est vraiment simple, vous attrapez l'exception sur une ligne spécifique, la then API est un alias qui utilisera les API AssertJ (similaire à l'utilisation de assertThat(ex).hasNoCause()...). À un moment donné, le projet s'est appuyé sur FEST-Assert l'ancêtre d'AssertJ. MODIFIER: Il semble que le projet prépare un support Java 8 Lambdas.

    Actuellement cette bibliothèque a deux défauts:

    • Au moment d'écrire ces lignes, il est intéressant de noter que cette bibliothèque est basée sur Mockito 1.x car elle crée un faux 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 s'il était basé sur mockito 2 dans la version actuelle, cela nécessiterait de déclarer un fabricant de simulateurs global (inline-mock-maker), quelque chose qui ne peut pas ce que vous voulez, car ce simulacre a des inconvénients différents que le simulacre régulier.

    • Cela nécessite encore une autre dépendance de test.

    Ces problèmes ne s'appliqueront pas une fois que la bibliothèque prendra en charge lambdas, cependant la fonctionnalité sera dupliquée par l'ensemble d'outils AssertJ.

    En tenant compte de tous, si vous ne voulez pas utiliser l'outil catch-exception, je vous recommanderai l'ancien bon try-catch bloquer, au moins jusqu'à la JDK7. Et pour les utilisateurs de JDK 8, vous préférerez peut-être utiliser AssertJ car il offre bien plus que de simples affirmations d'exceptions.

  2. Avec le JDK8, les lambdas entrent dans la scène de test, et ils se sont révélés être un moyen intéressant d'affirmer un comportement exceptionnel. AssertJ a été mis à jour pour fournir une API fluide agréable pour affirmer un comportement exceptionnel.

    Et un test d'échantillon 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 une réécriture presque complète de JUnit 5, des affirmations ont été amélioré un peu, ils peuvent s'avérer intéressants comme une façon originale d'affirmer correctement l'exception. Mais vraiment l'API d'assertion est encore 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 tu as remarqué assertEquals est toujours de retour void, et en tant que tel ne permet pas d'assertions en chaîne comme AssertJ.

    Aussi, si vous vous souvenez de l'affrontement de nom avec Matcher ou Assert, soyez prêt à rencontrer le même affrontement avec Assertions.

Je voudrais conclure aujourd'hui (2017-03-03) AssertJfacilité d'utilisation, API détectable, rapidité de développement et de facto la dépendance de test est la meilleure solution avec JDK8 quel que soit le framework de test (JUnit ou non), les JDK antérieurs devraient plutôt compter sur try-catch blocs même si elles se sentent maladroites.

Cette réponse a été copiée de une autre question qui n'ont pas la même visibilité, je suis le même auteur.


58
2017-12-07 14:19



BDD Solution de style: JUnit 4 + Exception de capture + AssertJ

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(foo).doStuff();

    then(caughtException()).isInstanceOf(IndexOutOfBoundsException.class);

}

Code source

Dépendances

eu.codearte.catch-exception:catch-exception:1.3.3

31
2017-11-15 19:17