Testng: Les méthodes @Test de la super classe sont réexécutées pour chaque sous-classe

CrĂ©Ă© le 10 juin 2019  Â·  12Commentaires  Â·  Source: cbeust/testng

TestNG Version 6.14.3

Comportement prévisible

Les méthodes @Test de la super classe sont exécutées une fois, quel que soit le nombre de sous-classes héritant de la super classe.

Comportement réel

Les méthodes @Test sont exécutées avec chaque sous-classe.

Le problĂšme est-il reproductible sur le coureur ?

  • [ ] Coquille
  • [ ] Maven
  • [x] Gradle
  • [ ] Fourmi
  • [ ] Éclipse
  • [ ] IntelliJ
  • [ ] NetBeans

Exemple de cas de test

public class BaseTest  extends SetupClass{
    @BeforeClass...
    @BeforeMethod...
    @AfterClass...
    @AfterMethod....

    <strong i="22">@Test</strong>
    public void baseMethod(){
        System.out.println("base method");
    }
}
public class Test1 extends BaseTest{
    <strong i="25">@Test</strong>
    public void test1(){
        System.out.println("test1");
    }
}
public class Test2 extends BaseTest{
    <strong i="28">@Test</strong>
    public void test2(){
        System.out.println("test2");
    }
}

J'exécute ces tests en parallÚle à partir d'un fichier XML TestNG qui ressemble à ceci :

<suite name="Regression Suite" verbose="1" parallel="classes" thread-count="4">
    <parameter name="project" value="TestPrj"/>
    <parameter name="server" value="QA01"/>
    <test name="QA01 TestPrj">
        <parameter name="environment" value="macos"/>
        <groups>
            <run>
                <include name="regression"/>
            </run>
        </groups>
        <packages>
            <package name="test"/>
        </packages>
    </test>
</suite>

PS L'Ă©dition de code dans les problĂšmes de github est incroyablement mauvaise... veuillez passer Ă  autre chose.

inheritance

Tous les 12 commentaires

@vlad230

PS L'Ă©dition de code dans les problĂšmes de github est incroyablement mauvaise... veuillez passer Ă  autre chose.

Vous voudrez peut-ĂȘtre passer du temps Ă  vous familiariser avec Markdown. Cela rendrait trĂšs facile l'ajout d'extraits de code avec le formatage.

Je ne suis pas sûr de comprendre votre problÚme ici. En fin de compte, les classes enfants obtiendraient également les méthodes de la classe de base. Et s'ils sont annotés avec des annotations TestNG, ils seront exécutés pour chaque classe enfant. C'est le comportement de TestNG. Je dirais donc que TestNG fonctionne comme prévu.

@juherr - Vos avis ?

@krmahadevan merci pour la mise en forme :)

Je ne suis pas sûr de comprendre votre problÚme ici. En fin de compte, les classes enfants obtiendraient également les méthodes de la classe de base. Et s'ils sont annotés avec des annotations TestNG, ils seront exécutés pour chaque classe enfant. C'est le comportement de TestNG. Je dirais donc que TestNG fonctionne comme prévu.

Si vous me demandez, cela n'aurait aucun sens d'exécuter les tests dans la super classe plusieurs fois.
Si c'est le comportement attendu de TestNG en ce moment, nous pourrions peut-ĂȘtre l'ajouter comme amĂ©lioration.

Fonctionne comme prévu.
Pourquoi ne déplacez-vous pas baseMethod test dans sa propre classe ?

@vlad230

Si vous me demandez, cela n'aurait aucun sens d'exécuter les tests dans la super classe plusieurs fois.

Qu'essayez-vous d'atteindre exactement ? Ce serait formidable si vous pouviez aider à élaborer ou à expliquer votre scénario.

Si c'est le comportement attendu de TestNG en ce moment, nous pourrions peut-ĂȘtre l'ajouter comme amĂ©lioration.

Je ne suis pas sûr que cela soit considéré comme une amélioration car c'est contre-intuitif. Si vous voulez essentiellement que la méthode de la classe de base soit exécutée exactement une fois, quel que soit le nombre de classes enfants présentes, vous pouvez le faire aujourd'hui dans votre projet de test en procédant comme suit

  1. Demandez à votre classe de base d'implémenter org.testng.IHookable
  2. Dans sa méthode run() , ajoutez une vérification d'édition qui vérifierait si la méthode de la classe de base est déjà exécutée et l'exécuterait si elle n'est pas exécutée.

Voici un Ă©chantillon

Interface de marqueur qui indique qu'une méthode de classe de base ne doit s'exécuter qu'une seule fois

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({METHOD, TYPE})
public <strong i="20">@interface</strong> RunOnce {
}

