Junit - exécuter la méthode de configuration une fois


119

J'ai mis en place une classe avec quelques tests et plutôt que d'utiliser, @Beforej'aimerais avoir une méthode de configuration qui ne s'exécute qu'une seule fois avant tous les tests. Est-ce possible avec Junit 4.8?


1
Jetez un œil à RunListener: stackoverflow.com/a/14773170/548473
Grigory Kislin

Réponses:


205

Bien que je sois d'accord avec @assylias, l'utilisation @BeforeClassest une solution classique, ce n'est pas toujours pratique. La méthode annotée avec @BeforeClassdoit être statique. C'est très gênant pour certains tests qui nécessitent une instance de cas de test. Par exemple, des tests basés sur Spring qui @Autowiredfonctionnent avec des services définis dans un contexte de printemps.

Dans ce cas, j'utilise personnellement la setUp()méthode régulière annotée avec @Beforeannotation et gère mon drapeau personnalisé static(!) boolean:

private static boolean setUpIsDone = false;
.....
@Before
public void setUp() {
    if (setUpIsDone) {
        return;
    }
    // do the setup
    setUpIsDone = true;
}

10
Ajoutant au commentaire de Kenny Cason sur la raison pour laquelle il doit être statique. Il doit être statique car JUnit instancie une nouvelle instance de la classe de test pour chaque méthode @Test. La variable d'instance sera réinitialisée à sa valeur par défaut (false) pour chaque instance si elle n'est pas statique. Voir pour plus d'informations: martinfowler.com/bliki/JunitNewInstance.html
dustin.schultz

2
Cela fonctionne sauf dans le cas où la setUp()méthode est dans une superclasse - ont publié une réponse ci-dessous pour tenter de résoudre ce problème.
Steve Chambers

4
J'hésite à dire cela à quelqu'un avec un représentant de 84k, mais BeforeClass ne répond pas en fait à la question: BeforeClass est exécuté au début de chaque classe de test. Mais l'OP en a demandé un qui s'exécute "une seule fois avant tous les tests". La solution que vous proposez pourrait le faire, mais vous devrez faire étendre toutes vos classes de test à une classe "CommonTest" ...
mike rodent

1
@mikerodent, IMHO OP a posé des questions sur tous les tests dans son cas de test, pas tous les tests dans l'ensemble. Donc, votre commentaire est moins pertinent. BTW, ne vous inquiétez pas de dire quoi que ce soit à qui que ce soit, même si sa réputation est élevée. Au moins c'est ce que je fais :). Et ma réputation était nettement inférieure en août 2012 lorsque j'ai répondu à la question.
AlexR

Cela ne fonctionne pas pour mon cas, les variables initialisées dans la configuration sont réinitialisées après chaque test, il est donc inutile de lancer une seule fois.
Aphax

89

Vous pouvez utiliser l' BeforeClassannotation :

@BeforeClass
public static void setUpClass() {
    //executed only once, before the first test
}

12
Je ne peux pas utiliser cela, j'ai quelques méthodes de configuration basées sur des composants non statiques tels que getClass ()
Bober02

1
@ Bober02 BeforeClass doit en effet être statique. Si vous ne pouvez pas utiliser cela, l'autre réponse fournit une solution de contournement.
assylias

2
Vous ne pouvez pas utiliser à la TheClassYouWant.classplace de votre appel getClass ()? Ceci est Java réelle: String.class.getName().
stolsvik


1
@mikerodent J'ai compris la question comme "tous les tests de la classe" - mais vous avez raison, ce n'est peut-être pas ce que l'OP voulait.
assylias

29

JUnit 5 a maintenant une annotation @BeforeAll:

Indique que la méthode annotée doit être exécutée avant toutes les méthodes @Test dans la classe ou la hiérarchie de classes actuelle; analogue à @BeforeClass de JUnit 4. Ces méthodes doivent être statiques.

Les annotations de cycle de vie de JUnit 5 semblent avoir enfin bien fait les choses! Vous pouvez deviner quelles annotations disponibles sans même regarder (par exemple @BeforeEach @AfterAll)


6
Il a le même problème @BeforeClass, il doit l'être static. La solution IMO @ AlexR est plus agréable.
zengr

@zengr a tendance à être d'accord avec vous: comme je l'ai dit à AlexR, sa solution nécessite que toutes les classes de test soient sous-classées à partir d'une classe CommonTest si elle n'est exécutée qu'une seule fois. Mais c'est aussi simple que possible, et à mon humble avis, vous ne devriez probablement pas utiliser une solution fournie par un framework «sophistiqué» lorsqu'un mécanisme simple est disponible dans le langage. Sauf s'il y a une bonne raison bien sûr. De plus, utiliser une chose simple comme la sienne, avec un bon nom de type «fait ce qu'il dit sur l'étain», aide à la lisibilité.
mike rodent

