Ruft den Namen des aktuell ausgeführten Tests in JUnit 4 ab

240

In JUnit 3 konnte ich den Namen des aktuell laufenden Tests wie folgt abrufen:

public class MyTest extends TestCase
{
    public void testSomething()
    {
        System.out.println("Current test is " + getName());
        ...
    }
}

das würde "Aktueller Test ist testSomething" drucken.

Gibt es in JUnit 4 eine sofort einsatzbereite oder einfache Möglichkeit, dies zu tun?

Hintergrund: Natürlich möchte ich nicht nur den Namen des Tests ausdrucken. Ich möchte testspezifische Daten laden, die in einer Ressource mit demselben Namen wie der Test gespeichert sind. Sie wissen, Konvention über Konfiguration und all das.

Dave Ray
quelle
Was gibt Ihnen der obige Code in JUnit 4?
Bill the Lizard
5
JUnit 3-Tests erweitern TestCase, in dem getName () definiert ist. JUnit 4-Tests erweitern keine Basisklasse, daher gibt es überhaupt keine getName () -Methode.
Dave Ray
Ich habe ein ähnliches Problem, bei dem ich den Testnamen <b> festlegen </ b> möchte, da ich den parametrisierten Läufer verwende, der mir nur nummerierte Testfälle gibt.
Volker Stolz
Schöne Lösung mit Test oder TestWatcher ... Sie fragen sich nur (laut), ob dies jemals nötig sein sollte? Ob ein Test langsam abläuft, können Sie anhand der von Gradle angegebenen Timing-Ausgabediagramme feststellen. Sie sollten nie wissen müssen, in welcher Reihenfolge Tests durchgeführt werden ...?
Mike Nagetier

Antworten:

378

JUnit 4.7 hat diese Funktion hinzugefügt, anscheinend mit TestName-Rule . Sieht so aus, als würden Sie den Methodennamen erhalten:

import org.junit.Rule;

public class NameRuleTest {
    @Rule public TestName name = new TestName();

    @Test public void testA() {
        assertEquals("testA", name.getMethodName());
    }

