@BeforeClass e herança - ordem de execução

90

Eu tenho uma classe base abstrata, que uso como base para meus testes de unidade (TestNG 5.10). Nesta classe, eu inicializo todo o ambiente para meus testes, configurando mapeamentos de banco de dados, etc. Esta classe abstrata possui um método com uma @BeforeClassanotação que faz a inicialização.

Em seguida, eu estendo essa classe com classes específicas nas quais tenho @Testmétodos e também @BeforeClassmétodos. Esses métodos fazem a inicialização específica da classe do ambiente (por exemplo, colocar alguns registros no banco de dados).

Como posso impor uma ordem específica dos @BeforeClassmétodos anotados? Preciso que os da classe base abstrata sejam executados antes dos da classe extensível.

Exemplo:

abstract class A {
    @BeforeClass
    doInitialization() {...}
}

class B extends A {
    @BeforeClass
    doSpecificInitialization() {...}

    @Test
    doTests() {...}
}

Pedido esperado:

A.doInitialization
B.doSpecificInitialization
B.doTests

Ordem real:

B.doSpecificInitialization // <- crashes, as the base init is missing
(A.doInitialization        // <---not executed
 B.doTests)                // <-/
Dominik Sandjaja
fonte

Respostas:

50

Não coloque @BeforeClassna abstractclasse. Chame-o de cada subclasse.

abstract class A {
    void doInitialization() {}
}

class B extends A {
    @BeforeClass
    void doSpecificInitialization() {
        super.doInitialization();
    }

    @Test
    void doTests() {}
}

Parece que o TestNG mudou @BeforeClass(dependsOnMethods={"doInitialization"})- experimente.

Bozho
fonte
9
Isso é basicamente o que eu queria evitar: não há necessidade de chamar explicitamente os métodos da classe super (abstrata). Especialmente porque também tenho classes, que herdam de A, mas não têm um método @BeforeClass próprio. Eu teria que inserir um apenas para esse propósito.
Dominik Sandjaja
5
A dependsOnMethodssolução alternativa funcionou. Embora eu prefira uma abordagem "superclasse primeiro" ...
Dominik Sandjaja
1
Para usar "dependsOnMethod" "doInitialization" não deve ser anotado com "@Test"? Isso é um problema, já que tecnicamente não é um teste em si ...
N3da
@BeforeClass deve anotar um método estático
Fabrizio Stellato
108

editar: A resposta abaixo é para JUnit , mas vou deixá-la aqui de qualquer maneira, porque pode ser útil.

De acordo com a api JUnit : "Os métodos @BeforeClass das superclasses serão executados antes daqueles da classe atual."

Eu testei isso e parece funcionar para mim.

No entanto, como @Odys menciona abaixo, para JUnit você precisa ter os dois métodos nomeados de forma diferente, embora, de outra forma, resultará na execução apenas do método da subclasse, porque o pai será ocultado.

Fortega
fonte
56
Mesmo que a pergunta original fosse para TestNG, cheguei aqui depois de pesquisar por JUnit no Google e sua resposta ajudou - obrigado!
teabot
9
para JUnit, você precisa ter os dois métodos nomeados de forma diferente, embora, de outra forma, resulte na execução apenas do método da subclasse, porque o pai será ocultado.
Odys
2
@Odys, muito obrigado por mencionar isso. Eu estava lutando para descobrir por que o método "setup" em minha subclasse estava em execução, enquanto o de sua superclasse não. Você acabou de me salvar de uma tonelada de aborrecimento!
Tom Catullo
Você fez meu dia. Obrigado!
raiks
7

Eu adicionei publicà classe abstrata e TestNG (6.0.1) executou o doInitialization () antes doTests. TestNG não será executado doInitialization()se eu remover publicda classe A.

public abstract class A {
 @BeforeClass
 doInitialization() {...}
}

class B extends A {    
 @Test
 doTests() {...}
}
Paulo
fonte
1
Isso é verdade, mas irrelevante. Isso não funciona quando a classe B também tem um @BeforeClassmétodo anotado, como no caso do OP.
jpaugh
1
Eu fiz o mesmo. Parece que a ordem de herança é perdida se o método base for privado. Obrigado!
Manu
6

Eu tentei seu exemplo com 5.11 e obtive o @BeforeClass da classe base chamada primeiro.

Você pode postar seu arquivo testng.xml? Talvez você esteja especificando A e B lá, enquanto apenas B é necessário.

Fique à vontade para acompanhar a lista de discussão de usuários de teste e poderemos examinar seu problema mais de perto.

- Cedric

Cedric Beust
fonte
2
Sem .xml para testng definido (explicitamente), ele é executado a partir do Eclipse e Maven.
Dominik Sandjaja
Como você está executando o Eclipse exatamente? Clicar com o botão direito na classe B?
Cedric Beust
4

Acabei de passar por isso e encontrei mais uma maneira de conseguir isso. Use apenas alwaysRunem @BeforeClassou @BeforeMethodna classe abstrata, funciona como você esperaria.

public class AbstractTestClass {
    @BeforeClass(alwaysRun = true)
    public void generalBeforeClass() {
        // do stuff
        specificBeforeClass();
    }
}
Konrad Garus
fonte
2

