@BeforeClass et héritage - ordre d'exécution


90

J'ai une classe de base abstraite, que j'utilise comme base pour mes tests unitaires (TestNG 5.10). Dans cette classe, j'initialise tout l'environnement pour mes tests, la configuration des mappages de bases de données, etc. Cette classe abstraite a une méthode avec une @BeforeClassannotation qui effectue l'initialisation.

Ensuite, j'étends cette classe avec des classes spécifiques dans lesquelles j'ai des @Testméthodes et aussi des @BeforeClassméthodes. Ces méthodes effectuent une initialisation spécifique à la classe de l'environnement (par exemple, mettre certains enregistrements dans la base de données).

Comment puis-je appliquer un ordre spécifique des @BeforeClassméthodes annotées? J'ai besoin que ceux de la classe de base abstraite soient exécutés avant ceux de la classe d'extension.

Exemple:

abstract class A {
    @BeforeClass
    doInitialization() {...}
}

class B extends A {
    @BeforeClass
    doSpecificInitialization() {...}

    @Test
    doTests() {...}
}

Commande attendue:

A.doInitialization
B.doSpecificInitialization
B.doTests

Commande réelle:

B.doSpecificInitialization // <- crashes, as the base init is missing
(A.doInitialization        // <---not executed
 B.doTests)                // <-/

Réponses:


50

Ne mettez pas le @BeforeClasssur la abstractclasse. Appelez-le à partir de chaque sous-classe.

abstract class A {
    void doInitialization() {}
}

class B extends A {
    @BeforeClass
    void doSpecificInitialization() {
        super.doInitialization();
    }

    @Test
    void doTests() {}
}

On dirait que TestNG a @BeforeClass(dependsOnMethods={"doInitialization"})- essayez-le.


9
C'est essentiellement ce que je voulais éviter: pas besoin d'appeler explicitement les méthodes de la classe super (abstraite). D'autant que j'ai aussi des classes, qui héritent de A mais n'ont pas de méthode @BeforeClass propre. Je devrais en insérer un uniquement à cette fin.
Dominik Sandjaja

5
La dependsOnMethodssolution de contournement a fait l'affaire. Bien que je préfère une approche "superclasse d'abord" ...
Dominik Sandjaja

1
Pour utiliser "dependOnMethod", "doInitialization" ne devrait-il pas être annoté avec "@Test"? C'est un problème puisque techniquement ce n'est pas un test en soi ...
N3da

@BeforeClass devrait annoter une méthode statique
Fabrizio Stellato

108

edit: La réponse ci-dessous est pour JUnit , mais je la laisserai quand même ici, car cela pourrait être utile.

Selon l' API JUnit : "Les méthodes @BeforeClass des superclasses seront exécutées avant celles de la classe actuelle."

J'ai testé cela, et cela semble fonctionner pour moi.

Cependant, comme @Odys le mentionne ci-dessous, pour JUnit, vous devez nommer les deux méthodes différemment, car sinon, seule la méthode de la sous-classe sera exécutée car le parent sera masqué.


56
Même si la question initiale était pour TestNG, je suis arrivé ici après avoir recherché sur Google JUnit et votre réponse m'a aidé - merci!
teabot

9
pour JUnit, vous devez nommer les deux méthodes différemment, car sinon, seule la méthode de sous-classe sera exécutée car le parent sera masqué.
Odys

2
@Odys, merci beaucoup d'avoir mentionné cela. J'avais du mal à comprendre pourquoi la méthode "setup" de ma sous-classe fonctionnait alors que celle de sa superclasse ne l'était pas. Vous venez de m'éviter une tonne d'aggravation!
Tom Catullo

Tu as fait ma journée. Merci!
raiks le

7

J'ai ajouté publicà la classe abstraite et TestNG (6.0.1) a exécuté le doInitialization () avant doTests. TestNG ne s'exécute pas doInitialization()si je retire publicde la classe A.

public abstract class A {
 @BeforeClass
 doInitialization() {...}
}

class B extends A {    
 @Test
 doTests() {...}
}

1
C'est vrai, mais sans importance. Cela ne fonctionne pas lorsque la classe a B également une @BeforeClassméthode -annotée, comme dans le cas de l'OP.
jpaugh

1
J'ai fait la même chose. Il semble que l'ordre d'héritage soit manqué si la méthode de base est privée. Merci!
Manu

6

Je viens d'essayer votre exemple avec 5.11 et j'obtiens le @BeforeClass de la classe de base invoquée en premier.

Pouvez-vous publier votre fichier testng.xml? Peut-être que vous spécifiez à la fois A et B ici, alors que seul B est nécessaire.

N'hésitez pas à suivre la liste de diffusion testng-users et nous pourrons examiner votre problème de plus près.

- Cédric


2
Pas de .xml pour le testng défini (explicitement), il est exécuté depuis Eclipse et Maven.
Dominik Sandjaja

Comment l'exécutez-vous exactement depuis Eclipse? Un clic droit sur la classe B?
Cedric Beust

4

Je viens de passer par là et j'ai trouvé un autre moyen d'y parvenir. Utilisez simplement alwaysRunsur @BeforeClassou @BeforeMethoddans la classe abstraite, fonctionne comme vous vous en doutez.

public class AbstractTestClass {
    @BeforeClass(alwaysRun = true)
    public void generalBeforeClass() {
        // do stuff
        specificBeforeClass();
    }
}

2