    @Test public void testB() {
        assertEquals("testB", name.getMethodName());
    }
}
FroMage
quelle
4
Beachten Sie auch, dass TestName in @before nicht verfügbar ist :( Siehe: old.nabble.com/…
jm.
41
Offenbar neuere Versionen von JUnit ausführen , @Rulebevor @Before- ich JUnit bin neu und wurde je nach TestNamemeinem @Beforeohne Schwierigkeiten.
MightyE
9
Hierfür stehen effizientere Möglichkeiten zur Verfügung .
Duncan Jones
2
Wenn Sie parametrisierte Tests verwenden, gibt "name.getMethodName ()" {testA [0], testA [1] usw.} zurück, daher verwende ich Folgendes: assertTrue (name.getMethodName (). Matches ("testA (\ [\ \ d \])? "));
Legna
2
@ DuncanJones Warum ist die vorgeschlagene Alternative "effizienter"?
Stephan
117

JUnit 4.9.x und höher

Seit JUnit 4.9 ist die TestWatchmanKlasse zugunsten der TestWatcherKlasse mit dem Aufruf veraltet :

@Rule
public TestRule watcher = new TestWatcher() {
   protected void starting(Description description) {
      System.out.println("Starting test: " + description.getMethodName());
   }
};

Hinweis: Die enthaltende Klasse muss deklariert werden public.

JUnit 4.7.x - 4.8.x.

Der folgende Ansatz druckt Methodennamen für alle Tests in einer Klasse:

@Rule
public MethodRule watchman = new TestWatchman() {
   public void starting(FrameworkMethod method) {
      System.out.println("Starting test: " + method.getName());
   }
};
Duncan Jones
quelle
1
@akacsot Das ist überraschend. Können Sie bitte eine neue Frage dazu posten und mir den Link hier anpingen?
Duncan Jones
Warum ein publicFeld verwenden?
Raffi Khatchadourian
1
@ RaffiKhatchadourian Siehe stackoverflow.com/questions/14335558/…
Duncan Jones
16

Einheit 5 und höher

In JUnit 5 können Sie injizieren, TestInfowas die Bereitstellung von Test-Metadaten für Testmethoden vereinfacht. Beispielsweise:

@Test
@DisplayName("This is my test")
@Tag("It is my tag")
void test1(TestInfo testInfo) {
    assertEquals("This is my test", testInfo.getDisplayName());
    assertTrue(testInfo.getTags().contains("It is my tag"));
}

Weitere Informationen : JUnit 5 Benutzerhandbuch , TestInfo javadoc .

Andrii Abramov
quelle
9

Versuchen Sie stattdessen Folgendes:

public class MyTest {
        @Rule
        public TestName testName = new TestName();

        @Rule
        public TestWatcher testWatcher = new TestWatcher() {
            @Override
            protected void starting(final Description description) {
                String methodName = description.getMethodName();
                String className = description.getClassName();
                className = className.substring(className.lastIndexOf('.') + 1);
                System.err.println("Starting JUnit-test: " + className + " " + methodName);
            }
        };

        @Test
        public void testA() {
                assertEquals("testA", testName.getMethodName());
        }

        @Test
        public void testB() {
                assertEquals("testB", testName.getMethodName());
        }
}

Die Ausgabe sieht folgendermaßen aus:

Starting JUnit-test: MyTest testA
Starting JUnit-test: MyTest testB

HINWEIS: Dies funktioniert NICHT , wenn Ihr Test eine Unterklasse von TestCase ist ! Der Test wird ausgeführt, aber der @ Regel- Code wird nie ausgeführt.

Yavin5
quelle
3
Gott segne Sie für Ihre ANMERKUNG ganz im Beispiel.
user655419
"Das funktioniert NICHT" - Beispiel: Gurke ignoriert @ Regel-Annotationen
Benjineer
8

Die Verwendung von SLF4J (Simple Logging Facade für Java) bietet einige nette Verbesserungen bei der Verwendung parametrisierter Nachrichten. Durch die Kombination von SLF4J mit JUnit 4-Regelimplementierungen können effizientere Protokollierungstechniken für Testklassen bereitgestellt werden.

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
import org.junit.rules.TestWatchman;
import org.junit.runners.model.FrameworkMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggingTest {

  @Rule public MethodRule watchman = new TestWatchman() {
    public void starting(FrameworkMethod method) {
      logger.info("{} being run...", method.getName());
    }
  };

  final Logger logger =
    LoggerFactory.getLogger(LoggingTest.class);

  @Test
  public void testA() {

  }

  @Test
  public void testB() {

  }
}
Geselle
quelle
6

Ein komplizierter Weg besteht darin, einen eigenen Runner zu erstellen, indem Sie org.junit.runners.BlockJUnit4ClassRunner in eine Unterklasse unterteilen.

Sie können dann so etwas tun:

public class NameAwareRunner extends BlockJUnit4ClassRunner {

    public NameAwareRunner(Class<?> aClass) throws InitializationError {
        super(aClass);
    }

    @Override
    protected Statement methodBlock(FrameworkMethod frameworkMethod) {
        System.err.println(frameworkMethod.getName());
        return super.methodBlock(frameworkMethod);
    }
}

Anschließend müssen Sie für jede Testklasse eine Annotation @RunWith (NameAwareRunner.class) hinzufügen. Alternativ können Sie diese Anmerkung in eine Test-Superklasse einfügen, wenn Sie sich nicht jedes Mal daran erinnern möchten. Dies schränkt natürlich Ihre Auswahl an Läufern ein, aber das kann akzeptabel sein.

Es kann auch ein wenig Kung Fu dauern, bis der aktuelle Testname aus dem Runner in Ihr Framework gelangt, aber dadurch erhalten Sie zumindest den Namen.

chris.f.jones
quelle
Zumindest konzeptionell scheint mir diese Idee ziemlich einfach zu sein. Mein Punkt ist: Ich würde es nicht als verschlungen bezeichnen.
user98761
"on a Test superclass ..." - Bitte nicht mehr von den schrecklichen vererbungsbasierten Designmustern. Das ist so JUnit3!
Oberlies
3

JUnit 4 verfügt nicht über einen sofort einsatzbereiten Mechanismus für einen Testfall, um seinen eigenen Namen zu erhalten (auch während des Auf- und Abbaus).

cordellcp3
quelle
1
Gibt es da draußen einen anderen Standardmechanismus als die Inspektion des Stapels?
Dave Ray
4
Nicht der Fall angesichts der Antworten unten! Vielleicht jemand anderem die richtige Antwort zuweisen?
Toby
3
String testName = null;
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
for (int i = trace.length - 1; i > 0; --i) {
    StackTraceElement ste = trace[i];
    try {
        Class<?> cls = Class.forName(ste.getClassName());
        Method method = cls.getDeclaredMethod(ste.getMethodName());
        Test annotation = method.getAnnotation(Test.class);
        if (annotation != null) {
            testName = ste.getClassName() + "." + ste.getMethodName();
            break;
        }
    } catch (ClassNotFoundException e) {
    } catch (NoSuchMethodException e) {
    } catch (SecurityException e) {
    }
}
jnorris
quelle
1
Ich kann argumentieren, dass er nur eine Lösung zeigen wollte .. nicht sehen, warum die negative Abstimmung .... @downvoter: zumindest zumindest nützliche Informationen hinzufügen ..
Victor
1
@skaffman Wir alle lieben es, die gesamte Palette alternativer Lösungen zu sehen. Dies ist die nächstgelegene für das, wonach ich suche: Abrufen des Testnamens nicht direkt in der Testklasse, sondern in der Klasse, die während des Tests verwendet wird (z. B. irgendwo in einer Logger-Komponente). Dort funktionieren testrelevante Annotationen nicht mehr.
Daniel Alder
3

Basierend auf dem vorherigen Kommentar und weiteren Überlegungen habe ich eine Erweiterung von TestWather erstellt, die Sie in Ihren JUnit-Testmethoden verwenden können:

public class ImportUtilsTest {
    private static final Logger LOGGER = Logger.getLogger(ImportUtilsTest.class);

    @Rule
    public TestWatcher testWatcher = new JUnitHelper(LOGGER);

    @Test
    public test1(){
    ...
    }
}

Die Testhelferklasse ist die nächste:

public class JUnitHelper extends TestWatcher {
private Logger LOGGER;

public JUnitHelper(Logger LOGGER) {
    this.LOGGER = LOGGER;
}

@Override
protected void starting(final Description description) {
    LOGGER.info("STARTED " + description.getMethodName());
}

@Override
protected void succeeded(Description description) {
    LOGGER.info("SUCCESSFUL " + description.getMethodName());
}

@Override
protected void failed(Throwable e, Description description) {
    LOGGER.error("FAILURE " + description.getMethodName());
}
}

Genießen!

Csaba Tenkes
quelle
Hallo was ist das ImportUtilsTest, ich bekomme eine Fehlermeldung, es scheint eine Logger-Klasse zu sein, habe ich weitere Informationen? Danke
Sylhare
1
Die benannte Klasse ist nur ein Beispiel für eine JUnit-Testklasse: der Benutzer von JUnitHelper. Ich werde das Verwendungsbeispiel korrigieren.
Csaba Tenkes
Ah, jetzt fühle ich mich dumm, es war so offensichtlich. Vielen Dank! ;)
Sylhare
1
@ClassRule
public static TestRule watchman = new TestWatcher() {
    @Override
    protected void starting( final Description description ) {
        String mN = description.getMethodName();
        if ( mN == null ) {
            mN = "setUpBeforeClass..";
        }

        final String s = StringTools.toString( "starting..JUnit-Test: %s.%s", description.getClassName(), mN );
        System.err.println( s );
    }
};
Leojkelav
quelle
0

