Testng: @Testmethoden der Superklasse werden für jede Unterklasse erneut ausgeführt

Erstellt am 10. Juni 2019  ·  12Kommentare  ·  Quelle: cbeust/testng

TestNG-Version 6.14.3

Erwartetes Verhalten

Die @Test Methoden der Superklasse werden einmal ausgeführt, egal wie viele Unterklassen die Superklasse erben.

Tatsächliches Verhalten

Die Methoden @Test werden mit jeder Unterklasse ausgeführt.

Ist das Problem auf dem Läufer reproduzierbar?

  • [ ] Hülse
  • [ ] Maven
  • [x] Gradle
  • [ ] Ameise
  • [ ] Sonnenfinsternis
  • [ ] IntelliJ
  • [ ] NetBeans

Testfallbeispiel

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");
    }
}

Ich führe diese Tests parallel aus einer TestNG-XML-Datei aus, die wie folgt aussieht:

<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 Das Bearbeiten von Code in Github-Problemen ist unglaublich schlecht ... bitte wechseln Sie zu etwas anderem.

inheritance

Alle 12 Kommentare

@vlad230

PS Das Bearbeiten von Code in Github-Problemen ist unglaublich schlecht ... bitte wechseln Sie zu etwas anderem.

Vielleicht möchten Sie einige Zeit damit verbringen, sich mit Markdown vertraut zu machen. Das würde es sehr einfach machen, Code-Snippets zusammen mit der Formatierung hinzuzufügen.

Bin mir nicht ganz sicher, ob ich dein Problem hier verstehe. Letztendlich würden die untergeordneten Klassen auch die Basisklassenmethoden erhalten. Und wenn sie mit TestNG-Annotationen annotiert sind, werden sie für jede untergeordnete Klasse ausgeführt. Das ist das Verhalten von TestNG. Ich würde also sagen, dass TestNG wie geplant funktioniert.

@juherr - Deine

@krmahadevan danke für die Formatierung :)

Bin mir nicht ganz sicher, ob ich dein Problem hier verstehe. Letztendlich würden die untergeordneten Klassen auch die Basisklassenmethoden erhalten. Und wenn sie mit TestNG-Annotationen annotiert sind, werden sie für jede untergeordnete Klasse ausgeführt. Das ist das Verhalten von TestNG. Ich würde also sagen, dass TestNG wie geplant funktioniert.

Wenn Sie mich fragen, würde es keinen Sinn machen, die Tests in der Superklasse mehrmals auszuführen.
Wenn dies derzeit das erwartete Verhalten von TestNG ist, könnten wir dies vielleicht als Verbesserung hinzufügen.

Arbeiten wie geplant.
Warum verschieben Sie baseMethod test nicht in eine eigene Klasse?

@vlad230

Wenn Sie mich fragen, würde es keinen Sinn machen, die Tests in der Superklasse mehrmals auszuführen.

Was genau versuchst du zu erreichen? Es wäre großartig, wenn Sie bitte helfen könnten, Ihr Szenario zu erläutern oder zu erklären.

Wenn dies derzeit das erwartete Verhalten von TestNG ist, könnten wir dies vielleicht als Verbesserung hinzufügen.

Ich bin mir nicht sicher, ob das als Verbesserung aufgenommen würde, weil es nicht intuitiv ist. Wenn Sie grundsätzlich möchten, dass die Basisklassenmethode genau einmal ausgeführt wird, egal wie viele untergeordnete Klassen vorhanden sind, können Sie dies heute in Ihrem Testprojekt tun, indem Sie Folgendes tun:

  1. Lassen Sie Ihre Basisklasse org.testng.IHookable implementieren
  2. Fügen Sie innerhalb der Methode run() eine Bearbeitungsprüfung hinzu, die prüft, ob die Basisklassenmethode bereits ausgeführt wurde, und sie ausführen, wenn sie nicht ausgeführt wird.

Hier ist ein Beispiel

Marker-Schnittstelle, die angibt, dass eine Basisklassenmethode nur einmal ausgeführt werden soll

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 {
}

Hier ist der State-Tracker-Singleton

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;
  }
}

So würde die Basisklasse aussehen

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"));
    }
  }
}

So würden die Kinderklassen aussehen

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");
  }
}

Sie müssten einen Filtermechanismus hinzufügen, der die Testergebnisse beschneidet und übersprungene Tests entfernt, die mit @RunOnce annotiert sind

@krmahadevan
Was genau versuchst du zu erreichen? Es wäre großartig, wenn Sie bitte helfen könnten, Ihr Szenario zu erläutern oder zu erklären.

Ich versuche, die Tests in mehrere Klassen zu unterteilen (die sich auf ein Feature beziehen). Zum Beispiel haben Sie eine "Basisklasse", die den Einrichtungsteil für diese Funktion mit @ Before- und @ After- Methoden neben anderen Kindklassen " unterstützen ". Auf diese Weise würden Sie Ihren untergeordneten Klassen einfach neue Tests hinzufügen und den Code in den untergeordneten Klassen nicht duplizieren.
Die Basisklasse hätte einige Testmethoden, die allgemeinere Teile dieses Features testen, und die untergeordneten Klassen haben spezifischere Tests.
Da ich versuche, die Testlaufzeit durch paralleles Ausführen von Klassen zu optimieren, musste ich meine ursprüngliche "Basisklasse" mit über 50 Testmethoden in mehrere kleinere "Kindklassen" aufteilen, sodass diese kleineren Klassen auf andere Threads verteilt werden /Ausführer.