Quand je cours depuis: JUnitCore.runClasses (TestClass.class); Il exécutera le parent correctement, avant l'enfant (Vous n'avez pas besoin de super.SetUpBeforeClass (); ) Si vous l'exécutez depuis Eclipse: Pour une raison quelconque, il ne parvient pas à exécuter la classe de base. Le contournement: Appelez la classe de base explicitement: ( BaseTest.setUpBeforeClass (); ) Vous souhaiterez peut-être avoir un indicateur dans la classe de base au cas où vous l'exécutiez à partir d'une application, pour déterminer si elle est déjà configurée ou non. Donc, il ne s'exécute qu'une seule fois si vous l'exécutez via les deux méthodes possibles (telles que depuis eclipse pour des tests personnels et via ANT pour une version de build).

Cela semble être un bogue avec Eclipse, ou du moins des résultats inattendus.


2

Pour JUnit : Comme @fortega l'a mentionné: Selon l'API JUnit: "Les méthodes @BeforeClass des superclasses seront exécutées avant celles de la classe actuelle."

Mais veillez à ne pas nommer les deux méthodes avec le même nom . Dans ce cas, la méthode parent sera masquée par le parent enfant. Source .


1

Que diriez-vous d'avoir votre méthode @BeforeClass appeler une méthode specificBeforeClass () vide qui peut ou non être écrasée par des sous-classes comme ceci:

public class AbstractTestClass {
  @BeforeClass
  public void generalBeforeClass() {
    // do stuff
    specificBeforeClass();
  }

  protected void specificBeforeClass() {}
}

public class SpecificTest {
  @Override
  protected void specificBeforeClass() {
    // Do specific stuff
  }

  // Tests
}

3
BeforeClass doit être statique, donc vous ne pouvez pas faire cela avec junit
madx

1

dependsOnMethod peut être utilisé.

par exemple dans le cas de Spring ( AbstractTestNGSpringContextTests)

@BeforeClass(alwaysRun = true, dependsOnMethods = "springTestContextPrepareTestInstance")

1

Vérifiez votre déclaration d'importation. Ça devrait être

import org.testng.annotations.BeforeClass;

ne pas

import org.junit.BeforeClass;


1

Cela fonctionne pour moi -

abstract class A {
    @BeforeClass
    doInitialization() {...}
}

class B extends A {
    @Override
    @BeforeClass
    doInitialization() { 

       //do class specific init

    }   

    @Test
    doTests() {...}
}

1
Ajoutez une brève explication de ce que fait ce code et répond à la question d'origine
Cray

0

Pourquoi n'essayez-vous pas de créer une méthode abstraite doSpecialInit () dans votre super classe, appelée à partir de votre méthode annotée BeforeClass dans superclass.

Les développeurs qui héritent de votre classe sont donc obligés d'implémenter cette méthode.


Pour être honnête, même la logique a peut-être changé au cours des 3 1/2 dernières années depuis que j'ai posé cette question ... ;-) Alors oui, peut-être que c'était une idée, peut-être que cela n'a pas fonctionné - honnêtement je ne le fais pas rappelles toi.
Dominik Sandjaja

0

Il existe une autre solution simple ici.

Ma situation particulière est que je dois injecter des services fictifs de "BeforeClass" dans la sous-classe avant que "BeforeClass" dans la superclasse ne soit exécuté.

Pour ce faire, utilisez simplement a @ClassRuledans la sous-classe.

Par exemple:

@ClassRule
public static ExternalResource mocksInjector = new ExternalResource() {
    @Override
    protected void before() {
        // inject my mock services here
        // Note: this is executed before the parent class @BeforeClass
    }
};

J'espère que ça aide. Cela peut exécuter efficacement la configuration statique dans l'ordre «inverse».


0

J'ai rencontré un problème similaire aujourd'hui, la seule différence était qu'une classe de base n'était pas abstraite

Voici mon cas

public class A {
    @BeforeClass
    private void doInitialization() {...}
}

public class B extends A {
    @BeforeClass
    private void doSpecificInitialization() {...}

    @Test
    public void doTests() {...}
}

Il s'est produit qu'une @BeforeClassméthode de la classe A n'a jamais été exécutée.

  • A.doInitialization () -> CELA N'A JAMAIS ÉTÉ EXÉCUTÉ silencieusement
  • B.doSpecificInitialization ()
  • B.doTests ()

En jouant avec les modificateurs de confidentialité, j'ai trouvé que TestNG n'exécutera pas une @BeforeClassméthode annotée à partir d'une classe héritée si une méthode n'est pas visible d'un héritier de classe

Donc cela fonctionnera:

public class A {
    @BeforeClass
    private void doInitialization() {...}
}

public class B extends A {
    @BeforeClass
    //Here a privacy modifier matters -> please make sure your method is public or protected so it will be visible for ancestors
    protected void doSpecificInitialization() {...}

    @Test
    public void doTests() {...}
}

En conséquence, ce qui suit se produit:

  • A.doInitialisation ()
  • B.doSpecificInitialization ()
  • B.doTests ()

-1

Dans mon cas (JUnit), j'ai les mêmes méthodes appelées setup () dans la classe de base et la classe dérivée. Dans ce cas, seule la méthode de la classe dérivée est appelée, et je la fais appeler la méthode de la classe de base.


-2

Une manière meilleure et plus propre d'y parvenir en utilisant l'héritage peut être la suivante:

abstract class A {

    @BeforeClass
    void doInitialization() {}
}

class B extends A {

    @Override
    @BeforeClass
    void doInitialization() {
        super.doInitialization();
    }

    @Test
    void doTests() {}
}

1
Si vous avez besoin que la méthode de la classe Parent soit exécutée en premier, il vous suffit de nommer la méthode de la classe Child différemment de celle de Parent (car si elles ont la même signature, le polymorphisme entre en action). Je pense que c'est une manière plus propre.
Anatolii Stepaniuk
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.