@BeforeClass y herencia - orden de ejecución

90

Tengo una clase base abstracta, que uso como base para mis pruebas unitarias (TestNG 5.10). En esta clase, inicializo todo el entorno para mis pruebas, configurando mapeos de base de datos, etc. Esta clase abstracta tiene un método con una @BeforeClassanotación que hace la inicialización.

A continuación, extiendo esa clase con clases específicas en las que tengo @Testmétodos y también @BeforeClassmétodos. Estos métodos realizan la inicialización del entorno específica de la clase (por ejemplo, colocan algunos registros en la base de datos).

¿Cómo puedo hacer cumplir un orden específico de los @BeforeClassmétodos anotados? Necesito que los de la clase base abstracta se ejecuten antes que los de la clase extensible.

Ejemplo:

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

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

    @Test
    doTests() {...}
}

Orden esperada:

A.doInitialization
B.doSpecificInitialization
B.doTests

Orden real:

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

Respuestas:

50

No pongas @BeforeClassla abstractclase. Llámelo de cada subclase.

abstract class A {
    void doInitialization() {}
}

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

    @Test
    void doTests() {}
}

Parece que TestNG lo ha hecho @BeforeClass(dependsOnMethods={"doInitialization"}), pruébalo.

Bozho
fuente
9
Eso es básicamente lo que quería evitar: no es necesario llamar explícitamente a métodos de la clase super (abstracta). Especialmente porque también tengo clases, que heredan de A pero no tienen un método propio @BeforeClass. Tendría que insertar uno solo para ese propósito.
Dominik Sandjaja
5
La dependsOnMethodssolución hizo el truco. Aunque prefiero un enfoque de "primera superclase" ...
Dominik Sandjaja
1
Para utilizar "depende de un método", ¿no debería "doInitialization" estar anotado con "@Test"? Eso es un problema ya que técnicamente no es una prueba en sí misma ...
N3da
@BeforeClass debe anotar un método estático
Fabrizio Stellato
107

editar: La respuesta a continuación es para JUnit , pero la dejaré aquí de todos modos, porque podría ser útil.

Según la API de JUnit : "Los métodos @BeforeClass de superclases se ejecutarán antes que los de la clase actual".

Probé esto y parece funcionar para mí.

Sin embargo, como @Odys menciona a continuación, para JUnit debe tener los dos métodos nombrados de manera diferente, aunque de lo contrario, solo se ejecutará el método de subclase porque el padre será sombreado.

Fortega
fuente
56
A pesar de que la pregunta original era para TestNG, llegué aquí después de buscar en Google JUnit y su respuesta ayudó, ¡gracias!
teabot
9
para JUnit, debe tener los dos métodos nombrados de manera diferente, aunque de lo contrario, solo se ejecutará el método de subclase porque el padre se sombreará.
Odys
2
@Odys, muchas gracias por mencionar esto. Estaba luchando por averiguar por qué el método de "configuración" en mi subclase se estaba ejecutando mientras que el de su superclase no. ¡Me acabas de ahorrar un montón de molestias!
Tom Catullo
Me has alegrado el día. ¡Gracias!
raiks
7

He añadido publica la clase abstracta y TestNG (6.0.1) ejecuta el doInitialization () antes doTests. TestNG no se ejecuta doInitialization()si elimino publicde la clase A.

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

class B extends A {    
 @Test
 doTests() {...}
}
Pablo
fuente
1
Eso es cierto, pero irrelevante. Esto no funciona cuando la clase B también tiene un @BeforeClassmétodo anotado, como en el caso del OP.
jpaugh
1
Yo hice lo mismo. Parece que se pierde el orden de herencia si el método base es privado. ¡Gracias!
Manu
6

Acabo de probar su ejemplo con 5.11 y obtengo el @BeforeClass de la clase base invocado primero.

¿Puedes publicar tu archivo testng.xml? Tal vez esté especificando tanto A como B allí, mientras que solo B es necesario.

No dude en hacer un seguimiento de la lista de correo de testng-users y podremos analizar más de cerca su problema.

- Cedric

Cedric Beust
fuente
2
No .xml para testng definido (explícitamente), se ejecuta desde Eclipse y Maven.
Dominik Sandjaja
¿Cómo lo estás ejecutando exactamente desde Eclipse? ¿Haciendo clic derecho en la clase B?
Cedric Beust
4