Cela dit, encore une fois à mon humble avis, il semble bien plus justifié d'avoir une annotation «AfterAll»: il serait très difficile et inventé de concevoir un mécanisme pour détecter quand tous les tests ont été effectués. Inversement, bien sûr, les puristes vont probablement dire que vous ne devriez jamais avoir à faire un "nettoyage final", c'est-à-dire que chaque "tearDown" devrait laisser toutes les ressources dans un état impeccable ... et ils ont probablement raison!
mike rodent

Est-ce que cela fonctionne avec Maven où il y a plusieurs modules, chacun avec ses tests?
Mark Boon

@mike rodent, dans mon cas, la configuration et la suppression de fichiers de test dans le système de fichiers avant / après chaque test semble entraîner des blocages sur les fichiers. Pour l'instant, je suis arrivé indépendamment à la solution d'AlexR pour une configuration unique. J'ai deux drapeaux statiques, déjà configurés et sales. setup () appelle cleanup () si un état sale est détecté initialement, ou si un échec de configuration conduit à un état sale. Pour nettoyer après avoir exécuté des tests, je les réexécute. Désordonné, pas idéal du tout, pas dans notre processus de construction. Toujours à la recherche d'un meilleur moyen (jUnit 4.12).
Rebeccah

9

Lorsqu'il setUp()est dans une superclasse de la classe de test (par exemple AbstractTestBaseci-dessous), la réponse acceptée peut être modifiée comme suit:

public abstract class AbstractTestBase {
    private static Class<? extends AbstractTestBase> testClass;
    .....
    public void setUp() {
        if (this.getClass().equals(testClass)) {
            return;
        }

        // do the setup - once per concrete test class
        .....
        testClass = this.getClass();
    }
}

Cela devrait fonctionner pour une seule setUp()méthode non statique mais je suis incapable de produire un équivalent pour tearDown()sans m'égarer dans un monde de réflexion complexe ... Bounty pointe vers tous ceux qui le peuvent!


3

Edit: Je viens de découvrir lors du débogage que la classe est également instanciée avant chaque test. Je suppose que l'annotation @BeforeClass est la meilleure ici.

Vous pouvez également configurer sur le constructeur, la classe de test est une classe après tout. Je ne sais pas si c'est une mauvaise pratique car presque toutes les autres méthodes sont annotées, mais cela fonctionne. Vous pouvez créer un constructeur comme ça:

public UT () {
    // initialize once here
}
@Test
// Some test here...

Le ctor sera appelé avant les tests car ils ne sont pas statiques.


0

Essayez cette solution: https://stackoverflow.com/a/46274919/907576 :

avec @BeforeAllMethods/ @AfterAllMethodsannotation, vous pouvez exécuter n'importe quelle méthode de la classe Test dans un contexte d'instance, où toutes les valeurs injectées sont disponibles.


S'appuie sur une bibliothèque tierce.
Andrew

0

Ma sale solution est:

public class TestCaseExtended extends TestCase {

    private boolean isInitialized = false;
    private int serId;

    @Override
    public void setUp() throws Exception {
        super.setUp();
        if(!isInitialized) {
            loadSaveNewSerId();
            emptyTestResultsDirectory();
            isInitialized = true;
        }
    }

   ...

}

Je l'utilise comme base de base pour tous mes testCases.


classe publique TestCaseExtended étend TestCase {booléen statique privé isInitialized = false; private static TestCaseExtended caseExtended; private int serId; @Override public void setUp () lève l'exception {super.setUp (); if (! isInitialized) {caseExtended = new TestCaseExtended (); caseExtended.loadSaveNewSerId (); caseExtended.emptyTestResultsDirectory (); isInitialized = true; }}
Obi Two

0

Si vous ne voulez pas forcer une déclaration d'une variable qui est définie et vérifiée à chaque sous-test, l'ajout de ceci à un SuperTest pourrait faire:

public abstract class SuperTest {

    private static final ConcurrentHashMap<Class, Boolean> INITIALIZED = new ConcurrentHashMap<>();
    protected final boolean initialized() {
        final boolean[] absent = {false};
        INITIALIZED.computeIfAbsent(this.getClass(), (klass)-> {
            return absent[0] = true;
        });
        return !absent[0];
    }
}



public class SubTest extends SuperTest {
    @Before
    public void before() {
        if ( super.initialized() ) return;

         ... magic ... 
    }

}

0

J'ai résolu ce problème comme ceci:

Ajoutez à votre classe abstraite de base (je veux dire la classe abstraite où vous initialisez votre pilote dans méthode setUpDriver () ) cette partie de code:

private static boolean started = false;
static{
    if (!started) {
        started = true;
        try {
            setUpDriver();  //method where you initialize your driver
        } catch (MalformedURLException e) {
        }
    }
}

Et maintenant, si vos cours de test étend de base classe abstraite -> setUpDriver () méthode sera exécutée avant la première @Test seulement UNE fois par course.


0

Utilisez la méthode @PostConstruct de Spring pour effectuer tout le travail d'initialisation et cette méthode s'exécute avant l'exécution de tout @Test

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.