Em que ordem os Junit @ Before / @ After são chamados?

133

Eu tenho um conjunto de testes de integração. Eu tenho uma IntegrationTestBaseaula para todos os meus testes estenderem. Essa classe base possui um método @Before( public void setUp()) e @After( public void tearDown()) para estabelecer conexões API e DB. O que venho fazendo é substituir esses dois métodos em cada caso de teste e chamar super.setUp()e super.tearDown(). No entanto, isso pode causar problemas se alguém esquecer de chamar o super ou colocá-lo no lugar errado e uma exceção for lançada e eles esquecerem de chamar super no finalmente ou algo assim.

O que eu quero fazer é criar os métodos setUpe tearDownna classe base finale, em seguida, basta adicionar nossos próprios métodos @Beforee anotados @After. Fazendo alguns testes iniciais, parece sempre chamar nesta ordem:

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

mas estou um pouco preocupado com o fato de o pedido não ser garantido e de causar problemas. Olhei em volta e não vi nada sobre o assunto. Alguém sabe se eu posso fazer isso e não tenho problemas?

Código:

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
fonte
1
O MyTestdesaparecido é um extends?
aioobe
@aioobe: not anymore :)
Joel

Respostas:

135

Sim, esse comportamento é garantido:

@Before:

Os @Beforemétodos das superclasses serão executados antes dos da classe atual, a menos que sejam substituídos na classe atual. Nenhuma outra ordem está definida.

@After:

Os @Aftermétodos declarados nas superclasses serão executados após os da classe atual, a menos que sejam substituídos na classe atual.

axtavt
fonte
15
Para ficar claro, a ordem de execução de todos os @Beforemétodos não é garantida. Se houver 10 @Beforemétodos, cada um deles pode ser executado em qualquer ordem; imediatamente antes de qualquer outro método.
Swati
5
Então, em vez de citar a documentação um tanto ambígua, você pode explicá-la com suas próprias palavras? Os métodos @Beforee são @Afterexecutados antes de qualquer outro método de classe (uma vez por método) ou imediatamente antes e depois de todo o conjunto de métodos de classe (uma vez por classe)?
BT
5
Veja a captura importante declarada por John Q Citizen: "isso se aplica apenas se cada método marcado com @Antes tiver um nome exclusivo na hierarquia de classes" É muito importante lembrar!
precisa saber é o seguinte
Eu tive um conflito de nome usando o mesmo nome de método em um método @Before (d) em uma classe e outro método em sua superclasse, on junit-4.12.
Stephane
Esta regra também se aplica aos métodos @BeforeExample do ConcordionRunner?
Adrian Pronk
51

Uma pegadinha em potencial que já me mordeu:

Eu gosto de ter no máximo um @Beforemétodo em cada classe de teste, porque a ordem de execução dos @Beforemétodos definidos em uma classe não é garantida. Normalmente, chamarei esse método setUpTest().

Mas, embora @Beforeesteja documentado como The @Before methods of superclasses will be run before those of the current class. No other ordering is defined., isso só se aplica se cada método marcado com @Beforetiver um nome exclusivo na hierarquia de classes.

Por exemplo, eu tive o seguinte:

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

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

Eu esperava AbstractFooTest.setUpTest()correr antes FooTest.setUpTest(), mas apenas FooTest.setupTest()foi executado. AbstractFooTest.setUpTest()não foi chamado.

O código deve ser modificado da seguinte maneira para funcionar:

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() {
    super.setUpTest();
    ...
  }
}
John Q Citizen
fonte
Por que não mudar apenas o nome do método @Before na classe base? Isso vai poupar você de ter de chamada para super em todas as crianças ... de qualquer maneira boa captura com os mesmos nomes emitir
Lawrence Tierney
24
Apenas uma observação para tornar as coisas mais seguras: Para evitar conflitos de nome, você pode criar os @Before/ @Aftermétodos na classe base final, para que o compilador se queixe se você (acidentalmente) tentar substituí-los na subclasse.
Stefan Winkler
4
O método pai com o mesmo nome que não está sendo executado não soa como um comportamento JUnit. Parece como funciona a substituição básica no POO. O método pai basicamente não existe no tempo de execução. A criança a substitui para todos os efeitos. É assim que o Java funciona.
Brandon
1
Outra dica é que as classes pai precisam ser públicas, caso contrário, seus @Beforemétodos marcados serão ignorados se uma subclasse também tiver um @Beforemétodo.
Rusins ​​29/07/19
21

Penso que, com base na documentação @Beforee @Afterna conclusão correta, é dar nomes únicos aos métodos. Eu uso o seguinte padrão nos meus testes:

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

e

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

dar como resultado

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

Vantagem dessa abordagem: os usuários da classe AbstractBaseTest não podem substituir os métodos setUp / tearDown por acidente. Se quiserem, precisam saber o nome exato e podem fazê-lo.

(Menor) desvantagem dessa abordagem: os usuários não conseguem ver que há coisas acontecendo antes ou depois de sua configuração / desmontagem. Eles precisam saber que essas coisas são fornecidas pela classe abstrata. Mas suponho que essa seja a razão pela qual eles usam a classe abstrata

Matthias Hoefel
fonte
2
bom exemplo - seria ainda mais ilustrativo se você tivesse dois métodos @Test, portanto, é possível observar que setUp e tearDown envolvem cada método de teste.
Mark
Acho que essa é a base para a melhor resposta ao OP, mas você deve preencher sua resposta de forma independente. Você poderia aumentar seu exemplo para abranger as alternativas sugeridas por outras pessoas e explicar por que sua proposta é superior?
Wachr 18/08
2

Se você inverter as coisas, poderá declarar o abstrato da classe base e os descendentes declararão os métodos setUp e tearDown (sem anotações) que são chamados nos métodos anotados setUp e tearDown da classe base.

Buhb
fonte
1
não é uma má idéia, mas eu não quero fazer cumprir um contrato sobre os testes que não precisa de sua própria configuração / tearDown
Joel
2

Você pode usar a @BeforeClassanotação para garantir que setup()sempre seja chamado primeiro. Da mesma forma, você pode usar a @AfterClassanotação para garantir que tearDown()sempre seja chamado por último.

Isso geralmente não é recomendado, mas é suportado .

Não é exatamente o que você deseja - mas essencialmente manterá sua conexão com o banco de dados aberta o tempo todo em que seus testes estiverem sendo executados e depois fechá-la de uma vez por todas no final.

Swati
fonte
2
Na verdade, se você fosse fazer isso, eu recomendo a criação de um método setupDB()e closeDB()e marcando-as com @BeforeClasse @AfterClasse substituir o seu / antes depois métodos com setup()etearDown()
Swati
Métodos anotados @BeforeClasse @AfterClassprecisam ser estáticos. E o caso, quando queremos usar variáveis ​​de instância dentro desses métodos?
Pratik Singhal
Um aviso ao usar @BeforeClasscom o Powermock: ele funciona apenas na primeira execução de teste. Veja esta edição: github.com/powermock/powermock/issues/398
Dagmar
2

Esta não é uma resposta para a pergunta do slogan, mas é uma resposta para os problemas mencionados no corpo da pergunta. Em vez de usar @Before ou @After, analise o uso de @ org.junit.Rule, pois oferece mais flexibilidade. ExternalResource (a partir de 4.7) é a regra na qual você estará mais interessado se estiver gerenciando conexões. Além disso, se você deseja garantir a ordem de execução de suas regras, use um RuleChain (a partir da versão 4.10). Acredito que todos eles estavam disponíveis quando essa pergunta foi feita. O exemplo de código abaixo é copiado dos javadocs do 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
fonte