web-dev-qa-db-fra.com

Comment partager la logique JUnit BeforeClass entre plusieurs classes de test

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:

  1. Une partie de l'intérêt de JUnit4 (d'après ma compréhension) est que nous ne devrions plus avoir besoin de cet héritage de test classique.
  2. Lorsque j'exécute ces tests dans le cadre d'une suite plutôt qu'individuellement (ce que nous faisons souvent), le @BeforeClass et @AfterClass être invoqué plusieurs fois, ce qui ralentit les tests - nous ne devrions vraiment les appeler qu'une seule fois

Ce 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)

28
Krease

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).

29
Krease

Vous pouvez utiliser le @BeforeClass et @AfterClassDANS 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(){

}

}
2
dkatzel

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

1
Eanlr