W jakiej kolejności są wywoływane Junit @ Before / @ After?

138

Mam pakiet testów integracji. Mam IntegrationTestBaseklasę na wszystkie moje testy do rozszerzenia. Ta klasa bazowa ma metody @Before( public void setUp()) i @After( public void tearDown()) do nawiązywania połączeń API i DB. To, co robiłem, to po prostu zastąpienie tych dwóch metod w każdym przypadku testowym i wywołanie super.setUp()i super.tearDown(). Może to jednak powodować problemy, jeśli ktoś zapomni zadzwonić do super lub umieści go w niewłaściwym miejscu i zostanie wyrzucony wyjątek i zapomni w końcu zadzwonić do super.

Chcę tylko utworzyć metody setUpi tearDownw klasie bazowej, finala następnie po prostu dodać własne metody @Beforei adnotacje @After. Podczas wykonywania wstępnych testów wydaje się, że zawsze wywołuje w następującej kolejności:

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

ale trochę się martwię, że zamówienie nie jest gwarantowane i może powodować problemy. Rozejrzałem się i nie widziałem nic na ten temat. Czy ktoś wie, czy mogę to zrobić i nie mam żadnych problemów?

Kod:

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
źródło
1
Czy MyTestbrakuje extends?
aioobe
@aioobe: już nie :)
Joel

Odpowiedzi:

138

Tak, to zachowanie jest gwarantowane:

@Before:

Te @Beforemetody superklas zostanie uruchomiony przed tymi z bieżącej klasy, chyba że są one zastępowane w obecnej klasie. Żadna inna kolejność nie jest zdefiniowana.

@After:

Te @Aftermetody zadeklarowane w superklas zostanie uruchomiony po tych z bieżącej klasy, chyba że są one zastępowane w obecnej klasie.

axtavt
źródło
15
Żeby było jasne, kolejność wykonania wszystkich @Beforemetod nie jest gwarantowana. Jeśli istnieje 10 @Beforemetod, każdą z nich można wykonać w dowolnej kolejności; tuż przed jakąkolwiek inną metodą.
Swati
5
Więc zamiast cytować nieco niejednoznaczną dokumentację, czy możesz wyjaśnić to własnymi słowami? Czy metody @Beforei @Aftersą uruchamiane przed każdą inną metodą klasową (raz na metodę), czy tuż przed i po całym zestawie metod klasowych (raz na klasę)?
BT,
6
Zobacz ważny haczyk podany przez Johna Q Citizen: „ma to zastosowanie tylko wtedy, gdy każda metoda oznaczona znakiem @Before ma unikalną nazwę w hierarchii klas”. Bardzo ważne do zapamiętania!
Bruno Bossola,
Wystąpił konflikt nazw przy użyciu tej samej nazwy metody w metodzie @Before (d) w klasie i innej metody w jej super klasie, on junit-4.12.
Stephane,
Czy ta reguła dotyczy również metod @BeforeExample programu ConcordionRunner?
Adrian Pronk
52

Jeden potencjalny chwyt, który mnie wcześniej ugryzł:

Lubię mieć co najwyżej jedną @Beforemetodę w każdej klasie testowej, ponieważ kolejność uruchamiania @Beforemetod zdefiniowanych w klasie nie jest gwarantowana. Zwykle nazwę taką metodę setUpTest().

Ale chociaż @Beforejest to udokumentowane jako The @Before methods of superclasses will be run before those of the current class. No other ordering is defined., ma to zastosowanie tylko wtedy, gdy każda metoda oznaczona @Beforema unikalną nazwę w hierarchii klas.

Na przykład miałem:

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

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

Spodziewałem AbstractFooTest.setUpTest()się ucieczki wcześniej FooTest.setUpTest(), ale tylko FooTest.setupTest()został stracony. AbstractFooTest.setUpTest()nie został w ogóle wezwany.