Voici le singleton de suivi d'Ă©tat

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class StateTracker {

  private static final StateTracker instance = new StateTracker();

  private final Map<String, Object> runOnceData = new ConcurrentHashMap<>();

  private StateTracker() {}

  public static StateTracker getInstance() {
    return instance;
  }

  boolean canExecute(Method method) {
    RunOnce runOnce = method.getAnnotation(RunOnce.class);
    String methodName = method.getName();
    boolean execute = true;
    if (runOnce != null) {
      if (runOnceData.containsKey(methodName)) {
        execute = false;
      } else {
        runOnceData.put(methodName, new Object());
      }
    }
    return execute;
  }
}

Voici Ă  quoi ressemblerait la classe de base

import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;
import org.testng.annotations.Test;

public class BaseTest implements IHookable {

  <strong i="27">@Test</strong>
  <strong i="28">@RunOnce</strong>
  public void baseclassTestMethod() {
    System.err.println("Base class test method executed");
  }

  <strong i="29">@Override</strong>
  public void run(IHookCallBack callBack, ITestResult testResult) {
    boolean execute =
        StateTracker.getInstance()
            .canExecute(testResult.getMethod().getConstructorOrMethod().getMethod());
    if (execute) {
      callBack.runTestMethod(testResult);
    } else {
      testResult.setStatus(ITestResult.SKIP);
      testResult.setThrowable(new RuntimeException("Intentionally skipping"));
    }
  }
}

Voici Ă  quoi ressembleraient les classes pour enfants

import org.testng.annotations.Test;

public class ChildClass1 extends BaseTest {

  <strong i="33">@Test</strong>
  public void childTest() {
    System.err.println(getClass().getName() + ".childTest() executed");
  }
}
import org.testng.annotations.Test;

public class ChildClass2 extends BaseTest {

  <strong i="36">@Test</strong>
  public void childTest() {
    System.err.println(getClass().getName() + ".childTest() executed");
  }
}

Vous devrez ajouter un mécanisme de filtrage qui élague les résultats des tests et supprime les tests ignorés qui sont annotés à l'aide de @RunOnce

@krmahadevan
Qu'essayez-vous d'atteindre exactement ? Ce serait formidable si vous pouviez aider à élaborer ou à expliquer votre scénario.

J'essaie de séparer les tests en plusieurs classes (qui sont liées à une fonctionnalité). Par exemple, vous avez une "classe de base" qui contient la partie configuration de cette fonctionnalité avec les méthodes @Before et @After aux cÎtés d'autres méthodes d'assistance (pas de tests) qui prennent en charge les tests de la classe de base aux cÎtés des tests dans les "classes enfants ". Ainsi, de cette façon, vous ajouteriez simplement de nouveaux tests à vos classes enfants et ne dupliqueriez pas le code dans les classes enfants.
La classe de base aurait des méthodes de test qui testent des parties plus générales de cette fonctionnalité et les classes enfants auront des tests plus spécifiques.
Étant donnĂ© que j'essaie d'optimiser le temps d'exĂ©cution du test en exĂ©cutant des classes en parallĂšle, j'ai dĂ» diviser ma "classe de base" d'origine qui comportait plus de 50 mĂ©thodes de test en plusieurs "classes enfants" plus petites de sorte que ces classes plus petites soient distribuĂ©es Ă  d'autres threads /exĂ©cuteurs.

@krmahadevan merci beaucoup pour la solution dĂ©taillĂ©e mais cela me semble un peu lourd. Étant donnĂ© que TestNG peut ĂȘtre personnalisĂ©, je prĂ©fĂšre voir cela comme le comportement par dĂ©faut.
Je ne vois pas la nécessité de ré-exécuter constamment les tests de la classe de base. S'il s'agit de configuration/démontage, on pourrait toujours utiliser @Before/ @After qui serait

Oui, je pense que le comportement pourrait ĂȘtre configurable.

En fait, je ne trouve pas de cas d'utilisation oĂč nous souhaitons exĂ©cuter le test parent pour chaque instance enfant.
Sauf si vous avez une dépendance directe à tester ( dependsOnMethods ) car l'état du test est souvent modifié comme cela dans les tests d'intégration.

@krmahadevan WDYT ?

@vlad230

merci beaucoup pour la solution détaillée mais cela me semble un peu lourd

Votre cas d'utilisation est Ă©galement un peu diffĂ©rent de la normale. De plus, je vous ai donnĂ© un exemple pratique de la façon dont cela peut ĂȘtre fait. Tout ce que vous avez Ă  faire est de l'adopter et de vous appuyer dessus.

