Marquer le test d’unité comme une défaillance attendue dans JUnit

Comment puis-je marquer un test comme une défaillance attendue dans JUnit 4?

Dans ce cas, je veux continuer à exécuter ce test jusqu’à ce que quelque chose soit corrigé en amont. Ignorer le test va un peu trop loin, car alors je pourrais l’oublier. Je pourrais peut-être append une annotation @expected et intercepter l’exception assertThat par assertThat , mais cela semble également être assertThat au comportement attendu.

Voici à quoi ressemble mon test actuel:

 @Test public void unmarshalledDocumentHasExpectedValue() { doc = unmarshaller.unmarshal(getResourceAsStream("mydoc.xml")); final ST title = doc.getTitle(); assertThat(doc.getTitle().toSsortingngContent(), equalTo("Expected")); } 

Cette affirmation devrait réussir, mais à cause d’un bug en amont, ce n’est pas le cas. Pourtant, ce test est correct. ça devrait réussir. Pratiquement toutes les alternatives que j’ai trouvées sont trompeuses. Pour le moment, je pense que @Ignore("This test should pass once fixed upstream") est mon meilleur pari, mais je dois encore me rappeler de revenir. Je préférerais que le test fonctionne.

En Python, je peux utiliser le décorateur expectedFailure :

 class ExpectedFailureTestCase(unittest.TestCase): @unittest.expectedFailure def test_fail(self): self.assertEqual(1, 0, "broken") 

Avec QTestLib de Qt en C ++, vous pouvez utiliser QEXPECT_FAIL :

 QEXPECT_FAIL("", "Will be fixed next version", Continue); QCOMPARE(i, 42); 

Dans les deux cas ci-dessus, le test unitaire est ce que j’espère avoir fait. Est-ce que je manque quelque chose dans JUnit?

Je suppose ici que vous voulez que le test réussisse si votre assertion échoue, mais si l’assertion réussit, alors le test devrait également réussir.