Aby kod działał, należy zmodyfikować go w następujący sposób:

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() {
    super.setUpTest();
    ...
  }
}
John Q Citizen
źródło
Dlaczego po prostu nie zmienić nazwy metody @Before w klasie bazowej? Dzięki temu nie będziesz musiał dzwonić do super wszystkich dzieci ... w każdym razie dobry chwyt z tymi samymi nazwiskami
Lawrence Tierney
26
Tylko uwaga, aby uczynić wszystko bezpieczniejszym: aby uniknąć kolizji nazw, możesz utworzyć metodę @Before/ @Aftermetody w klasie bazowej final, więc kompilator będzie narzekał, jeśli (przypadkowo) spróbujesz zastąpić je w podklasie.
Stefan Winkler,
4
Nie uruchamiana metoda nadrzędna o tej samej nazwie nie brzmi jak zachowanie JUnit. To brzmi tak, jak działa podstawowy over-riding w OOP. Metoda nadrzędna w zasadzie nie istnieje w czasie wykonywania. Dziecko zastępuje go we wszystkich zamiarach i celach. Tak działa Java.
Brandon
1
Innym problemem jest to, że klasy nadrzędne muszą być publiczne, w przeciwnym razie ich @Beforezaznaczone metody zostaną zignorowane, jeśli podklasa również ma @Beforemetodę.
rusins
To jest teraz udokumentowane zachowanie. "metody nadklas będą uruchamiane przed metodami bieżącej klasy, chyba że zostaną nadpisane w bieżącej klasie . Żadna inna kolejność nie jest zdefiniowana" Pozwala to na posiadanie @Beforeinterfejsów mixin, ale także zapobiega ich wykonaniu, jeśli podano Override
iwat0qs
23

Myślę, że na podstawie dokumentacji @Beforei @Afterwłaściwym wnioskiem jest nadanie metodom unikalnych nazw. W swoich testach używam następującego wzoru:

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

i

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

dać w rezultacie

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

Zaleta tego podejścia: Użytkownicy klasy AbstractBaseTest nie mogą przez przypadek przesłonić metod setUp / tearDown. Jeśli chcą, muszą znać dokładną nazwę i mogą to zrobić.

(Niewielka) wada tego podejścia: Użytkownicy nie widzą, że coś dzieje się przed lub po ich setUp / tearDown. Muszą wiedzieć, że te rzeczy są dostarczane przez klasę abstrakcyjną. Ale zakładam, że to jest powód, dla którego używają klasy abstrakcyjnej

Matthias Hoefel
źródło
2
fajny przykład - byłby jeszcze bardziej obrazowy, gdybyś miał dwie metody @Test, więc widać, że setUp i tearDown zawijają każdą metodę testową.
Mark
Myślę, że to jest podstawa najlepszej odpowiedzi na PO, ale powinieneś wypełnić swoją odpowiedź na samodzielną odpowiedź. Czy mógłbyś rozszerzyć swój przykład, aby objąć alternatywne rozwiązania, które sugerowali inni, i wyjaśnić, dlaczego Twoja propozycja jest lepsza?
wachr
2

Jeśli zmienisz sytuację, możesz zadeklarować abstrakcję swojej klasy bazowej, a potomkowie zadeklarują metody setUp i tearDown (bez adnotacji), które są wywoływane w metodach setUp i tearDown klasy bazowej z adnotacjami.

Buhb
źródło
1
niezły pomysł, ale nie chcę egzekwować umowy na testy, które nie potrzebują własnego setUp / tearDown
Joel
2

Możesz użyć @BeforeClassadnotacji, aby upewnić się, że setup()zawsze jest wywoływana jako pierwsza. Podobnie, możesz użyć @AfterClassadnotacji, aby upewnić się, że tearDown()zawsze nazywana jest ostatnią.

Zwykle nie jest to zalecane, ale jest obsługiwane .

Nie jest to dokładnie to, czego chcesz - ale zasadniczo utrzyma połączenie z bazą danych otwarte przez cały czas trwania testów, a następnie zamknie je raz na zawsze na końcu.

Swati
źródło
2
Właściwie, gdybyś to zrobił, poleciłbym utworzenie metody setupDB()i closeDB()zaznaczenie ich za pomocą @BeforeClassi @AfterClassoraz zastąpienie metod przed / po setup()itearDown()
Swati
Metody z adnotacjami @BeforeClassi @AfterClassmuszą być statyczne. A co w przypadku, gdy chcemy użyć zmiennych instancji wewnątrz tych metod?
Pratik Singhal
Ostrzeżenie podczas używania @BeforeClassz Powermock: działa tylko podczas pierwszego uruchomienia testowego. Zobacz ten numer: github.com/powermock/powermock/issues/398
Dagmar
2

To nie jest odpowiedź na pytanie sloganowe, ale jest to odpowiedź na problemy wymienione w treści pytania. Zamiast używać @Before lub @After, przyjrzyj się używaniu @ org.junit.Rule, ponieważ zapewnia to większą elastyczność. Zasoby zewnętrzne (od 4.7) to reguła, którą będziesz najbardziej zainteresowany, jeśli zarządzasz połączeniami. Ponadto, jeśli chcesz mieć gwarantowaną kolejność wykonywania swoich reguł, użyj RuleChain (od 4.10). Sądzę, że wszystkie z nich były dostępne, gdy zadano to pytanie. Poniższy przykład kodu jest kopiowany z javadoców 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
źródło