41 votes

Marquer le test unitaire comme un échec attendu dans JUnit

Comment peut-on marquer un essai, comme un échec prévisible dans JUnit 4?

Dans ce cas, je veux continuer à exécuter ce test jusqu'à ce que quelque chose est patché en amont. Ignorant le test va un peu trop loin, alors je risque de l'oublier. J'ai peut-être ajouter un @expected d'annotation et d'intercepter l'exception levée par assertThat, mais qui semble être sur le comportement attendu.

Voici ce que mon test actuel ressemble:

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

Qui affirment devrait réussir, mais en raison d'un bogue amont, il ne le fait pas. Pourtant, ce test est correct; il doit réussir. Pratiquement toutes les solutions que j'ai trouvé sont trompeuses. Maintenant, je pense que @Ignore("This test should pass once fixed upstream") est mon meilleur pari, mais j'ai encore à vous rappeler de revenir à elle. Je préfère que l'exécution des tests.

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

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

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

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

Dans les deux cas ci-dessus, l'exécution des tests unitaires, qui est ce que je suis en espérant qu'il arrive. Ai-je raté quelque chose dans JUnit?

25voto

Brad Mace Points 12173

Je ne suis pas tout à fait obtenir les détails de votre scénario, mais voici comment j'ai généralement de test pour de défaillance prévu:

La nappe de manière nouvelle:

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

pour les anciennes versions de JUnit:

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

Si vous avez un tas de choses que vous voulez vous assurer que de générer une exception, vous pouvez également utiliser cette deuxième technique à l'intérieur d'une boucle plutôt que de créer un autre méthode de test pour chaque cas. Si vous étiez juste à boucle à travers un tas de cas dans une seule méthode à l'aide d' expected, le premier à lancer une exception serait la fin de l'essai, et les cas ultérieurs ne serait pas vérifiée.

17voto

Matthew Farwell Points 31257

Je suppose ici que vous voulez le test à passer si votre assertion échoue, mais, si l'assertion de succès, alors le test doit se passer ainsi.

La façon la plus simple pour ce faire est d'utiliser un TestRule. TestRule donne la possibilité d'exécuter du code avant et après la méthode de test est exécuté. 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é comme @Deprecated. Je suis à l'utilisation de ce comme un marqueur de la méthode pour laquelle je veux ignorer les échecs d'assertion. Vous pouvez faire ce que vous voulez à identifier les méthodes, c'est juste un exemple.

Ensuite, dans l' ExpectedFailure#apply(), quand je fais de la base.evaluate(), je suis attraper des Throwable (qui comprend AssertionError) et si la méthode est marqué avec l'annotation @Deprecated, je ignorer l'erreur. Vous pouvez effectuer quelle que soit la logique de vous décider si vous devez ignorer l'erreur ou non, en se basant sur le numéro de version du texte, etc. Vous pouvez également passer une déterminées dynamiquement drapeau ExpectedFailure pour l'autoriser à l'échec 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 d'autres exemples, voir ExternalResource, et ExpectedException

Ignorant un échec attendu de test plutôt que de le laisser passer

Si vous voulez marquer des tests que Ignorés plutôt que de Succès, il devient un peu plus complexe, parce que les tests sont ignorés avant leur exécution, de sorte que vous avez à rétrospectivement marquer un essai, comme ignoré, ce qui impliquerait la construction de votre propre Coureur. Pour vous donner un début, voir ma réponse à Comment définir JUnit méthode de la règle dans une suite?. Ou poser une autre question.

15voto

pholser Points 1928

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

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

Si vous êtes raisonnablement convaincu que seule la machinerie JUnit dans le test soulèverait AssertionError, cela semble aussi auto-documenté que n'importe quoi.

Vous courriez toujours le risque d'oublier un tel test. Je ne laisserais pas de tels tests dans le contrôle de version pendant longtemps, voire jamais.

7voto

Aaron McIver Points 16766

Une option est de marquer le test comme @Ignore et d'y mettre du texte qui est peut-être un bug et en attente d'une correction. De cette façon, cela ne fonctionnera pas. Il sera ensuite ignoré. Vous pouvez également utiliser les extensions pour répondre à vos besoins de manière potentiellement différente.

4voto

Stefan Haberl Points 764

J'ai pris Matthieu répondre à une étape plus loin et effectivement mis en œuvre un @Optional d'annotation vous pouvez utiliser à la place de l' @Deprecated marqueurs d'annotation, il mentionne dans sa réponse. Bien que simple, je vais partager le code avec vous, c'est peut-être de l'aide à 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<? extends Throwable>[] exception();
}

Avec une simple modification de Matt ExpectedFailure classe:

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 qu'il ne manquera pas, même si le type d'exception est levée (fournir un ou plusieurs types de vous souhaitez la méthode d'essai de passer):

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

}

[Mise à JOUR]

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

D' Assumes'JavaDoc:

Un ensemble de méthodes utiles pour énoncer des hypothèses sur les conditions dans lesquelles le test est significatif.L'échec d'une hypothèse ne signifie pas que le code est cassée, mais que le test ne fournit aucune information utile. La valeur par défaut runner JUnit traite des tests avec défaut d'hypothèses ignoré.

Assume est disponible depuis JUnit 4.4

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X