Ich würde vorschlagen, dass Sie den Namen der Testmethode von Ihrem Testdatensatz entkoppeln. Ich würde eine DataLoaderFactory-Klasse modellieren, die die Testdatensätze aus Ihren Ressourcen lädt / zwischenspeichert, und dann in Ihrem Testfall eine Schnittstellenmethode aufrufen, die einen Satz von Testdaten für den Testfall zurückgibt. Wenn die Testdaten mit dem Namen der Testmethode verknüpft sind, wird davon ausgegangen, dass die Testdaten nur einmal verwendet werden können. In den meisten Fällen würde ich vorschlagen, dass dieselben Testdaten in mehreren Tests verwendet werden, um verschiedene Aspekte Ihrer Geschäftslogik zu überprüfen.

smaragdjava
quelle
0

Sie können dies mit Slf4jund erreichenTestWatcher

private static Logger _log = LoggerFactory.getLogger(SampleTest.class.getName());

@Rule
public TestWatcher watchman = new TestWatcher() {
    @Override
    public void starting(final Description method) {
        _log.info("being run..." + method.getMethodName());
    }
};
Codierer
quelle
0

In JUnit 5 TestInfofungiert es als Drop-In-Ersatz für die TestName-Regel von JUnit 4.

Aus der Dokumentation:

TestInfo wird verwendet, um Informationen über den aktuellen Test oder Container in die Methoden @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll und @AfterAll einzufügen.

Um den Methodennamen des aktuell ausgeführten Tests abzurufen, haben Sie zwei Möglichkeiten: String TestInfo.getDisplayName()und Method TestInfo.getTestMethod().

Es TestInfo.getDisplayName()reicht möglicherweise nicht aus, nur den Namen der aktuellen Testmethode abzurufen , da der Standardanzeigename der Testmethode lautet methodName(TypeArg1, TypeArg2, ... TypeArg3).
Duplizieren von Methodennamen in@DisplayName("..") ist keine gute Idee.

Alternativ können Sie TestInfo.getTestMethod()ein Optional<Method>Objekt zurückgeben.
Wenn die Abrufmethode innerhalb einer Testmethode verwendet wird, müssen Sie den Optionalumschlossenen Wert nicht einmal testen .

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.Test;

@Test
void doThat(TestInfo testInfo) throws Exception {
    Assertions.assertEquals("doThat(TestInfo)",testInfo.getDisplayName());
    Assertions.assertEquals("doThat",testInfo.getTestMethod().get().getName());
}
davidxxx
quelle
0

JUnit 5 über ExtensionContext

Vorteil:

Sie erhalten die zusätzlichen Funktionen von ExtensionContextdurch Überschreiben afterEach(ExtensionContext context).

public abstract class BaseTest {

    protected WebDriver driver;

    @RegisterExtension
    AfterEachExtension afterEachExtension = new AfterEachExtension();

    @BeforeEach
    public void beforeEach() {
        // Initialise driver
    }

    @AfterEach
    public void afterEach() {
        afterEachExtension.setDriver(driver);
    }

}
public class AfterEachExtension implements AfterEachCallback {

    private WebDriver driver;

    public void setDriver(WebDriver driver) {
        this.driver = driver;
    }

    @Override
    public void afterEach(ExtensionContext context) {
        String testMethodName = context.getTestMethod().orElseThrow().getName();
        // Attach test steps, attach scsreenshots on failure only, etc.
        driver.quit();
    }

}
Silber
quelle