Dans quel ordre les Junit @ Before / @ After sont-ils appelés?

133

J'ai une suite de tests d'intégration. J'ai une IntegrationTestBaseclasse pour étendre tous mes tests. Cette classe de base a une méthode @Before( public void setUp()) et @After( public void tearDown()) pour établir des connexions API et DB. Ce que j'ai fait, c'est simplement remplacer ces deux méthodes dans chaque cas de test et appeler super.setUp()et super.tearDown(). Cependant, cela peut causer des problèmes si quelqu'un oublie d'appeler le super ou le met au mauvais endroit et qu'une exception est levée et qu'il oublie d'appeler super finalement ou quelque chose comme ça.

Ce que je veux faire, c'est créer les méthodes setUpand tearDownsur la classe de base final, puis ajouter simplement nos propres méthodes @Beforeand annotées @After. En faisant quelques tests initiaux, il semble toujours appeler dans cet ordre:

Base @Before
Test @Before
Test
Test @After
Base @After

mais je suis juste un peu inquiet que la commande ne soit pas garantie et qu'elle puisse causer des problèmes. J'ai regardé autour de moi et je n'ai rien vu sur le sujet. Est-ce que quelqu'un sait si je peux le faire et si je n'ai aucun problème?

Code:

public class IntegrationTestBase {

    @Before
    public final void setUp() { *always called 1st?* }

    @After
    public final void tearDown() { *always called last?* }
}


public class MyTest extends IntegrationTestBase {

    @Before
    public final void before() { *always called 2nd?* }

    @Test
    public void test() { *always called 3rd?* }

    @After
    public final void after() { *always called 4th?* }
}
Joël
la source
1
Le MyTestmanquant est- il un extends?
aioobe
@aioobe: pas plus :)
Joel

Réponses:

135

Oui, ce comportement est garanti:

@Before:

Les @Beforeméthodes des superclasses seront exécutées avant celles de la classe courante, sauf si elles sont remplacées dans la classe courante. Aucun autre ordre n'est défini.

@After:

Les @Afterméthodes déclarées dans les superclasses seront exécutées après celles de la classe courante, sauf si elles sont remplacées dans la classe courante.

axtavt
la source
15
Pour être clair, l'ordre d'exécution de toutes les @Beforeméthodes n'est pas garanti. S'il y a 10 @Beforeméthodes, chacune d'elles peut être exécutée dans n'importe quel ordre; juste avant toute autre méthode.
Swati
5
Alors au lieu de citer la documentation quelque peu ambiguë, pouvez-vous l'expliquer avec vos propres mots? Les méthodes @Beforeet sont -elles @Afterexécutées avant chaque autre méthode de classe (une fois par méthode), ou juste avant et après toute la suite de méthodes de classe (une fois par classe)?
BT
5
Voir la capture importante déclarée par John Q Citizen: "cela ne s'applique que si chaque méthode marquée avec @Before a un nom unique dans la hiérarchie des classes" Très important à retenir!
Bruno Bossola
J'ai eu un conflit de nom en utilisant le même nom de méthode sur une méthode @Before (d) dans une classe et une autre méthode dans sa super classe, on junit-4.12.
Stephane
Cette règle s'applique-t-elle également aux méthodes @BeforeExample de ConcordionRunner?
Adrian Pronk
51

Un gotcha potentiel qui m'a mordu auparavant:

J'aime avoir au plus une @Beforeméthode dans chaque classe de test, car l'ordre d'exécution des @Beforeméthodes définies dans une classe n'est pas garanti. En règle générale, j'appellerai une telle méthode setUpTest().

Mais, bien que @Beforedocumenté comme The @Before methods of superclasses will be run before those of the current class. No other ordering is defined., cela ne s'applique que si chaque méthode marquée avec @Beforea un nom unique dans la hiérarchie des classes.

Par exemple, j'avais ce qui suit:

public class AbstractFooTest {
  @Before
  public void setUpTest() { 
     ... 
  }
}

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() { 
    ...
  }
}

Je m'attendais AbstractFooTest.setUpTest()à courir avant FooTest.setUpTest(), mais seulement j'ai FooTest.setupTest()été exécuté. AbstractFooTest.setUpTest()n'a pas été appelé du tout.

Le code doit être modifié comme suit pour fonctionner:

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() {
    super.setUpTest();
    ...
  }
}
Citoyen John Q
la source
Pourquoi ne pas simplement changer le nom de la méthode @Before dans la classe de base? Cela vous évitera d'avoir à appeler à super dans tous les enfants ... de toute façon bonne prise avec le même problème de noms
Lawrence Tierney
24
Juste une remarque pour rendre les choses plus sûres: pour éviter les conflits de noms, vous pouvez créer la @Beforeou les @Afterméthodes / dans la classe de base final, ainsi le compilateur se plaindra si vous essayez (accidentellement) de les remplacer dans la sous-classe.
Stefan Winkler
4
La méthode parente du même nom qui n'est pas exécutée ne ressemble pas à un comportement JUnit. Cela ressemble à la façon dont le dépassement de base fonctionne en POO. La méthode parente n'existe pas au moment de l'exécution. L'enfant le remplace à toutes fins utiles. C'est ainsi que fonctionne Java.
Brandon
1
Un autre problème est que les classes parentes doivent être publiques, sinon leurs @Beforeméthodes marquées seront ignorées si une sous-classe a également une @Beforeméthode.
rusins ​​le
21

