Hook d'exécution avant et après la suite dans jUnit 4.x

84

J'essaie de préformer la configuration et le démontage pour un ensemble de tests d'intégration, en utilisant jUnit 4.4 pour exécuter les tests. Le démontage doit être exécuté de manière fiable. J'ai d'autres problèmes avec TestNG, donc je cherche à revenir sur jUnit. Quels hooks sont disponibles pour l'exécution avant l'exécution des tests et une fois tous les tests terminés?

Remarque: nous utilisons maven 2 pour notre build. J'ai essayé d'utiliser les phases pre-& de post-integration-testmaven, mais si un test échoue, maven s'arrête et ne s'exécute pas post-integration-test, ce qui n'aide pas.

sblundy
la source
1
Pour les tests d'intégration, vous devez utiliser le plugin maven-failafe -plugin au lieu de surefire. Cela ne sautera pas post-integration-testsi un test échoue. Voir aussi cette page wiki .
Chris H.
pouvez-vous partager votre mise en œuvre finale s'il vous plaît?
vikramvi

Réponses:

113

Oui, il est possible d'exécuter de manière fiable des méthodes de configuration et de suppression avant et après les tests d'une suite de tests. Laissez-moi vous démontrer dans le code:

package com.test;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({Test1.class, Test2.class})
public class TestSuite {

    @BeforeClass
    public static void setUp() {
        System.out.println("setting up");
    }

    @AfterClass
    public static void tearDown() {
        System.out.println("tearing down");
    }

}

Donc, votre Test1classe ressemblerait à quelque chose comme:

package com.test;

import org.junit.Test;


public class Test1 {
    @Test
    public void test1() {
        System.out.println("test1");
    }

}

... et vous pouvez imaginer que cela Test2semble similaire. Si vous couriez TestSuite, vous obtiendrez:

setting up
test1
test2
tearing down

Ainsi, vous pouvez voir que la configuration / la suppression ne s'exécutent qu'avant et après tous les tests, respectivement.

Le hic: cela ne fonctionne que si vous exécutez la suite de tests et que vous n'exécutez pas Test1 et Test2 en tant que tests JUnit individuels. Vous avez mentionné que vous utilisiez maven, et le plugin maven surefire aime exécuter des tests individuellement et ne fait pas partie d'une suite. Dans ce cas, je recommanderais de créer une superclasse que chaque classe de test étend. La superclasse contient alors les méthodes annotées @BeforeClass et @AfterClass. Bien que pas aussi propre que la méthode ci-dessus, je pense que cela fonctionnera pour vous.

En ce qui concerne le problème des tests ayant échoué, vous pouvez définir maven.test.error.ignore afin que la compilation continue en cas d'échec des tests. Ce n'est pas recommandé comme pratique continue, mais cela devrait vous permettre de fonctionner jusqu'à ce que tous vos tests réussissent. Pour plus de détails, consultez la documentation maven surefire .

Julie
la source
2
Cela a parfaitement fonctionné pour moi une fois que je suis entré dans le plugin maven-surefire-plugin et que j'ai créé une liste d'inclusion qui pointait vers la suite que je voulais exécuter.
Jherico
2
Depuis JUnit 4.8.2, cela ne fonctionne pas bien avec les tests paramétrés. Les méthodes @BeforeClass de la Suite seront exécutées après la méthode @ Parameterized.Parameters du test, évitant ainsi toute dépendance à la configuration de la Suite.
Anm
En réponse à moi-même, lors de l'utilisation de @Theories, l'appel à la méthode @DataPoints est appelé après la @BeforeClass de la Suite.
Anm
18
Désolé pour le necro - mais l'ajout de BeforeClass / AfterClass à une super classe ne fonctionne pas comme prévu - ils sont toujours appelés après la fin de chaque classe de test. C'est pour la postérité.
Subu Sankara Subramanian
3
Est-ce toujours une approche valable? Comment éviter d'avoir à énumérer la liste des classes de test dans l'annotation SuiteClasses?
Burhan Ali
33

Un de mes collègues a suggéré ce qui suit: vous pouvez utiliser un RunListener personnalisé et implémenter la méthode testRunFinished (): http://junit.sourceforge.net/javadoc/org/junit/runner/notification/RunListener.html#testRunFinished(org. junit.runner.Result)

