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.
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.
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 unAssert.fail
à la fin dutry
bloquer sinon le test peut en manquer un du côté des assertions ; PMD, findbugs ou Sonar permettra de repérer ces problèmes.@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 }
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.
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 à utiliserassertThat(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.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"); }
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 retourvoid
, 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
ouAssert
, être prêt à affronter le même choc avecAssertions
.
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.
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());
}
}
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.
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.