In JUnit 3 konnte ich den Namen des aktuell laufenden Tests folgendermaßen erhalten:
public class MyTest extends TestCase
{
public void testSomething()
{
System.out.println("Current test is " + getName());
...
}
}
was würde drucken "Aktueller Test ist TestSomething".
Gibt es eine vordefinierte oder einfache Möglichkeit, dies in JUnit 4 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 das alles.
JUnit 4.7 fügte diese Funktion hinzu. Es scheint, als würde sie TestName-Rule verwenden. 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());
}
}
Seit JUnit 4.9 wurde die TestWatchman
-Klasse zugunsten der TestWatcher
-Klasse aufgegeben:
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
System.out.println("Starting test: " + description.getMethodName());
}
};
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());
}
};
In JUnit 5 gibt es eine TestInfo
-Injection, die die Bereitstellung von Test-Metadaten für Testmethoden vereinfacht. Zum Beispiel:
@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"));
}
Mehr dazu: JUnit 5 Benutzerhandbuch , TestInfo javadoc .
Erwägen Sie die Verwendung von SLF4J (Simple Logging Facade für Java) und bieten einige Verbesserungen mit parametrisierten Meldungen. Durch die Kombination von SLF4J mit JUnit 4-Regelimplementierungen können effizientere Testklassen-Protokollierungsverfahren 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() {
}
}
Eine gewundene Möglichkeit besteht darin, einen eigenen Runner zu erstellen, indem Sie org.junit.runners.BlockJUnit4ClassRunner subclassing.
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);
}
}
Dann müssen Sie für jede Testklasse eine Annotation @RunWith (NameAwareRunner.class) hinzufügen. Alternativ können Sie diese Anmerkung in eine Superklasse "Test" setzen, wenn Sie sich nicht jedes Mal daran erinnern wollen. Dies begrenzt natürlich die Auswahl der Läufer, dies kann jedoch akzeptabel sein.
Es kann auch ein wenig Kung-Fu erforderlich sein, um den aktuellen Testnamen aus dem Runner und in Ihr Framework zu holen, aber dies bringt Ihnen zumindest den Namen.
Versuchen Sie es stattdessen:
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: DieseDOES NOTfunktioniert nicht, wenn Ihr Test eine Unterklasse von TestCase ist! Der Test läuft, aber der @Rule - Code wird nie ausgeführt.
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 Testhelper-Klasse 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!
JUnit 4 verfügt über keinen Standardmechanismus für einen Testfall, um seinen eigenen Namen zu erhalten (einschließlich während des Setups und des Herunterfahrens).
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) {
}
}
@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 );
}
};
In JUnit 5 fungiert TestInfo
als Drop-In-Ersatz für die TestName-Regel von JUnit 4.
Aus der Dokumentation:
TestInfo wird verwendet, um Informationen zum aktuellen Test oder .__ einzugeben. Container in @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll und @AfterAll Methoden.
Um den Methodennamen des aktuell ausgeführten Tests abzurufen, haben Sie zwei Optionen: String TestInfo.getDisplayName()
und Method TestInfo.getTestMethod()
.
Um nur den Namen der aktuellen Testmethode abzurufen, reicht TestInfo.getDisplayName()
möglicherweise nicht aus, da der Standardanzeigename der Testmethode methodName(TypeArg1, TypeArg2, ... TypeArg3)
lautet.
Das Duplizieren von Methodennamen in @DisplayName("..")
ist keine gute Idee.
Als Alternative können Sie TestInfo.getTestMethod()
verwenden, die ein Optional<Method>
-Objekt zurückgibt.
Wenn die Abrufmethode innerhalb einer Testmethode verwendet wird, müssen Sie nicht einmal den umschlossenen Wert Optional
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());
}
Dies erreichen Sie mit Slf4j
und TestWatcher
.
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());
}
};
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 cam eine Schnittstellenmethode aufrufen, die einen Testdatensatz für den Testfall zurückgibt. Wenn die Testdaten an den Namen der Testmethode gebunden 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.