Actuellement, tous mes tests JUnit s'étendent à partir d'une classe de base commune qui fournit des méthodes marquées avec @BeforeClass
et @AfterClass
annotations - tout cela ne fait que configurer un tas de ressources/services statiques pour les tests à utiliser.
Cela me semble gênant pour plusieurs raisons:
@BeforeClass
et @AfterClass
être invoqué plusieurs fois, ce qui ralentit les tests - nous ne devrions vraiment les appeler qu'une seule foisCe que je voudrais faire, c'est en quelque sorte déplacer la logique BeforeClass/AfterClass actuelle hors de la chaîne d'héritage et la placer dans quelque chose qui peut être partagé par des tests individuels et la suite dans son ensemble.
Cela peut-il être fait? Si oui, comment? (Si cela importe, j'utilise JUnit 4.7, et il pourrait être difficile de mettre à jour vers une version différente)
Une solution au premier problème consiste à déplacer la logique dans une extension de org.junit.rules.ExternalResource
connecté au test via un @ClassRule
, introduit dans 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 cette manière, les ressources précédemment gérées par la classe de base sont déplacées hors de la hiérarchie des classes de test et vers des "ressources" plus modulaires/consommables qui peuvent être créées avant l'exécution d'une classe et détruites après l'exécution d'une classe.
En ce qui concerne la résolution des deux problèmes en même temps - c'est-à-dire: avoir la même configuration/démontage de haut niveau exécutée à la fois dans le cadre d'un test individuel et dans le cadre d'une suite - il ne semble pas y avoir de support intégré spécifique pour cela. . Cependant ... , vous pouvez l'implémenter vous-même:
Modifiez simplement le @ClassRule
création de ressource dans un modèle d'usine qui fait référence au comptage en interne pour déterminer s'il faut ou non créer/détruire la ressource.
Par exemple (veuillez noter que cela est approximatif et pourrait nécessiter quelques ajustements/gestion des erreurs pour la robustesse):
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");
}
}
}
Vos deux suites/classes de test utiliseraient simplement la ressource comme @ClassResource
par la méthode d'usine:
@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");
}
}
Lors de l'exécution d'un test individuel, le recomptage n'aura aucun effet - le "démarrage réel" et le "démontage réel" ne se produiront qu'une seule fois. Lors de l'exécution de la suite, la suite créera le TestResource, et les tests individuels ne feront que réutiliser celui déjà instancié (le recomptage l'empêche d'être réellement détruit et recréé entre les tests de la suite).
Vous pouvez utiliser le @BeforeClass
et @AfterClass
DANS LA CLASSE SUITE.
Cela exécutera les méthodes avant l'exécution des classes de test de la suite et après la fin de toutes les classes de test (respectivement)
De cette façon, vous ne pouvez les exécuter qu'une seule fois.
//..usual @RunWith etc annotations here
public class MySuite{
@BeforeClass
public static void setup(){
}
@AfterClass
public static void tearDown(){
}
}
Je suis tombé sur un problème similaire (Spring n'était pas une option et je n'écris pas TestSuites dans les projets Maven), j'ai donc écrit un simple junit runner pour résoudre ce problème.
Vous devez écrire la classe SharedResource et marquer votre test pour exiger cette ressource.
public class SampleSharedResource implements SharedResource {
public void initialize() throws Exception {
//init your resource
}
}
@RunWith(JUnitSharedResourceRunner.class)
@JUnitSharedResourceRunner.WithSharedResources({SampleSharedResource.class})
public class SharedResourceRunnerATest {
...
Sources sur https://github.com/eanlr/junit-shared-resources-runner