В каком порядке вызывается Junit @ Before / @ After?

133

У меня есть набор интеграционных тестов. У меня есть IntegrationTestBaseкласс для расширения всех моих тестов. Этот базовый класс имеет методы @Before( public void setUp()) и @After( public void tearDown()) для установления соединений API и БД. Я просто переопределяю эти два метода в каждом тестовом примере и вызываю super.setUp()и super.tearDown(). Однако это может вызвать проблемы, если кто-то забывает вызвать super или помещает их в неправильное место, и возникает исключение, и они забывают вызвать super в finally или еще что-то.

То , что я хочу сделать , это сделать setUpи tearDownметоды в базовом классе , finalа затем просто добавить свою собственную аннотированную @Beforeи @Afterметоду. При выполнении некоторых начальных тестов кажется, что он всегда вызывает в следующем порядке:

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

но я немного обеспокоен тем, что порядок не гарантирован и может вызвать проблемы. Я огляделась и ничего не нашла по этой теме. Кто-нибудь знает, смогу ли я это сделать и не будет никаких проблем?

Код:

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
источник
1
Является ли MyTestотсутствует знак extends?
aioobe 06
@aioobe: больше нет :)
Джоэл

Ответы:

135

Да, такое поведение гарантировано:

@Before:

В @Beforeметодах суперкласса будут выполняться до тех текущего класса, если они не будут переопределены в текущем классе. Никакой другой порядок не определен.

@After:

Эти @Afterметоды , объявленные в суперклассе будут работать после того, как те из текущего класса, если они не будут переопределены в текущем классе.

axtavt
источник
15
Чтобы было ясно, порядок выполнения всех @Beforeметодов не гарантируется. Если есть 10 @Beforeметодов, каждый из них может выполняться в любом порядке; непосредственно перед любым другим методом.
Свати,
5
Так что вместо того, чтобы цитировать несколько двусмысленную документацию, не могли бы вы объяснить это своими словами? Выполняются ли методы @Beforeи @Afterперед каждым другим методом класса (один раз для каждого метода) или непосредственно перед и после всего набора методов класса (один раз для каждого класса)?
BT
5
См. Важную уловку, заявленную Джоном Q Citizen: «это применимо только в том случае, если каждый метод, отмеченный @Before, имеет уникальное имя в иерархии классов» Очень важно помнить!
Бруно Босола
У меня возник конфликт имен при использовании того же имени метода в методе @Before (d) в классе и другом методе в его суперклассе, в junit-4.12.
Стефан
Применяется ли это правило также к методам @BeforeExample ConcordionRunner?
Адриан Пронк
51

Одна потенциальная проблема, которая укусила меня раньше:

Мне нравится иметь не более одного @Beforeметода в каждом тестовом классе, потому что порядок выполнения @Beforeметодов, определенных в классе, не гарантируется. Обычно я назову такой метод setUpTest().

Но, хотя @Beforeэто задокументировано как The @Before methods of superclasses will be run before those of the current class. No other ordering is defined., это применимо только в том случае, если каждый метод, отмеченный значком, @Beforeимеет уникальное имя в иерархии классов.

Например, у меня было следующее:

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

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

Раньше я ожидал AbstractFooTest.setUpTest()бежать FooTest.setUpTest(), но только FooTest.setupTest()был казнен. AbstractFooTest.setUpTest()вообще не звонили.

Для работы код необходимо изменить следующим образом:

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() {
    super.setUpTest();
    ...
  }
}
Джон Кью Ситизен
источник
Почему бы просто не изменить имя метода @Before в базовом классе? Это избавит вас от необходимости называть супер всех детей ... в любом случае, хороший улов с тем же вопросом имен
Лоуренс Тирни
24
Просто замечание для большей безопасности: чтобы избежать конфликтов имен, вы можете сделать @Before/ @Afterметод (ы) в базовом классе final, поэтому компилятор будет жаловаться, если вы (случайно) попытаетесь переопределить их в подклассе.
Стефан Винклер,
4
Не запускаемый родительский метод с таким же именем не похож на поведение JUnit. Похоже на то, как в ООП работает базовое переопределение. Родительский метод практически не существует во время выполнения. Ребенок заменяет его для всех намерений и целей. Так работает Java.
Брэндон
1
Еще одна проблема заключается в том, что родительские классы должны быть общедоступными, иначе их @Beforeотмеченные методы будут проигнорированы, если у подкласса также есть @Beforeметод.
rusins
21

Я думаю, что на основе документации @Beforeи @Afterправильным выводом будет дать методам уникальные имена. В своих тестах я использую следующий шаблон:

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

и

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

дать в результате

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

Преимущество этого подхода: пользователи класса AbstractBaseTest не могут случайно переопределить методы setUp / tearDown. Если они хотят, им нужно знать точное имя и они могут это сделать.

(Незначительный) недостаток этого подхода: пользователи не видят, что что-то происходит до или после их setUp / tearDown. Им нужно знать, что эти вещи предоставляются абстрактным классом. Но я предполагаю, что причина, по которой они используют абстрактный класс

Маттиас Хёфель
источник
2
хороший пример - был бы еще более наглядным, если бы у вас было два метода @Test, поэтому можно увидеть, что setUp и tearDown обертывают каждый тестовый метод.
Марк
Я думаю, что это основа для лучшего ответа на OP, но вы должны заполнить свой ответ автономно. Не могли бы вы дополнить свой пример, чтобы охватить альтернативы, предложенные другими, и объяснить, почему ваше предложение лучше?
wachr
2

Если вы все измените, вы можете объявить абстрактный базовый класс, а потомки объявить методы setUp и tearDown (без аннотаций), которые вызываются в аннотированных методах setUp и tearDown базового класса.

Buhb
источник
1
неплохая идея, но я не хочу навязывать контракт на тесты, которые не нуждаются в их собственном setUp / tearDown
Джоэл
2

Вы можете использовать @BeforeClassаннотацию, чтобы гарантировать, что setup()всегда вызывается первым. Точно так же вы можете использовать @AfterClassаннотацию, чтобы гарантировать, что tearDown()всегда вызывается последней.

Обычно это не рекомендуется, но поддерживается .

Это не совсем то, что вы хотите, но по сути это будет держать ваше соединение с БД открытым все время выполнения ваших тестов, а затем закрыть его раз и навсегда в конце.

Свати
источник
2
На самом деле, если бы вы сделали это, я бы порекомендовал создать метод setupDB()и closeDB()пометить их с помощью @BeforeClassи @AfterClassи заменить ваши методы до / после с помощью setup()иtearDown()
Swati
Методы с пометкой @BeforeClassи @AfterClassдолжны быть статическими. А как насчет случая, когда мы хотим использовать переменные экземпляра внутри этих методов?
Pratik Singhal
Предупреждение при использовании @BeforeClassс Powermock: работает только при первом тестовом запуске. См. Этот выпуск: github.com/powermock/powermock/issues/398
Dagmar
2

Это не ответ на вопрос с лозунгом, но это ответ на проблемы, упомянутые в теле вопроса. Вместо использования @Before или @After попробуйте использовать @ org.junit.Rule, потому что это дает вам больше гибкости. ExternalResource (начиная с версии 4.7) - это правило, которое вас больше всего заинтересует, если вы управляете соединениями. Кроме того, если вам нужен гарантированный порядок выполнения ваших правил, используйте RuleChain ( начиная с 4.10). Я считаю, что все они были доступны, когда был задан этот вопрос. Пример кода ниже скопирован из javadocs 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
источник