Je pense que sur la base de la documentation de la @Beforeet @Afterla bonne conclusion est de donner aux méthodes des noms uniques. J'utilise le modèle suivant dans mes tests:

public abstract class AbstractBaseTest {

  @Before
  public final void baseSetUp() { // or any other meaningful name
    System.out.println("AbstractBaseTest.setUp");
  }

  @After
  public final void baseTearDown() { // or any other meaningful name
    System.out.println("AbstractBaseTest.tearDown");
  }
}

et

public class Test extends AbstractBaseTest {

  @Before
  public void setUp() {
    System.out.println("Test.setUp");
  }

  @After
  public void tearDown() {
    System.out.println("Test.tearDown");
  }

  @Test
  public void test1() throws Exception {
    System.out.println("test1");
  }

  @Test
  public void test2() throws Exception {
    System.out.println("test2");
  }
}

donner en conséquence

AbstractBaseTest.setUp
Test.setUp
test1
Test.tearDown
AbstractBaseTest.tearDown
AbstractBaseTest.setUp
Test.setUp
test2
Test.tearDown
AbstractBaseTest.tearDown

Avantage de cette approche: les utilisateurs de la classe AbstractBaseTest ne peuvent pas remplacer les méthodes setUp / tearDown par accident. S'ils le souhaitent, ils doivent connaître le nom exact et peuvent le faire.

Inconvénient (mineur) de cette approche: les utilisateurs ne peuvent pas voir qu'il se passe des choses avant ou après leur configuration / déchirement. Ils ont besoin de savoir que ces éléments sont fournis par la classe abstraite. Mais je suppose que c'est la raison pour laquelle ils utilisent la classe abstraite

Matthias Hoefel
la source
2
bel exemple - serait encore plus illustratif si vous aviez deux méthodes @Test, donc on peut voir que setUp et tearDown enveloppent chaque méthode de test.
Mark
Je pense que c'est la base de la meilleure réponse au PO, mais vous devriez remplir votre réponse de manière autonome. Pourriez-vous compléter votre exemple pour couvrir les alternatives que d'autres ont suggérées, et expliquer pourquoi votre proposition est supérieure?
wachr
2

Si vous changez les choses, vous pouvez déclarer votre résumé de classe de base et faire en sorte que les descendants déclarent les méthodes setUp et tearDown (sans annotations) qui sont appelées dans les méthodes annotées setUp et tearDown de la classe de base.

Buhb
la source
1
pas une mauvaise idée, mais je ne veux pas appliquer un contrat sur les tests qui n'ont pas besoin de leur propre setUp / tearDown
Joel
2

Vous pouvez utiliser l' @BeforeClassannotation pour vous assurer qu'elle setup()est toujours appelée en premier. De même, vous pouvez utiliser l' @AfterClassannotation pour vous assurer qu'elle tearDown()est toujours appelée en dernier.

Ce n'est généralement pas recommandé, mais cela est pris en charge .

Ce n'est pas exactement ce que vous voulez - mais cela gardera essentiellement votre connexion DB ouverte tout le temps que vos tests sont en cours d'exécution, puis la fermera une fois pour toutes à la fin.

Swati
la source
2
En fait, si vous deviez faire cela, je vous recommanderais de créer une méthode setupDB()et de les closeDB()marquer avec @BeforeClasset @AfterClasset de remplacer vos méthodes avant / après par setup()ettearDown()
Swati
Les méthodes annotées @BeforeClasset @AfterClassdoivent être statiques. Qu'en est-il du cas, lorsque nous voulons utiliser des variables d'instance à l'intérieur de ces méthodes?
Pratik Singhal
Un avertissement lors de l'utilisation @BeforeClassavec Powermock: cela ne fonctionne que pour le premier test. Voir ce numéro: github.com/powermock/powermock/issues/398
Dagmar
2

Ce n'est pas une réponse à la question du slogan, mais c'est une réponse aux problèmes mentionnés dans le corps de la question. Au lieu d'utiliser @Before ou @After, envisagez d'utiliser @ org.junit.Rule car cela vous donne plus de flexibilité. ExternalResource (à partir de 4.7) est la règle qui vous intéressera le plus si vous gérez des connexions. De plus, si vous voulez un ordre d'exécution garanti de vos règles, utilisez une RuleChain (à partir de 4.10). Je crois que tous ces éléments étaient disponibles lorsque cette question a été posée. L'exemple de code ci-dessous est copié à partir des javadocs d'ExternalResource.

 public static class UsesExternalResource {
  Server myServer= new Server();

  @Rule
  public ExternalResource resource= new ExternalResource() {
      @Override
      protected void before() throws Throwable {
          myServer.connect();
         };

      @Override
      protected void after() {
          myServer.disconnect();
         };
     };

  @Test
  public void testFoo() {
      new Client().run(myServer);
     }
 }
successhawk
la source