La méthode la plus simple consiste à utiliser une règle de test . TestRule permet d’exécuter du code avant et après l’exécution d’une méthode de test. Voici un exemple:

 public class ExpectedFailureTest { public class ExpectedFailure implements TestRule { public Statement apply(Statement base, Description description) { return statement(base, description); } private Statement statement(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { try { base.evaluate(); } catch (Throwable e) { if (description.getAnnotation(Deprecated.class) != null) { // you can do whatever you like here. System.err.println("test failed, but that's ok:"); } else { throw e; } } } }; } } @Rule public ExpectedFailure expectedFailure = new ExpectedFailure(); // actually fails, but we catch the exception and make the test pass. @Deprecated @Test public void testExpectedFailure() { Object o = null; o.equals("foo"); } // fails @Test public void testExpectedFailure2() { Object o = null; o.equals("foo"); } } 

Tout d’abord, notez que la première méthode est marquée comme @Deprecated . J’utilise ceci comme marqueur pour la méthode pour laquelle je veux ignorer les échecs d’assertion. Vous pouvez faire ce que vous voulez pour identifier les méthodes, ce n’est qu’un exemple.

Ensuite, dans le fichier ExpectedFailure#apply() , lorsque j’effectue la base.evaluate (), j’attrape tout object Throwable (qui inclut AssertionError) et si la méthode est marquée avec l’annotation @Deprecated, j’ignore l’erreur. Vous pouvez effectuer n’importe quelle logique pour décider si vous devez ou non ignorer l’erreur, en fonction du numéro de version, du texte, etc. Vous pouvez également transmettre un indicateur déterminé dynamicment à ExpectedFailure pour échouer pour certains numéros de version:

 public void unmarshalledDocumentHasExpectedValue() { doc = unmarshaller.unmarshal(getResourceAsStream("mydoc.xml")); expectedFailure.setExpectedFailure(doc.getVersionNumber() < 3000); final ST title = doc.getTitle(); assertThat(doc.getTitle().toStringContent(), equalTo("Expected")); } 

Pour plus d'exemples, consultez ExternalResource et ExpectedException

Ignorer un test d'échec attendu plutôt que de le passer

Si vous souhaitez que vos tests soient ignorés plutôt que réussis, cela devient un peu plus complexe, car les tests sont ignorés avant d'être exécutés. Vous devez donc marquer rétrospectivement un test comme étant ignoré, ce qui impliquerait la construction de votre propre Runner. Pour commencer, consultez ma réponse à Comment définir la règle de méthode JUnit dans une suite? . Ou posez une autre question.

Je ne comprends pas tout à fait les détails de votre scénario, mais voici comment je teste généralement les échecs attendus:

La nouvelle voie:

 @Test(expected=NullPointerException.class) public void expectedFailure() { Object o = null; o.toSsortingng(); } 

pour les anciennes versions de JUnit:

 public void testExpectedFailure() { try { Object o = null; o.toSsortingng(); fail("shouldn't get here"); } catch (NullPointerException e) { // expected } } 

Si vous voulez vous assurer de lancer une exception, vous pouvez également utiliser cette seconde technique dans une boucle plutôt que de créer une méthode de test distincte pour chaque cas. Si vous deviez simplement parcourir un tas de cas dans une seule méthode en utilisant expected , le premier à lancer une exception mettrait fin au test et les cas suivants ne seraient pas vérifiés.

Qu’en est-il de l’attente explicite d’une AssertionError?

 @Test(expected = AssertionError.class) public void unmarshalledDocumentHasExpectedValue() { // ... } 

Si vous êtes raisonnablement confiant que seule la machine JUnit dans le test génère AssertionError, cela semble être une auto-documentation.

Vous courrez toujours le risque d’oublier un tel test. Je ne laisserais pas de tels tests dans le contrôle de version pour longtemps, si jamais.

Une option est de marquer le test comme @Ignore et d’y insérer un texte qui est peut-être un bug et en attente d’un correctif. De cette façon, il ne fonctionnera pas. Il sera alors sauté. Vous pouvez également utiliser les extensions pour répondre à vos besoins d’une manière potentiellement différente.

J’ai pris la réponse de Matthew un peu plus loin et @Optional implémenté une annotation @Optional vous pourriez utiliser à la place de l’annotation @Deprecated marqueur qu’il mentionne dans sa réponse. Bien que simple, je partagerai le code avec vous, peut-être que cela peut aider quelqu’un:

 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Optional { /** * Specify a Throwable, to cause a test method to succeed even if an exception * of the specified class is thrown by the method. */ Class[] exception(); } 

Avec une simple modification de la classe ExpectedFailure de Matt:

 public class ExpectedFailure implements TestRule { @Override public Statement apply(final Statement base, final Description description) { return statement(base, description); } private Statement statement(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { try { base.evaluate(); } catch (Throwable e) { // check for certain exception types Optional annon = description.getAnnotation(Optional.class); if (annon != null && ArrayUtils.contains(annon.exception(), e.getClass())) { // ok } else { throw e; } } } }; } } 

Vous pouvez maintenant annoter votre méthode de test avec @Optional et cela n’échouera pas, même si le type d’exception donné est déclenché (indiquez un ou plusieurs types que vous souhaitez que la méthode de test réussisse):

 public class ExpectedFailureTest { @Rule public ExpectedFailure expectedFailure = new ExpectedFailure(); // actually fails, but we catch the exception and make the test pass. @Optional(exception = NullPointerException.class) @Test public void testExpectedFailure() { Object o = null; o.equals("foo"); } } 

[METTRE À JOUR]

Vous pouvez également réécrire vos tests en utilisant org.junit.Assume de JUnit au lieu de org.junit.Assert org.junit.Assert , si vous voulez que vos tests réussissent même si l’hypothèse ne tient pas.

De la JavaDoc d’ Assume :

Un ensemble de méthodes utiles pour énoncer des hypothèses sur les conditions dans lesquelles un test est significatif. Une hypothèse d’échec ne signifie pas que le code est cassé, mais que le test ne fournit aucune information utile. Le runner JUnit par défaut traite les tests avec des hypothèses défaillantes comme étant ignorés.

Assume soit disponible depuis JUnit 4.4

Utilisez si possible une classe amont simulée. Stub avec le résultat correct. Si vous le souhaitez, remplacez la maquette par un object réel après la correction du bogue.