Confusion JUnit: utiliser 'extend TestCase' ou '@Test'?

152

J'ai trouvé l'utilisation correcte (ou du moins la documentation) de JUnit très déroutante. Cette question sert à la fois de référence future et de vraie question.

Si j'ai bien compris, il existe deux approches principales pour créer et exécuter un test JUnit:

Approche A (style JUnit 3): créez une classe qui étend TestCase et démarrez les méthodes de test avec le mot test. Lors de l'exécution de la classe en tant que test JUnit (dans Eclipse), toutes les méthodes commençant par le mot testsont automatiquement exécutées.

import junit.framework.TestCase;

public class DummyTestA extends TestCase {

    public void testSum() {
        int a = 5;
        int b = 10;
        int result = a + b;
        assertEquals(15, result);
    }
}

Approche B (style JUnit 4): créez une classe «normale» et ajoutez une @Testannotation à la méthode. Notez que vous ne devez PAS démarrer la méthode avec le mot test.

import org.junit.*;
import static org.junit.Assert.*;

public class DummyTestB {

    @Test
    public void Sum() {
        int a = 5;
        int b = 10;
        int result = a + b;
        assertEquals(15, result);
    }
}

Mélanger les deux ne semble pas être une bonne idée, voir par exemple cette question de stackoverflow :

Maintenant, mes questions:

  1. Quelle est l'approche préférée ou quand utiliseriez-vous l'une au lieu de l'autre?
  2. L'approche B permet de tester les exceptions en étendant l'annotation @Test comme dans @Test(expected = ArithmeticException.class). Mais comment tester les exceptions lors de l'utilisation de l'approche A?
  3. Lorsque vous utilisez l'approche A, vous pouvez regrouper un certain nombre de classes de test dans une suite de tests comme ceci:

    TestSuite suite = new TestSuite("All tests");
    suite.addTestSuite(DummyTestA.class);
    suite.addTestSuite(DummyTestAbis.class);

    Mais cela ne peut pas être utilisé avec l'approche B (puisque chaque classe de test doit sous-classe TestCase). Quelle est la bonne façon de regrouper les tests pour l'approche B?

Edit: j'ai ajouté les versions JUnit aux deux approches

Rabarberski
la source
J'ai vu extends TestCaseet ensuite chaque test annoté avec @Testjuste pour confondre les choses. :)
EM-Creations

Réponses:

119

La distinction est assez simple:

  • l'extension TestCaseest la façon dont les tests unitaires ont été écrits dans JUnit 3 (bien sûr, il est toujours pris en charge dans JUnit 4)
  • l'utilisation de l' @Testannotation est la manière introduite par JUnit 4

En général, vous devez choisir le chemin de l'annotation, sauf si la compatibilité avec JUnit 3 (et / ou une version Java antérieure à Java 5) est nécessaire. La nouvelle méthode présente plusieurs avantages:

Pour tester les exceptions attendues dans un JUnit 3, TestCasevous devez rendre le texte explicite.

public void testMyException() {
  try {
    objectUnderTest.myMethod(EVIL_ARGUMENT);
    fail("myMethod did not throw an Exception!");
  } catch (MyException e) {
    // ok!
    // check for properties of exception here, if desired
  }
}

JUnit 5 a introduit un autre changement d'API, mais utilise toujours des annotations. La nouvelle @Testannotation est org.junit.jupiter.api.Test(l'ancienne "JUnit 4 l'était org.junit.Test), mais elle fonctionne à peu près de la même manière que celle de JUnit 4.