Acabo de pasar por esto y encontré una forma más de lograrlo. Solo use alwaysRunen @BeforeClasso @BeforeMethoden la clase abstracta, funciona como es de esperar.

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

Cuando ejecuto desde: JUnitCore.runClasses (TestClass.class); Ejecutará el padre correctamente, antes que el hijo (no necesita super.SetUpBeforeClass (); ) Si lo ejecuta desde Eclipse: por alguna razón, no puede ejecutar la clase base. La solución alternativa : Llame explícitamente a la clase base: ( BaseTest.setUpBeforeClass (); ) Es posible que desee tener una marca en la clase base en caso de que la ejecute desde una aplicación, para determinar si ya está configurada o no. Por lo tanto, solo se ejecuta una vez si lo ejecuta a través de ambos métodos posibles (como desde eclipse para pruebas personales y a través de ANT para una versión de compilación).

Esto parece ser un error con Eclipse, o al menos resultados inesperados.

Steve
fuente
2

Para JUnit : Como ha mencionado @fortega: Según la api de JUnit: "Los métodos @BeforeClass de superclases se ejecutarán antes que los de la clase actual".

Pero tenga cuidado de no nombrar ambos métodos con el mismo nombre . Dado que en este caso el método principal estará oculto por el padre secundario. Fuente .

Anatolii Stepaniuk
fuente
1

¿Qué tal si tu método @BeforeClass llama a un método específicoBeforeClass () vacío que puede o no ser sobrescrito por subclases como este?

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
fuente
3
BeforeClass debe ser estático para que no pueda hacer esto con junit
madx
1

dependsOnMethod puede ser usado.

por ejemplo, en el caso de Spring ( AbstractTestNGSpringContextTests)

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

Verifique su declaración de importación. Debería ser

import org.testng.annotations.BeforeClass;

no

import org.junit.BeforeClass;

nishantrevo
fuente
1

Esto funciona para mí

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

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

       //do class specific init

    }   

    @Test
    doTests() {...}
}
srikar
fuente
1
Agregue una breve explicación de lo que hace este código y responda la pregunta original
Cray
0

¿Por qué no intentas crear un método abstracto doSpecialInit () en tu superclase, llamado desde tu método anotado BeforeClass en la superclase?

Entonces, los desarrolladores que heredan su clase se ven obligados a implementar este método.

Ludo
fuente
Para ser honesto, incluso la lógica puede haber cambiado en los últimos 3 1/2 años desde que hice esta pregunta ... ;-) Así que sí, tal vez esto fue una idea, tal vez no funcionó, honestamente no recuerda.
Dominik Sandjaja
0

Aquí hay otra solución sencilla.

Mi situación particular es que necesito inyectar servicios simulados de "BeforeClass" en la subclase antes de que se ejecute "BeforeClass" en la superclase.

Para hacer esto, simplemente use a @ClassRuleen la subclase.

Por ejemplo:

@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
    }
};

Espero que esto ayude. Esto puede ejecutar de forma eficaz la configuración estática en orden "inverso".

vikingsteve
fuente
0

Hoy me he enfrentado a un problema similar, la única diferencia era que una clase base no era abstracta

Este es mi caso

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

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

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

Ocurrió que un @BeforeClassmétodo de la clase A nunca se ejecutó.

  • A.doInitialization () -> ESTO NUNCA FUE EJECUTADO silenciosamente
  • B.doSpecificInitialization ()
  • B.doTests ()

Jugando con modificadores de privacidad, encontré que TestNG no ejecutará un @BeforeClassmétodo anotado de una clase heredada si un método no es visible desde un heredero de clase

Entonces esto 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, sucede lo siguiente:

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

En mi caso (JUnit) tengo los mismos métodos llamados setup () en la clase base y la clase derivada. En este caso, solo se llama al método de la clase derivada, y lo hago llamar al método de la clase base.

Mike Sokolov
fuente
-2

Una forma mejor y más limpia de lograr esto usando la herencia puede ser la siguiente:

abstract class A {

    @BeforeClass
    void doInitialization() {}
}

class B extends A {

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

    @Test
    void doTests() {}
}
kushal
fuente
1
Si necesita que el método en la clase principal se ejecute primero, solo necesita nombrar el método en la clase secundaria de manera diferente al de los padres (porque si tienen la misma firma, entonces el polimorfismo entra en acción). Creo que es una forma más limpia.
Anatolii Stepaniuk