Étant donnĂ© que TestNG peut ĂȘtre personnalisĂ©, je prĂ©fĂšre voir cela comme le comportement par dĂ©faut.

Non, cela ne peut pas ĂȘtre fait Ă  mon avis. Les tests sont censĂ©s ĂȘtre exĂ©cutĂ©s tout le temps. Ce n'est pas parce qu'ils rĂ©sident dans une classe de base qu'ils doivent ĂȘtre exĂ©cutĂ©s de maniĂšre sĂ©lective. TestNG peut ĂȘtre personnalisĂ©. Mais cela ne signifie pas que TestNG doit prendre en charge toutes les personnalisations. Il doit simplement fournir des moyens par lesquels un utilisateur devrait pouvoir le personnaliser selon ses besoins et travailler avec. L'exemple que j'ai partagĂ© est un exemple d'application de ces personnalisations sans que TestNG n'ait Ă  subir de modifications.

Je ne vois pas la nécessité de ré-exécuter constamment les tests de la classe de base. S'il s'agit de configuration/démontage, on peut toujours utiliser @Before/@ after qui serait

Pour moi, ce n'est qu'une façon de voir les choses. Si une mĂ©thode particuliĂšre ne doit pas ĂȘtre exĂ©cutĂ©e Ă  chaque fois, vous ne devriez pas utiliser l'annotation @Test . Vous pouvez trĂšs bien utiliser les annotations de configuration telles que @BeforeTest (qui n'est exĂ©cutĂ©e qu'une seule fois par <test> tag) ou @BeforeSuite (qui n'est exĂ©cutĂ©e qu'une seule fois par <suite> balise

Je ne sais pas si vos méthodes de test dans votre classe de base sont vraiment des méthodes de test. Pour moi, cela ressemble plus à des configurations qui établissent les conditions de configuration à satisfaire pour que le test se déroule.

@juherr - Je ne suis pas sĂ»r d'ĂȘtre d'accord avec le cas d'utilisation ici. Un test, quel que soit l'endroit oĂč il se trouve, est destinĂ© Ă  ĂȘtre exĂ©cutĂ©. S'il existe un scĂ©nario dans lequel nous ne voulons pas qu'il soit exĂ©cutĂ© pour toutes les classes enfants, nous devrions utiliser les annotations de configuration et non l'annotation @Test .

Du cÎté de l'implémentation, je pense que cela ajoutera beaucoup de chaos à la base de code, en particulier lorsque les gens commenceront à utiliser des groupes comme moyen d'exécution.

De plus, je pense qu'il existe dĂ©jĂ  un moyen de le faire actuellement au sein de TestNG (que j'ai partagĂ© Ă  titre d'exemple). Pourquoi ne pas simplement en tirer parti ? Cela semble ĂȘtre un cas d'utilisation unique et ne semble pas correspondre Ă  la maniĂšre habituelle d'utiliser TestNG.

@krmahadevan, je suis désolé, mais je pense que vous n'avez pas compris ce que j'essaie de faire.
Dans ma classe parent (BaseTest - par exemple DashboardTest), j'ai maintenant 15 tests et j'ai 3 classes enfants (par exemple DashboardFilterTest, DashboardTreeTest, DashboardEditorTest) qui contiennent chacune des tests. En utilisant l'approche que j'ai décrite dans mon article, chaque fois que j'exécute les tests, ces 15 tests sont exécutés 3 fois (donc, 3x15 = 45 tests en cours d'exécution) n'apportant aucune valeur et prolongeant inutilement le temps d'exécution, au lieu de simplement exécuter le 15 tests une fois.

Je pense toujours que cela n'a pas de sens que les tests situés dans une classe parente soient réexécutés chaque fois que les tests d'une classe enfant sont exécutés. Préférerait le comportement par défaut de TestNG pour exécuter les méthodes de test d'une classe parente une fois, quel que soit le nombre de classes enfants qui l'étendent.

Si cela ne peut pas ĂȘtre fait, je comprends. Merci quand mĂȘme.

@vlad230

Merci d'avoir ajouté un contexte supplémentaire.

Dans ma classe parent (BaseTest - par exemple DashboardTest), j'ai maintenant 15 tests et j'ai 3 classes enfants (par exemple DashboardFilterTest, DashboardTreeTest, DashboardEditorTest) qui contiennent chacune des tests. En utilisant l'approche que j'ai décrite dans mon article, chaque fois que j'exécute les tests, ces 15 tests sont exécutés 3 fois (donc, 3x15 = 45 tests en cours d'exécution) n'apportant aucune valeur et prolongeant inutilement le temps d'exécution, au lieu de simplement exécuter le 15 tests une fois.

