Какой порядок называется Junit @Before/@After?

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

Что я хочу сделать, это сделать методы 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?* }
}

Ответ 1

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

@Before:

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

@After:

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

Ответ 2

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

Мне нравится иметь не более одного метода @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();
    ...
  }
}

Ответ 3

Я думаю, что на основе документации @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. Им нужно знать, что эти вещи предоставляются абстрактным классом. Но я предполагаю, что причина, по которой они используют абстрактный класс

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

Это не ответ на вопрос с тегом, но это ответ на проблемы, упомянутые в теле вопроса. Вместо использования @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);
     }
 }