Quando eu corro de: JUnitCore.runClasses (TestClass.class); Ele executará o pai corretamente, antes do filho (você não precisa de super.SetUpBeforeClass (); ) Se você executá-lo a partir do Eclipse: Por algum motivo, ele falha ao executar a classe base. A solução alternativa : Chame a classe base explicitamente: ( BaseTest.setUpBeforeClass (); ) Você pode querer ter um sinalizador na classe base no caso de executá-la a partir de um aplicativo, para determinar se ela já está configurada ou não. Portanto, ele é executado apenas uma vez se você executá-lo por meio de ambos os métodos possíveis (como do eclipse para teste pessoal e por meio do ANT para uma versão de construção).

Isso parece ser um bug do Eclipse, ou pelo menos resultados inesperados.

Steve
fonte
2

Para JUnit : Como @fortega mencionou: De acordo com a api JUnit: "Os métodos @BeforeClass das superclasses serão executados antes daqueles da classe atual."

Mas tome cuidado para não nomear ambos os métodos com o mesmo nome . Visto que, neste caso, o método pai será oculto pelo pai filho. Fonte .

Anatolii Stepaniuk
fonte
1

Que tal fazer com que seu método @BeforeClass chame um método specificBeforeClass () vazio que pode ou não ser substituído por subclasses como:

public class AbstractTestClass {
  @BeforeClass
  public void generalBeforeClass() {
    // do stuff
    specificBeforeClass();
  }

  protected void specificBeforeClass() {}
}

public class SpecificTest {
  @Override
  protected void specificBeforeClass() {
    // Do specific stuff
  }

  // Tests
}
Tatome
fonte
3
BeforeClass deve ser estático, então você não pode fazer isso com junit
madx
1

dependsOnMethod pode ser usado.

por exemplo, no caso de Spring ( AbstractTestNGSpringContextTests)

@BeforeClass(alwaysRun = true, dependsOnMethods = "springTestContextPrepareTestInstance")
Sandeep Jindal
fonte
1

Verifique sua declaração de importação. Deveria ser

import org.testng.annotations.BeforeClass;

não

import org.junit.BeforeClass;

Nishantrevo
fonte
1

Isso funciona para mim -

abstract class A {
    @BeforeClass
    doInitialization() {...}
}

class B extends A {
    @Override
    @BeforeClass
    doInitialization() { 

       //do class specific init

    }   

    @Test
    doTests() {...}
}
Srikar
fonte
1
Adicione uma breve explicação do que este código faz e responde à pergunta original
Cray
0

Por que você não tenta criar um método abstrato doSpecialInit () em sua superclasse, chamado de seu método anotado BeforeClass na superclasse.

Portanto, os desenvolvedores que herdam sua classe são forçados a implementar este método.

Ludo
fonte
Para ser honesto, até a lógica pode ter mudado nos últimos 3 anos e meio desde que fiz esta pergunta ... ;-) Então, sim, talvez isso fosse uma ideia, talvez não funcionasse - eu honestamente não lembrar.
Dominik Sandjaja
0

Existe outra solução fácil aqui.

Minha situação particular é que preciso injetar serviços de simulação de "BeforeClass" na subclasse antes de "BeforeClass" na superclasse ser executado.

Para fazer isso - simplesmente use a @ClassRulena subclasse.

Por exemplo:

@ClassRule
public static ExternalResource mocksInjector = new ExternalResource() {
    @Override
    protected void before() {
        // inject my mock services here
        // Note: this is executed before the parent class @BeforeClass
    }
};

Eu espero que isso ajude. Isso pode executar a configuração estática com eficácia na ordem "reversa".

vikingsteve
fonte
0

Eu enfrentei um problema semelhante hoje, a única diferença era que uma classe base não era abstrata

Aqui está o meu caso

public class A {
    @BeforeClass
    private void doInitialization() {...}
}

public class B extends A {
    @BeforeClass
    private void doSpecificInitialization() {...}

    @Test
    public void doTests() {...}
}

Ocorreu que um @BeforeClassmétodo da classe A nunca foi executado.

  • A.doInitialization () -> ISTO NUNCA FOI EXECUTADO silenciosamente
  • B.doSpecificInitialization ()
  • B.doTests ()

Jogando com modificadores de privacidade, descobri que TestNG não executará um @BeforeClassmétodo anotado de uma classe herdada se um método não for visível de um herdeiro de classe

Então isso vai funcionar:

public class A {
    @BeforeClass
    private void doInitialization() {...}
}

public class B extends A {
    @BeforeClass
    //Here a privacy modifier matters -> please make sure your method is public or protected so it will be visible for ancestors
    protected void doSpecificInitialization() {...}

    @Test
    public void doTests() {...}
}

Como resultado, acontece o seguinte:

  • A.doInitialization ()
  • B.doSpecificInitialization ()
  • B.doTests ()
Rodikno
fonte
-1

No meu caso (JUnit), tenho os mesmos métodos chamados setup () na classe base e na classe derivada. Nesse caso, apenas o método da classe derivada é chamado, e eu o faço chamar o método da classe base.

Mike Sokolov
fonte
-2

Uma maneira melhor e mais limpa de conseguir isso usando herança pode ser a seguinte -

abstract class A {

    @BeforeClass
    void doInitialization() {}
}

class B extends A {

    @Override
    @BeforeClass
    void doInitialization() {
        super.doInitialization();
    }

    @Test
    void doTests() {}
}
Kushal
fonte
1
Se você precisa que o método na classe Parent seja executado primeiro, você só precisa nomear o método na classe Child de maneira diferente da classe Parent (porque se eles tiverem a mesma assinatura, o polimorfismo entra em ação). Acredito que seja uma forma mais limpa.
Anatolii Stepaniuk