Dans ce cas, pourquoi DashboardFilterTest, DashboardTreeTest, DashboardEditorTest Ă©tendrait-il DashboardTest ? Il semble que les tests DashboardTest doivent rĂ©sider dans leur propre classe au lieu que vous utilisiez une approche d'hĂ©ritage ici, non ? La classe de base doit ĂȘtre refactorisĂ©e pour supprimer toutes les mĂ©thodes de test courantes et ĂȘtre hĂ©bergĂ©e dans une classe de test distincte, et DashboardTest doit ĂȘtre refactorisĂ© pour hĂ©berger uniquement les mĂ©thodes courantes non @Test non ?

Si cela ne peut pas ĂȘtre fait, je comprends. Merci quand mĂȘme.

Il ne s'agit pas de savoir si elles peuvent ĂȘtre rĂ©alisĂ©es ou non (les dĂ©fis de mise en Ɠuvre sont un sujet distinct), mais j'ai toujours du mal Ă  essayer de comprendre la validitĂ© du cas d'utilisation lui-mĂȘme.

@krmahadevan

Dans ce cas, pourquoi DashboardFilterTest, DashboardTreeTest, DashboardEditorTest Ă©tendrait-il DashboardTest ? Il semble que les tests DashboardTest doivent rĂ©sider dans leur propre classe au lieu que vous utilisiez une approche d'hĂ©ritage ici, non ? La classe de base doit ĂȘtre refactorisĂ©e pour supprimer toutes les mĂ©thodes de test courantes et ĂȘtre hĂ©bergĂ©e dans une classe de test distincte, et DashboardTest doit ĂȘtre refactorisĂ© pour hĂ©berger uniquement les mĂ©thodes courantes non @Test non ?

'DashboardTest' contient des méthodes d'assistance (par exemple openDashboard(), createDashboard() etc.) et des méthodes setup/teardown/cleanup (@Before/@After) qui sont également nécessaires pour les classes enfants. Donc, je n'ai pas besoin de répliquer le code dans les classes enfants.
Oui, certains des tests de « DashboardTest » pourraient ĂȘtre dĂ©placĂ©s vers d'autres classes spĂ©cifiques, mais je voulais les garder ici car ils sont plus gĂ©nĂ©raux (par exemple, vĂ©rifier l'apparence gĂ©nĂ©rale du tableau de bord, les tests CRUD de base ou mĂȘme certains tests qui ne l'ont pas fait » t rentrent dans une catĂ©gorie spĂ©cifique ou sont un mĂ©lange).

Bien sĂ»r, je pourrais avoir une classe de test non @Test (mĂȘme si ce serait bizarre et que quelqu'un pourrait ajouter des tests ici par erreur), mais ce serait juste pour Ă©viter ce problĂšme, non ?

@vlad230

'DashboardTest' contient des méthodes d'assistance (par exemple openDashboard(), createDashboard() etc.) et des méthodes setup/teardown/cleanup (@Before/@after) qui sont également nécessaires pour les classes enfants. Donc, je n'ai pas besoin de répliquer le code dans les classes enfants.

C'est logique. et oui, ils peuvent trÚs bien résider dans la classe de base afin que les méthodes de configuration soient également mises à la disposition des classes enfants.

Oui, certains des tests de « DashboardTest » pourraient ĂȘtre dĂ©placĂ©s vers d'autres classes spĂ©cifiques, mais je voulais les garder ici car ils sont plus gĂ©nĂ©raux (par exemple, vĂ©rifier l'apparence gĂ©nĂ©rale du tableau de bord, les tests CRUD de base ou mĂȘme certains tests qui ne l'ont pas fait » t rentrent dans une catĂ©gorie spĂ©cifique ou sont un mĂ©lange).

Vous pouvez toujours créer une classe appelée GenericDashboardTest extends DashboardTest qui contient toutes les méthodes génériques @Test .

Bien sĂ»r, je pourrais avoir une classe de test non @test (mĂȘme si ce serait bizarre et que quelqu'un pourrait ajouter des tests ici par erreur), mais ce serait juste pour Ă©viter ce problĂšme, non ?

Eh bien, n'est-ce pas à cela que servent les revues de code :) Pour éviter de tels dérapages. La classe de base (d'aprÚs ce que vous avez expliqué) n'a pas besoin de contenir les méthodes génériques courantes @Test . Il peut trÚs bien héberger uniquement les assistants et les configurations et vous pouvez avoir une classe de test supplémentaire qui étend la classe de base et héberge les méthodes de test génériques. Cela devrait résoudre la confusion actuelle.

Fermeture de ce problÚme avec une résolution comme _fonctionnant comme prévu_

Cette page vous a été utile?
0 / 5 - 0 notes