@krmahadevan vielen Dank für die ausführliche Lösung, sieht mir aber etwas umständlich aus. Da TestNG angepasst werden kann, würde ich dies eher als Standardverhalten sehen.
Ich sehe keine Notwendigkeit, die Tests von der Basisklasse ständig neu auszuführen. Wenn es um Setup/Teardown geht, könnte man immer @Before/ @After verwenden , das jedes Mal vor einer

Ja, ich denke, das Verhalten könnte konfigurierbar sein.

Tatsächlich finde ich keinen Anwendungsfall, bei dem wir den übergeordneten Test für jede untergeordnete Instanz ausführen möchten.
Es sei denn, Sie haben eine direkte Abhängigkeit zum Testen ( dependsOnMethods ), da der Teststatus in Integrationstests oft so geändert wird.

@ krmahadevan WDYT?

@vlad230

vielen dank für die ausführliche lösung, sieht mir aber etwas umständlich aus

Ihr Anwendungsfall sieht auch ein bisschen anders aus als der normale. Außerdem habe ich Ihnen ein funktionierendes Beispiel dafür gegeben, wie es gemacht werden kann. Alles, was Sie tun müssen, ist, dies einfach zu übernehmen und darauf aufzubauen.

Da TestNG angepasst werden kann, würde ich dies eher als Standardverhalten sehen.

Nein. Das geht meiner Meinung nach nicht. Tests sollen die ganze Zeit ausgeführt werden. Nur weil sie sich in einer Basisklasse befinden, heißt das nicht, dass sie selektiv ausgeführt werden müssen. TestNG kann individuell angepasst werden. Das bedeutet jedoch nicht, dass TestNG alle Anpassungen unterstützen muss. Es muss lediglich Möglichkeiten bieten, wie ein Benutzer es an seine Bedürfnisse anpassen und damit arbeiten kann. Das von mir geteilte Beispiel ist ein Beispiel für die Anwendung dieser Anpassungen, ohne dass TestNG Änderungen unterzogen werden muss.

Ich sehe keine Notwendigkeit, die Tests von der Basisklasse ständig neu auszuführen. Wenn es um Setup/Teardown geht, könnte man immer @Before/ @after verwenden , das jedes Mal vor einer

Für mich ist das nur eine Sichtweise auf die Dinge. Wenn eine bestimmte Methode nicht jedes Mal ausgeführt werden soll, sollten Sie die Annotation @Test . Sie könnten sehr gut die Konfigurationsanmerkungen wie @BeforeTest (die nur einmal pro <test> Tag ausgeführt wird) oder @BeforeSuite (die nur einmal pro <suite> -Tag) nein? Würden sie nicht für dich arbeiten.

Ich bin mir nicht sicher, ob Ihre Testmethoden in Ihrer Basisklasse wirklich Testmethoden sind. Für mich klingen sie eher nach Konfigurationen, die die Setup-Bedingungen für den Testdurchlauf festlegen.

@juherr - Ich bin mir nicht ganz sicher, ob ich dem Anwendungsfall hier zustimme. Ein Test soll unabhängig davon, wo er gefunden wird, ausgeführt werden. Wenn es ein Szenario gibt, in dem es nicht für alle untergeordneten Klassen ausgeführt werden soll, sollten wir die Konfigurationsanmerkungen und nicht die @Test Anmerkungen verwenden.

Auf der Implementierungsseite denke ich, dass dies der Codebasis eine Menge Chaos hinzufügen wird, insbesondere wenn Leute anfangen, Gruppen als Mittel zur Ausführung zu verwenden.

Darüber hinaus habe ich das Gefühl, dass es derzeit innerhalb von TestNG bereits eine Möglichkeit gibt, dies zu tun (die ich als Beispiel geteilt habe). Warum nutzt man das nicht einfach? Dies scheint ein einmaliger Anwendungsfall zu sein und scheint nicht in die normale Art und Weise zu passen, wie TestNG verwendet wird.

@krmahadevan Es tut mir leid, aber ich glaube, du hast nicht verstanden, was ich versuche.
In meiner Elternklasse (BaseTest - zB DashboardTest) habe ich jetzt 15 Tests und ich habe 3 Unterklassen (zB DashboardFilterTest, DashboardTreeTest, DashboardEditorTest), die jeweils Tests enthalten. Mit dem Ansatz, den ich in meinem Beitrag beschrieben habe, werden diese 15 Tests jedes Mal, wenn ich die Tests ausführe, dreimal ausgeführt (also 3x15 = 45 Tests werden ausgeführt), was keinen Wert bringt und die Laufzeit nutzlos verlängert, anstatt nur die auszuführen 15 Tests einmal.