Pour enregistrer le RunListener, configurez simplement le plugin surefire comme suit: http://maven.apache.org/surefire/maven-surefire-plugin/examples/junit.html section "Utilisation d'écouteurs et de reporters personnalisés"

Cette configuration doit également être sélectionnée par le plugin de sécurité. Cette solution est excellente car vous n'avez pas besoin de spécifier des suites, des classes de test de recherche ou tout autre chose - elle permet à Maven de faire sa magie, en attendant que tous les tests soient terminés.

Martin Vysny
la source
5
+1 C'est la première solution utilisable que j'ai vue sans l'entretien fastidieux d'une classe Suites!
Stefan Haberl
7

En utilisant des annotations, vous pouvez faire quelque chose comme ceci:

import org.junit.*;
import static org.junit.Assert.*;
import java.util.*;

class SomethingUnitTest {
    @BeforeClass
    public static void runBeforeClass()
    {

    }

    @AfterClass
    public static void runAfterClass()
    {  

    }

    @Before  
    public void setUp()
    {

    }

    @After
    public void tearDown()
    {

    }

    @Test
    public void testSomethingOrOther()
    {

    }

}
Jroc
la source
4
La configuration et le démontage doivent être exécutés une fois par exécution. Cela n'aiderait que si tous les tests sont dans une seule classe.
sblundy le
3

Ici, nous

  • mis à niveau vers JUnit 4.5,
  • écrit des annotations pour marquer chaque classe ou méthode de test nécessitant un service opérationnel,
  • écrit des gestionnaires pour chaque annotation contenant des méthodes statiques pour implémenter la configuration et le démontage du service,
  • étendu le Runner habituel pour localiser les annotations sur les tests, en ajoutant les méthodes de gestion statique dans la chaîne d'exécution des tests aux points appropriés.

la source
2

Quant à "Note: nous utilisons maven 2 pour notre build. J'ai essayé d'utiliser les phases de test pré et post-intégration de maven, mais, si un test échoue, maven s'arrête et n'exécute pas le test post-intégration , ce qui n’aide pas. "

vous pouvez essayer le plugin de sécurité à la place, je pense qu'il a la possibilité de s'assurer que le nettoyage se produit indépendamment de la configuration ou de l'état de l'étape intermédiaire

Pantalon Binod
la source
Oui, le plugin de sécurité vous permettra de spécifier une configuration et un démontage spécifiques. Bien que je ne pense pas que la sécurité intégrée existait au moment où cette question a été publiée.
Jason Axelson
2

A condition que tous vos tests puissent étendre une classe "technique" et soient dans le même package, vous pouvez faire une petite astuce:

public class AbstractTest {
  private static int nbTests = listClassesIn(<package>).size();
  private static int curTest = 0;

  @BeforeClass
  public static void incCurTest() { curTest++; }

  @AfterClass
  public static void closeTestSuite() {
      if (curTest == nbTests) { /*cleaning*/ }             
  }
}

public class Test1 extends AbstractTest {
   @Test
   public void check() {}
}
public class Test2 extends AbstractTest {
   @Test
   public void check() {}
}

Sachez que cette solution présente de nombreux inconvénients:

  • doit exécuter tous les tests du package
  • doit sous-classer une classe "techincal"
  • vous ne pouvez pas utiliser @BeforeClass et @AfterClass dans les sous-classes
  • si vous n'exécutez qu'un seul test dans le package, le nettoyage n'est pas effectué
  • ...

Pour information: listClassesIn () => Comment trouver toutes les sous-classes d'une classe donnée en Java?

christophe blin
la source
1
ce n'est pas vrai pour autant que mes propres tests le montrent. J'ai une super classe qui démarre un glassfish intégré avant la classe et l'éteint après la classe. J'ai alors 2 classes qui s'étendent de cette super classe. La classe beforeclass est exécutée avant d'exécuter les tests définis dans chaque classe.
Jonathan Morales Vélez
0

Autant que je sache, il n'y a pas de mécanisme pour faire cela dans JUnit, mais vous pouvez essayer de sous-classer Suite et de remplacer la méthode run () par une version qui fournit des hooks.

utilisateur15299
la source
0

Étant donné que maven-surefire-plugin n'exécute pas la classe Suite en premier, mais traite les classes de suite et de test de la même manière, nous pouvons donc configurer le plugin comme ci-dessous pour activer uniquement les classes de suite et désactiver tous les tests. Suite exécutera tous les tests.

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.5</version>
            <configuration>
                <includes>
                    <include>**/*Suite.java</include>
                </includes>
                <excludes>
                    <exclude>**/*Test.java</exclude>
                    <exclude>**/*Tests.java</exclude>
                </excludes>
            </configuration>
        </plugin>
Anurag Sharma
la source
0

La seule façon que je pense alors d'obtenir la fonctionnalité que vous voulez serait de faire quelque chose comme

import junit.framework.Test;  
import junit.framework.TestResult;  
import junit.framework.TestSuite;  

public class AllTests {  
    public static Test suite() {  
        TestSuite suite = new TestSuite("TestEverything");  
        //$JUnit-BEGIN$  
        suite.addTestSuite(TestOne.class);  
        suite.addTestSuite(TestTwo.class);  
        suite.addTestSuite(TestThree.class);  
        //$JUnit-END$  
     }  

     public static void main(String[] args)  
     {  
        AllTests test = new AllTests();  
        Test testCase = test.suite();  
        TestResult result = new TestResult();  
        setUp();  
        testCase.run(result);  
        tearDown();  
     }  
     public void setUp() {}  
     public void tearDown() {}  
} 

J'utilise quelque chose comme ça dans eclipse, donc je ne suis pas sûr à quel point il est portable en dehors de cet environnement

Jroc
la source
3
Ceci est un exemple pour JUnit3, et l'OP a demandé JUnit4, mais juste au cas où certains utilisateurs de JUnit3 trouveraient cette question ... Pour JUnit3, il serait préférable de se débarrasser de la méthode main () et d'avoir la méthode suite () enveloppez la TestSuite dans une sous-classe de junit.extensions.TestSetup. Vous avez toujours les mêmes mises en garde que l'exemple de Julie concernant l'exécution des classes de test individuelles dans votre IDE.
NamshubWriter
0

Si vous ne voulez pas créer de suite et que vous devez lister toutes vos classes de test, vous pouvez utiliser la réflexion pour trouver le nombre de classes de test de manière dynamique et compter à rebours dans une classe de base @AfterClass pour faire le tearDown une seule fois:

public class BaseTestClass
{
    private static int testClassToRun = 0;

    // Counting the classes to run so that we can do the tear down only once
    static {
        try {
            Field field = ClassLoader.class.getDeclaredField("classes");
            field.setAccessible(true);

            @SuppressWarnings({ "unchecked", "rawtypes" })
            Vector<Class> classes = (Vector<Class>) field.get(BlockJUnit4ClassRunner.class.getClassLoader());
            for (Class<?> clazz : classes) {
                if (clazz.getName().endsWith("Test")) {
                    testClassToRun++;
                }
            }
        } catch (Exception ignore) {
        }
    }

    // Setup that needs to be done only once
    static {
        // one time set up
    }

    @AfterClass
    public static void baseTearDown() throws Exception
    {
        if (--testClassToRun == 0) {
            // one time clean up
        }
    }
}

Si vous préférez utiliser @BeforeClass au lieu des blocs statiques, vous pouvez également utiliser un indicateur booléen pour effectuer le décompte des réflexions et tester la configuration une seule fois lors du premier appel. J'espère que cela aide quelqu'un, il m'a fallu un après-midi pour trouver un meilleur moyen que d'énumérer toutes les classes dans une suite.

Il ne vous reste plus qu'à étendre cette classe à toutes vos classes de test. Nous avions déjà une classe de base pour fournir des éléments communs pour tous nos tests, donc c'était la meilleure solution pour nous.

L'inspiration vient de cette réponse SO https://stackoverflow.com/a/37488620/5930242

Si vous ne voulez pas étendre cette classe partout, cette dernière réponse SO pourrait faire ce que vous voulez.

FredBoutin
la source