In welcher Reihenfolge werden die Junit @ Before / @ After aufgerufen?

133

Ich habe eine Integration Test Suite. Ich habe eine IntegrationTestBaseKlasse für alle meine Tests zu erweitern. Diese Basisklasse verfügt über die Methoden @Before( public void setUp()) und @After( public void tearDown()) zum Herstellen von API- und DB-Verbindungen. Was ich habe tut zwingende nur diese beiden Methoden in jedem Testfall und ruft super.setUp()und super.tearDown(). Dies kann jedoch zu Problemen führen, wenn jemand vergisst, den Super anzurufen, oder ihn an die falsche Stelle setzt und eine Ausnahme ausgelöst wird und er vergisst, den Super im Endlich oder so anzurufen.

Was ich tun möchte, ist, die Methoden setUpund tearDownin der Basisklasse zu erstellen finalund dann einfach unsere eigenen Anmerkungen @Beforeund @AfterMethoden hinzuzufügen . Bei einigen ersten Tests scheint es immer in dieser Reihenfolge aufzurufen:

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

aber ich bin nur ein wenig besorgt, dass die Bestellung nicht garantiert ist und dass es Probleme verursachen könnte. Ich habe mich umgesehen und nichts zu diesem Thema gesehen. Weiß jemand, ob ich das kann und keine Probleme habe?

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?* }
}
Joel
quelle
1
Ist das MyTestfehlt ein extends?
Aioobe
@aioobe: nicht mehr :)
Joel

Antworten:

135

Ja, dieses Verhalten ist garantiert:

@Before::

Die @BeforeMethoden von Oberklassen werden vor denen der aktuellen Klasse ausgeführt, sofern sie in der aktuellen Klasse nicht überschrieben werden. Es ist keine andere Reihenfolge definiert.

@After::

Die @Afterin Oberklassen deklarierten Methoden werden nach denen der aktuellen Klasse ausgeführt, sofern sie in der aktuellen Klasse nicht überschrieben werden.

axtavt
quelle
15
Um klar zu sein, ist die Ausführungsreihenfolge aller @BeforeMethoden nicht garantiert. Wenn es 10 @BeforeMethoden gibt, kann jede in beliebiger Reihenfolge ausgeführt werden. kurz vor jeder anderen Methode.
Swati
5
Können Sie die etwas mehrdeutige Dokumentation nicht in eigenen Worten erklären, anstatt sie zu zitieren? Werden @Beforeund @AfterMethoden vor jeder anderen Klassenmethode (einmal pro Methode) oder kurz vor und nach der gesamten Suite von Klassenmethoden (einmal pro Klasse) ausgeführt?
BT
5
Siehe den wichtigen Haken von John Q Citizen: "Dies gilt nur, wenn jede mit @Before gekennzeichnete Methode einen eindeutigen Namen in der Klassenhierarchie hat." Sehr wichtig zu beachten!
Bruno Bossola
Ich hatte einen Namenskonflikt mit demselben Methodennamen bei einer @ Before (d) -Methode in einer Klasse und einer anderen Methode in ihrer Superklasse junit-4.12.
Stephane
Gilt diese Regel auch für @ BeforeExample-Methoden von ConcordionRunner?
Adrian Pronk
51

Ein möglicher Fall, der mich schon einmal gebissen hat:

Ich möchte höchstens eine @BeforeMethode in jeder Testklasse haben, da die Reihenfolge der Ausführung der @Beforein einer Klasse definierten Methoden nicht garantiert ist. Normalerweise werde ich eine solche Methode aufrufen setUpTest().

Obwohl dies @Beforeals dokumentiert ist The @Before methods of superclasses will be run before those of the current class. No other ordering is defined., gilt dies nur, wenn jede mit gekennzeichnete Methode @Beforeeinen eindeutigen Namen in der Klassenhierarchie hat.

Zum Beispiel hatte ich Folgendes:

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

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

Ich hatte erwartet AbstractFooTest.setUpTest(), vorher zu laufen FooTest.setUpTest(), wurde aber nur FooTest.setupTest()ausgeführt. AbstractFooTest.setUpTest()wurde überhaupt nicht angerufen.