Joachim Sauer
la source
Réponse utile et approfondie, mais je ne comprends pas entièrement "vérifier le message d'exception". Vérifier par rapport à une chaîne codée en dur va être un cauchemar de maintenance. Vous devez avoir voulu dire "vérifier les propriétés de votre type d'exception spécifique".
thSoft
3
@thSoft: ce n'est pas souvent qu'il est utilisé, mais je veux parfois m'assurer que la méthode d'exception mentionne le champ incriminé, par exemple. Ensuite, un simple assertTrue(e.getMessage().contains("foo"))pourrait être utile.
Joachim Sauer le
1
Même dans JUnit4, c'est un idiome important lorsque vous devez vérifier le message ou une autre propriété de l'exception (telle que la cause). La expectedméthode vérifie uniquement le type.
Yishai
@Yishai: c'est vrai, mais la plupart du temps, je suis déjà satisfait si la méthode lance le type d'exception correct sur une entrée problématique.
Joachim Sauer
Pour cette raison, JUnit 5 a apporté un changement radical dans les tests d'exception. assertThrows () est fantastique :-)
Marcus K.
25

J'ai une préférence pour JUnit 4 (approche d'annotation) car je la trouve plus flexible.

Si vous souhaitez créer une suite de tests dans JUnit 4, vous devez créer une classe regroupant tous les tests comme ceci:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;


@RunWith(Suite.class)
@SuiteClasses({
    Test1.class,
    Test2.class,
    Test3.class,
    Test4.class
})public class TestSuite
{
 /* empty class */
}
Grimmy
la source
15

Il y a une partie sans réponse à votre question, à savoir "Quelle est la bonne façon de regrouper les tests pour l'approche B?"

La réponse officielle est que vous annotez une classe avec un @RunWith (Suite.class), puis utilisez l'annotation @ Suite.SuiteClasses pour répertorier les classes. C'est ainsi que les développeurs JUnit le font (listant chaque classe dans une suite manuellement). À bien des égards, cette approche est une amélioration, en ce sens qu'il est trivial et intuitif d'ajouter des comportements avant et après suite (ajoutez simplement une méthode @BeforeClass et @AfterClass à la classe annotée avec @RunWith - bien mieux que l'ancien TestFixture ).

Cependant, il y a un pas en arrière, dans la mesure où les annotations ne vous permettent pas de créer dynamiquement la liste des classes, et contourner ce problème devient un peu laid. Vous devez sous-classer la classe Suite et créer dynamiquement le tableau de classes dans la sous-classe et le transmettre au constructeur Suite, mais c'est une solution incomplète dans la mesure où les autres sous-classes de Suite (telles que Catégories) ne fonctionnent pas avec elle et essentiellement ne prennent pas en charge la collection de classes de test dynamique.

Yishai
la source
1
+1 pour cela. Après m'être lancé dans une tâche d'écriture d'une solution dynamique pour ajouter des tests à une TestSuite, j'ai dû étendre TestCase dans chacun de mes tests. Cela a à son tour interrompu les tests unitaires de travail qui utilisaient des annotations JUnit4 pour définir les exceptions attendues. Ma recherche d'un moyen de peupler dynamiquement une suite de tests m'a conduit à ce fil de discussion, et plus précisément à votre réponse, qui, je crois, est l'une des rares raisons souhaitables restantes de continuer avec JUnit 3.
8bitjunkie
4

Vous devriez utiliser JUnit 4. C'est mieux.

De nombreux frameworks ont commencé à rendre obsolète la prise en charge de JUnit 3.8.

Ceci provient de la documentation de référence de Spring 3.0:

[Avertissement] La hiérarchie des classes Legacy JUnit 3.8 est obsolète

En général, vous devriez toujours essayer d'utiliser la dernière version stable d'un framework lorsque vous démarrez quelque chose de nouveau.

Espen
la source
1
  1. L'approche "préférée" serait d'utiliser des annotations qui ont été introduites depuis juin 4. Elles facilitent beaucoup de choses (voir votre deuxième question)

  2. Vous pouvez utiliser un simple bloc try / catch pour cela:


public void testForException() {
    try {
        Integer.parseInt("just a string");
        fail("Exception should have been thrown");
    } catch (final Exception e) {
        // expected
    }
}
noir666
la source