ChatGPT解决这个技术问题 Extra ChatGPT

Get name of currently executing test in JUnit 4

In JUnit 3, I could get the name of the currently running test like this:

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

which would print "Current test is testSomething".

Is there any out-of-the-box or simple way to do this in JUnit 4?

Background: Obviously, I don't want to just print the name of the test. I want to load test-specific data that is stored in a resource with the same name as the test. You know, convention over configuration and all that.

What does the above code give you in JUnit 4?
JUnit 3 tests extend TestCase where getName() is defined. JUnit 4 tests do not extend a base class, so there is no getName() method at all.
I have a similar problem where I want to set the test name since I'm using the Parametrized runner that only gives me numbered test cases.
Lovely solution using Test or TestWatcher... just wondering (out loud) whether there should ever be a need for this? You can find whether a test is running slowly by looking at the timing output charts given by Gradle. You should never need to know the order in which tests operate... ?

S
Shaulian

JUnit 4.7 added this feature it seems using TestName-Rule. Looks like this will get you the method name:

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

Also note that TestName is not available in @before :( See: old.nabble.com/…
Apparently newer versions of JUnit execute @Rule before @Before - I'm new to JUnit and was depending on TestName in my @Before without any difficulties.
If you are using parameterized tests "name.getMethodName()" will return {testA[0], testA[1], etc} thus I use some like : assertTrue(name.getMethodName().matches("testA(\[\\d\])?"));
@DuncanJones Why the proposed alternative is "more efficient"?
C
Community

JUnit 4.9.x and higher

Since JUnit 4.9, the TestWatchman class has been deprecated in favour of the TestWatcher class, which has invocation:

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

Note: The containing class must be declared public.

JUnit 4.7.x - 4.8.x

The following approach will print method names for all tests in a class:

@Rule
public MethodRule watchman = new TestWatchman() {
   public void starting(FrameworkMethod method) {
      System.out.println("Starting test: " + method.getName());
   }
};

@takacsot That's surprising. Can you please post a fresh question about this and ping me the link here?
Why use a public field?
@RaffiKhatchadourian See stackoverflow.com/questions/14335558/…
A
Andrii Abramov

JUnit 5 and higher

In JUnit 5 you can inject TestInfo which simplifies test metadata injection to test methods. For example:

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

See more: JUnit 5 User guide, TestInfo javadoc.


This also works in methods marked @BeforeEach and @AfterEach. You can also define those methods in an abstract base class that your other test classes inherit/extend from, so that you only need to put the log statements in one spot instead of repeating that in every test class.
Y
Yavin5

Try this instead:

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

The output looks like this:

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

NOTE: This DOES NOT work if your test is a subclass of TestCase! The test runs but the @Rule code just never runs.


God bless you for your NOTE at the very of the example.
"This DOES NOT work" - case in point - cucumber ignores @Rule annotations
P
Peter O.

Consider using SLF4J (Simple Logging Facade for Java) provides some neat improvements using parameterized messages. Combining SLF4J with JUnit 4 rule implementations can provide more efficient test class logging techniques.

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() {

  }
}

c
chris.f.jones

A convoluted way is to create your own Runner by subclassing org.junit.runners.BlockJUnit4ClassRunner.

You can then do something like this:

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

Then for each test class, you'll need to add a @RunWith(NameAwareRunner.class) annotation. Alternatively, you could put that annotation on a Test superclass if you don't want to remember it every time. This, of course, limits your selection of runners but that may be acceptable.

Also, it may take a little bit of kung fu to get the current test name out of the Runner and into your framework, but this at least gets you the name.


Conceptually at least, this idea seems rather straightforward to me. My point being: I wouldn't call it convoluted.
"on a Test superclass ..." - Please, no more of the horrible inheritance based design patterns. This is so JUnit3!
c
cordellcp3

JUnit 4 does not have any out-of-the-box mechanism for a test case to get it’s own name (including during setup and teardown).


Is there an not-out-of-the-box mechanism out there other than inspecting the stack?
Not the case given the answers below! maybe assign the correct answer to someone else?
j
jnorris
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) {
    }
}

I can argue that he only wanted to show a solution .. do not see why the negative vote.... @downvoter: at least, at least, add useful information..
@skaffman We all love to see the full range of alternative solutions. This is the closest one for what I'm looking for: Getting the test name not directly in the testclass but in the class which gets used during the test (for example somewhere in a logger component). There, test-relevant annotations don't work anymore.
C
Csaba Tenkes

Based on the previous comment and further considering I created an extension of TestWather which you can use in your JUnit test methods with this:

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

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

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

The test helper class is the next:

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

Enjoy!


Hi what is that ImportUtilsTest, I get an error, it seems to be a logger class, do I have more information? Thanks
The named class is just an example of a JUnit test class: the user of JUnitHelper. I will correct the usage example.
Ah now I feel dumb, it was so obvious. Thanks a lot! ;)
d
davidxxx

In JUnit 5 TestInfo acts as a drop-in replacement for the TestName rule from JUnit 4.

From the documentation :

TestInfo is used to inject information about the current test or container into to @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll, and @AfterAll methods.

To retrieve the method name of the current executed test, you have two options : String TestInfo.getDisplayName() and Method TestInfo.getTestMethod().

To retrieve only the name of the current test method TestInfo.getDisplayName() may not be enough as the test method default display name is methodName(TypeArg1, TypeArg2, ... TypeArg3).
Duplicating method names in @DisplayName("..") is not necessary a good idea.

As alternative you could use TestInfo.getTestMethod() that returns a Optional<Method> object.
If the retrieval method is used inside a test method, you don't even need to test the Optional wrapped value.

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

s
silver

JUnit 5 via ExtensionContext

Advantage:

You get to have the added functionalities of ExtensionContext by overriding 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();
    }

}

l
leojkelav
@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 );
    }
};

e
emeraldjava

I'd suggest you decouple the test method name from your test data set. I would model a DataLoaderFactory class which loads/caches the sets of test data from your resources, and then in your test case cam call some interface method which returns a set of test data for the test case. Having the test data tied to the test method name assumes the test data can only be used once, where in most case i'd suggest that the same test data in uses in multiple tests to verify various aspects of your business logic.


C
Coder

You can achieve this using Slf4j and 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());
    }
};

J
James Crawford

I have a Junit4 test class that extends TestCase so the example with @Rule didn't work (as mentioned in other answers).

However, if your class extends TestCase you can use getName() to get the current test name so this works:

@Before
public void setUp() {
  System.out.println("Start test: " + getName());
}

@After
public void tearDown() {
  System.out.println("Finish test: " + getName());
}

T
Tomasz

I usually use something like this:

/** Returns text with test method name
    @param offset index of method on call stack to print, 1 for a caller of this method.
    */
    static String getName(int offset)
    { 
        Throwable t = new Throwable();
        t.fillInStackTrace();
        return 
               t.getStackTrace()[offset].getMethodName()+":"+t.getStackTrace()[offset].getLineNumber(); 
    };

This is exactly what Exception do use when printing stack trace. Depending on the exact context You may have to figure out correct offset value. It is crude and primitive tough and is not using any fancy modern futures.