Der Code muss wie folgt geändert werden, damit er funktioniert:

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() {
    super.setUpTest();
    ...
  }
}
John Q Bürger
quelle
Warum nicht einfach den Namen der @ Before-Methode in der Basisklasse ändern? Dies erspart Ihnen, dass Sie bei allen Kindern Super anrufen müssen ... trotzdem ein guter Fang mit dem gleichen Namen
Lawrence Tierney,
24
Nur eine Bemerkung, um die Sicherheit zu erhöhen: Um Namenskonflikte zu vermeiden, können Sie die @Before/ @After-Methode (n) in der Basisklasse erstellen final, sodass sich der Compiler beschwert, wenn Sie (versehentlich) versuchen, sie in der Unterklasse zu überschreiben.
Stefan Winkler
4
Die übergeordnete Methode mit demselben Namen, die nicht ausgeführt wird, klingt nicht nach einem JUnit-Verhalten. Das klingt so, wie grundlegendes Übersteuern in OOP funktioniert. Die übergeordnete Methode existiert zur Laufzeit grundsätzlich nicht. Das Kind ersetzt es in jeder Hinsicht. So funktioniert Java.
Brandon
1
Ein weiteres Problem ist, dass die übergeordneten Klassen öffentlich sein müssen, andernfalls werden ihre @Beforemarkierten Methoden ignoriert, wenn eine Unterklasse auch eine @BeforeMethode hat.
Rusins
21

Ich denke, basierend auf der Dokumentation der @Beforeund @Afterder richtigen Schlussfolgerung ist es, den Methoden eindeutige Namen zu geben. Ich verwende das folgende Muster in meinen 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");
  }
}

und

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

als Ergebnis geben

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

Vorteil dieses Ansatzes: Benutzer der AbstractBaseTest-Klasse können die setUp / tearDown-Methoden nicht versehentlich überschreiben. Wenn sie wollen, müssen sie den genauen Namen kennen und können es tun.

(Kleiner) Nachteil dieses Ansatzes: Benutzer können nicht sehen, dass vor oder nach ihrem SetUp / TearDown etwas passiert. Sie müssen wissen, dass diese Dinge von der abstrakten Klasse bereitgestellt werden. Aber ich nehme an, das ist der Grund, warum sie die abstrakte Klasse verwenden

Matthias Hoefel
quelle
2
schönes Beispiel - wäre noch anschaulicher, wenn Sie zwei @ Test-Methoden hätten, so dass ersichtlich ist, dass setUp und tearDown jede Testmethode umschließen .
Mark
Ich denke, dies ist die Grundlage für die beste Antwort auf das OP, aber Sie sollten Ihre Antwort auf Standalone eingeben. Könnten Sie Ihr Beispiel erweitern, um die Alternativen abzudecken, die auch andere vorgeschlagen haben, und erklären, warum Ihr Vorschlag überlegen ist?
Wachr
2

Wenn Sie die Dinge umdrehen, können Sie Ihre Basisklasse als abstrakt deklarieren und Nachkommen setUp- und tearDown-Methoden (ohne Anmerkungen) deklarieren lassen, die in den kommentierten setUp- und tearDown-Methoden der Basisklasse aufgerufen werden.

Buhb
quelle
1
Keine schlechte Idee, aber ich möchte keinen Vertrag für die Tests durchsetzen, die kein eigenes SetUp / TearDown benötigen
Joel
2

Sie können @BeforeClassAnmerkungen verwenden, um sicherzustellen, dass diese setup()immer zuerst aufgerufen werden. Ebenso können Sie @AfterClassAnmerkungen verwenden, um sicherzustellen, dass diese tearDown()immer als letzte aufgerufen werden.

Dies wird normalerweise nicht empfohlen, aber unterstützt .

Es ist nicht genau das, was Sie wollen - aber es hält Ihre DB-Verbindung im Wesentlichen während der gesamten Laufzeit Ihrer Tests offen und schließt sie am Ende ein für alle Mal.

Swati
quelle
2
Eigentlich, wenn Sie dies tun, würde ich empfehlen , ein Verfahren zu schaffen setupDB()und closeDB()und markiert sie mit @BeforeClassund @AfterClassund den Austausch Ihre vor / nach Methoden setup()undtearDown()
Swati
Methoden, die mit statischen Anmerkungen versehen sind @BeforeClassund @AfterClassstatisch sein müssen. Was ist mit dem Fall, wenn wir Instanzvariablen innerhalb dieser Methoden verwenden möchten?
Pratik Singhal
Eine Warnung bei Verwendung @BeforeClassmit Powermock: Sie funktioniert nur beim ersten Testlauf. Siehe diese Ausgabe: github.com/powermock/powermock/issues/398
Dagmar
2

Dies ist keine Antwort auf die Slogan-Frage, aber eine Antwort auf die im Hauptteil der Frage genannten Probleme. Anstatt @Before oder @After zu verwenden, sollten Sie @ org.junit.Rule verwenden, da dies Ihnen mehr Flexibilität bietet. ExternalResource (ab 4.7) ist die Regel, an der Sie am meisten interessiert sind, wenn Sie Verbindungen verwalten. Wenn Sie eine garantierte Ausführungsreihenfolge Ihrer Regeln wünschen, verwenden Sie eine RuleChain (ab 4.10). Ich glaube, all diese waren verfügbar, als diese Frage gestellt wurde. Das folgende Codebeispiel wird aus den Javadocs von ExternalResource kopiert.

 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
quelle