¿A qué orden se llama Junit @ Before / @ After?

133

Tengo una suite de prueba de integración. Tengo una IntegrationTestBaseclase para extender todas mis pruebas. Esta clase base tiene un método @Before( public void setUp()) y @After( public void tearDown()) para establecer conexiones API y DB. Lo que he estado haciendo es anular esos dos métodos en cada caso de prueba y llamar super.setUp()y super.tearDown(). Sin embargo, esto puede causar problemas si alguien olvida llamar al súper o los coloca en el lugar equivocado y se produce una excepción y se olvida de llamar al súper finalmente o algo así.

Lo que quiero hacer es crear los métodos setUpy tearDownen la clase base finaly luego simplemente agregar nuestros propios métodos @Beforey anotados @After. Al hacer algunas pruebas iniciales, parece que siempre se llama en este orden:

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

pero me preocupa un poco que el pedido no esté garantizado y que pueda causar problemas. Miré a mi alrededor y no he visto nada sobre el tema. ¿Alguien sabe si puedo hacer eso y no tengo ningún problema?

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
fuente
1
¿Es la MyTestfalta un extends?
aioobe
@aioobe: ya no :)
Joel

Respuestas:

135

Sí, este comportamiento está garantizado:

@Before:

Los @Beforemétodos de las superclases se ejecutarán antes que los de la clase actual, a menos que se anulen en la clase actual. No se define ningún otro orden.

@After:

Los @Aftermétodos declarados en superclases se ejecutarán después de los de la clase actual, a menos que se anulen en la clase actual.

axtavt
fuente
15
Para ser claros, el orden de ejecución de todos los @Beforemétodos no está garantizado. Si hay 10 @Beforemétodos, cada uno de ellos puede ejecutarse en cualquier orden; justo antes de cualquier otro método.
Swati
55
Entonces, en lugar de citar la documentación algo ambigua, ¿puede explicarla en sus propias palabras? ¿ Se ejecutan los métodos @Beforey @Afterantes de cualquier otro método de clase (una vez por método), o justo antes y después de todo el conjunto de métodos de clase (una vez por clase)?
BT
55
Vea la captura importante declarada por John Q Citizen: "esto solo se aplica si cada método marcado con @Before tiene un nombre único en la jerarquía de clases" ¡Muy importante para recordar!
Bruno Bossola
Tuve un conflicto de nombre usando el mismo nombre de método en un método @Before (d) en una clase y otro método en su superclase, en junit-4.12.
Stephane
¿Esta regla también se aplica a los métodos @BeforeExample de ConcordionRunner?
Adrian Pronk
51

Una potencial trampa que me ha mordido antes:

Me gusta tener como máximo un @Beforemétodo en cada clase de prueba, porque el orden de ejecución de los @Beforemétodos definidos dentro de una clase no está garantizado. Por lo general, llamaré a tal método setUpTest().

Pero, aunque @Beforeestá documentado como The @Before methods of superclasses will be run before those of the current class. No other ordering is defined., esto solo se aplica si cada método marcado con @Beforetiene un nombre único en la jerarquía de clases.

Por ejemplo, tuve lo siguiente:

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

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

Esperaba AbstractFooTest.setUpTest()correr antes FooTest.setUpTest(), pero solo FooTest.setupTest()fue ejecutado. AbstractFooTest.setUpTest()no fue llamado en absoluto.

El código debe modificarse de la siguiente manera para que funcione:

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() {
    super.setUpTest();
    ...
  }
}
John Q Citizen
fuente
¿Por qué no simplemente cambiar el nombre del método @Before en la clase base? Esto le ahorrará tener que llamar a super en todos los niños ... de todos modos, buena captura con el mismo nombre
Lawrence Tierney
24
Solo un comentario para hacer las cosas más seguras: para evitar conflictos de nombres, puede hacer que los @Before/ @Aftermétodos en la clase base final, por lo que el compilador se quejará si (accidentalmente) intenta anularlos en la subclase.
Stefan Winkler
44
El método principal del mismo nombre que no se ejecuta no suena como un comportamiento JUnit. Eso suena como funciona la anulación básica en OOP. El método padre básicamente no existe en tiempo de ejecución. El niño lo reemplaza para todos los efectos. Así es como funciona Java.
Brandon
1
Otro problema es que las clases principales deben ser públicas, de lo contrario, sus @Beforemétodos marcados se ignorarán si una subclase también tiene un @Beforemétodo.
rusins
21

Creo que basado en la documentación de la @Before y @Afterla conclusión correcta es dar a los métodos nombres únicos. Utilizo el siguiente patrón en mis pruebas:

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

y

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

Ventaja de este enfoque: los usuarios de la clase AbstractBaseTest no pueden anular los métodos setUp / tearDown por accidente. Si quieren, necesitan saber el nombre exacto y pueden hacerlo.

Desventaja (menor) de este enfoque: los usuarios no pueden ver que están sucediendo cosas antes o después de su configuración / desgarro. Necesitan saber que estas cosas son proporcionadas por la clase abstracta. Pero supongo que esa es la razón por la que usan la clase abstracta

Matthias Hoefel
fuente
2
Un buen ejemplo: sería aún más ilustrativo si tuviera dos métodos @Test, por lo que se puede ver que setUp y tearDown ajustan cada método de prueba.
Mark
Creo que esta es la base para la mejor respuesta al OP, pero debe completar su respuesta de forma independiente. ¿Podría aumentar su ejemplo para cubrir también las alternativas que otros han sugerido y explicar por qué su propuesta es superior?
wachr
2

Si cambia las cosas, puede declarar su resumen de clase base y hacer que los descendientes declaren los métodos setUp y tearDown (sin anotaciones) que se llaman en los métodos setUp y tearDown anotados de la clase base.

Buhb
fuente
1
no es una mala idea, pero no quiero hacer cumplir un contrato en las pruebas que no necesitan su propio setUp / tearDown
Joel
2

Puede usar la @BeforeClassanotación para asegurarse de que setup()siempre se llame primero. Del mismo modo, puede usar la @AfterClassanotación para asegurarse de que tearDown()siempre se llame último.

Esto generalmente no se recomienda, pero es compatible .

No es exactamente lo que desea, pero esencialmente mantendrá su conexión de base de datos abierta todo el tiempo que se ejecutan sus pruebas, y luego la cerrará de una vez por todas al final.

Swati
fuente
2
En realidad, si hiciera esto, recomendaría crear un método setupDB()y closeDB()marcarlos con @BeforeClassy @AfterClassy reemplazar sus métodos antes / después con setup()ytearDown()
Swati
Los métodos anotados con @BeforeClassy @AfterClassdeben ser estáticos. ¿Qué pasa con el caso, cuando queremos usar variables de instancia dentro de estos métodos?
Pratik Singhal
Una advertencia cuando se usa @BeforeClasscon Powermock: solo funciona para la primera ejecución de prueba. Vea este número: github.com/powermock/powermock/issues/398
Dagmar
2

Esta no es una respuesta a la pregunta del lema, pero es una respuesta a los problemas mencionados en el cuerpo de la pregunta. En lugar de usar @Before o @After, considere usar @ org.junit.Rule porque le brinda más flexibilidad. ExternalResource (a partir de 4.7) es la regla que más le interesará si gestiona conexiones. Además, si desea un orden de ejecución garantizado de sus reglas, use una RuleChain (a partir de 4.10). Creo que todo esto estaba disponible cuando se hizo esta pregunta. El ejemplo de código a continuación se copia de los javadocs de 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
fuente