java - test - Cómo compartir la lógica JUnit BeforeClass entre varias clases de prueba
junit tutorial español (3)
Me encontré con un problema similar (Spring no era una opción y no escribo TestSuites en varios proyectos), así que escribí un junit runner simple para resolver este problema.
Debe escribir la clase SharedResource y marcar su prueba para requerir ese recurso.
public class SampleSharedResource implements SharedResource {
public void initialize() throws Exception {
//init your resource
}
}
@RunWith(JUnitSharedResourceRunner.class)
@JUnitSharedResourceRunner.WithSharedResources({SampleSharedResource.class})
public class SharedResourceRunnerATest {
...
Fuentes en https://github.com/eanlr/junit-shared-resources-runner
Actualmente, todas mis pruebas de JUnit se extienden desde una clase base común que proporciona métodos etiquetados con las anotaciones @BeforeClass
y @AfterClass
; todo esto realmente es configurar un conjunto de recursos / servicios estáticos para que las @AfterClass
.
Esto me parece un poco incómodo por varias razones:
- Parte del punto de JUnit4 (a mi entender) es que ya no deberíamos necesitar esta herencia de prueba clásica.
- Cuando ejecuto estas pruebas como parte de una suite en lugar de individualmente (lo que a menudo hacemos), se invoca a
@BeforeClass
y@AfterClass
varias veces, ralentizando las pruebas; realmente solo deberíamos llamarlas una vez
Lo que me gustaría hacer es mover de algún modo la lógica de BeforeClass / AfterClass actual de la cadena de herencia a algo que puedan compartir las pruebas individuales y la suite en su conjunto.
Se puede hacer esto? ¿Si es así, cómo? (Si importa, estoy usando JUnit 4.7, y podría ser difícil venderlo para una versión diferente)
Puede usar @BeforeClass
y @AfterClass
EN LA CLASE DE SUITE .
Esto ejecutará los métodos antes de que se ejecute cualquiera de las clases de prueba en la suite y después de que todas las clases de prueba terminen (respectivamente)
De esta manera puedes correrlos solo una vez.
//..usual @RunWith etc annotations here
public class MySuite{
@BeforeClass
public static void setup(){
}
@AfterClass
public static void tearDown(){
}
}
Una solución al primer problema es mover la lógica a una extensión de org.junit.rules.ExternalResource
conectada a la prueba a través de una @ClassRule
, introducida en JUnit 4.9:
public class MyTest {
@ClassRule
public static final TestResources res = new TestResources();
@Test
public void testFoo() {
// test logic here
}
}
public class TestResources extends ExternalResource {
protected void before() {
// Setup logic that used to be in @BeforeClass
}
protected void after() {
// Setup logic that used to be in @AfterClass
}
}
De esta manera, los recursos administrados previamente por la clase base se mueven fuera de la jerarquía de clases de prueba y se convierten en "recursos" más modulares / consumibles que pueden crearse antes de que una clase se ejecute y destruirse después de que se ejecute una clase.
Sin embargo, para resolver ambos problemas al mismo tiempo, es decir, tener el mismo nivel de configuración / desmontaje ejecutado como parte de una prueba individual y como parte de una suite, no parece haber ningún soporte integrado específico para esto. . Sin embargo ... , podrías implementarlo tú mismo :
Simplemente cambie la creación del recurso @ClassRule
a un patrón de fábrica que haga referencia al conteo interno para determinar si crear o destruir el recurso.
Por ejemplo (tenga en cuenta que esto es difícil y podría necesitar algunos ajustes / manejo de errores para mayor robustez):
public class TestResources extends ExternalResource {
private static int refCount = 0;
private static TestResources currentInstance;
public static TestResources getTestResources () {
if (refCount == 0) {
// currentInstance either hasn''t been created yet, or after was called on it - create a new one
currentInstance = new TestResources();
}
return currentInstance;
}
private TestResources() {
System.out.println("TestResources construction");
// setup any instance vars
}
protected void before() {
System.out.println("TestResources before");
try {
if (refCount == 0) {
System.out.println("Do actual TestResources init");
}
}
finally {
refCount++;
}
}
protected void after() {
System.out.println("TestResources after");
refCount--;
if (refCount == 0) {
System.out.println("Do actual TestResources destroy");
}
}
}
@ClassResource
clases de suite / prueba solo usarían el recurso como @ClassResource
través del método de fábrica:
@RunWith(Suite.class)
@SuiteClasses({FooTest.class, BarTest.class})
public class MySuite {
@ClassRule
public static TestResources res = TestResources.getTestResources();
@BeforeClass
public static void suiteSetup() {
System.out.println("Suite setup");
}
@AfterClass
public static void suiteTeardown() {
System.out.println("Suite teardown");
}
}
public class FooTest {
@ClassRule
public static TestResources res = TestResources.getTestResources();
@Test
public void testFoo() {
System.out.println("testFoo");
}
}
public class BarTest {
@ClassRule
public static TestResources res = TestResources.getTestResources();
@Test
public void testBar() {
System.out.println("testBar");
}
}
Cuando se ejecuta una prueba individual, el refcounting no tendrá ningún efecto; el "inicio real" y el "desmontaje real" solo ocurrirán una vez. Cuando se ejecuta a través de la suite, la suite creará el TestResource, y las pruebas individuales solo reutilizarán la ya instanciada (el refcounting evita que se destruya realmente y se vuelva a crear entre las pruebas de la suite).