Ich denke immer noch, dass es nicht sinnvoll ist, dass Tests, die sich in einer übergeordneten Klasse befinden, jedes Mal neu ausgeführt werden, wenn Tests einer untergeordneten Klasse ausgeführt werden. Würde es vorziehen, dass das Standardverhalten von TestNG Testmethoden einer übergeordneten Klasse einmal ausführt, unabhängig von der Anzahl der untergeordneten Klassen, die sie erweitern.

Wenn dies nicht möglich ist, verstehe ich das. Danke trotzdem.

@vlad230

Vielen Dank für das Hinzufügen von zusätzlichem Kontext.

In meiner Elternklasse (BaseTest - zB DashboardTest) habe ich jetzt 15 Tests und ich habe 3 Unterklassen (zB DashboardFilterTest, DashboardTreeTest, DashboardEditorTest), die jeweils Tests enthalten. Mit dem Ansatz, den ich in meinem Beitrag beschrieben habe, werden diese 15 Tests jedes Mal, wenn ich die Tests ausführe, dreimal ausgeführt (also 3x15 = 45 Tests werden ausgeführt), was keinen Wert bringt und die Laufzeit nutzlos verlängert, anstatt nur die auszuführen 15 Tests einmal.

Warum sollte DashboardFilterTest, DashboardTreeTest, DashboardEditorTest in diesem Fall DashboardTest ? Es sieht so aus, als ob DashboardTest Tests in einer eigenen Klasse untergebracht werden müssen, anstatt dass Sie hier einen Vererbungsansatz nutzen, nicht wahr? Die Basisklasse sollte umgestaltet werden, um alle gängigen Testmethoden zu entfernen und in einer separaten Testklasse untergebracht zu werden, und DashboardTest sollte umgestaltet werden, um nur allgemeine nicht @Test Methoden aufzunehmen nein ?

Wenn dies nicht möglich ist, verstehe ich das. Danke trotzdem.

Es geht nicht darum, ob sie machbar sind oder nicht (die Herausforderungen bei der Implementierung sind ein separates Thema), aber es fällt mir immer noch schwer, die Gültigkeit des Anwendungsfalls selbst zu verstehen.

@krmahadevan

Warum sollte DashboardFilterTest, DashboardTreeTest, DashboardEditorTest in diesem Fall DashboardTest ? Es sieht so aus, als ob DashboardTest Tests in einer eigenen Klasse untergebracht werden müssen, anstatt dass Sie hier einen Vererbungsansatz nutzen, nicht wahr? Die Basisklasse sollte umgestaltet werden, um alle gängigen Testmethoden zu entfernen und in einer separaten Testklasse untergebracht zu werden, und DashboardTest sollte umgestaltet werden, um nur allgemeine nicht @Test Methoden aufzunehmen nein ?

'DashboardTest' enthält Hilfsmethoden (zB openDashboard(), createDashboard() etc.) und setup/teardown/cleanup (@Before/@After) Methoden, die auch für die untergeordneten Klassen benötigt werden. Daher muss ich den Code in den untergeordneten Klassen nicht replizieren.
Ja, einige der Tests in 'DashboardTest' könnten in andere spezifische Klassen verschoben werden, aber ich wollte sie hier behalten, da sie allgemeiner sind (z. passen nicht in eine bestimmte Kategorie oder sind eine Mischung).

Sicher, ich könnte eine Nicht-@Test- Testklasse haben (obwohl es seltsam wäre und jemand hier versehentlich Tests hinzufügen könnte), aber es wäre nur, um dieses Problem zu vermeiden, oder?

@vlad230

'DashboardTest' enthält Hilfsmethoden (zB openDashboard(), createDashboard() etc.) und setup/teardown/cleanup (@Before/@after) Methoden, die auch für die untergeordneten Klassen benötigt werden. Daher muss ich den Code in den untergeordneten Klassen nicht replizieren.

Das macht Sinn. und ja, sie können sich sehr gut in der Basisklasse befinden, so dass die Konfigurationsmethoden auch den untergeordneten Klassen zur Verfügung gestellt werden.

Ja, einige der Tests in 'DashboardTest' könnten in andere spezifische Klassen verschoben werden, aber ich wollte sie hier behalten, da sie allgemeiner sind (z. passen nicht in eine bestimmte Kategorie oder sind eine Mischung).

Sie könnten immer noch eine Klasse namens GenericDashboardTest extends DashboardTest erstellen, die alle generischen @Test Methoden enthält.

Sicher, ich könnte eine Nicht-@test- Testklasse haben (obwohl es seltsam wäre und jemand hier versehentlich Tests hinzufügen könnte), aber es wäre nur, um dieses Problem zu vermeiden, oder?

Nun, dafür sind Code-Reviews nicht da :) Um solche Slippages zu verhindern. Die Basisklasse (von dem, was Sie erklärt haben) muss nicht die üblichen generischen @Test Methoden enthalten. Es kann sehr gut nur die Helfer und die Konfigurationen aufnehmen und Sie können eine weitere Testklasse haben, die die Basisklasse erweitert und die generischen Testmethoden beherbergt. Das sollte die Verwirrung auflösen.

Schließen dieses Problems mit einer Lösung als _wie geplant arbeiten_

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen