获取JUnit 4中当前正在执行的测试的名称

在JUnit 3中,我可以像这样获得当前运行的测试的名称:

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

它会打印“当前测试是testSomething”。

在JUnit 4中是否有任何开箱即用或简单的方法来做到这一点?

背景:显然,我不想只打印测试的名称。我希望加载存储在与测试同名的资源中的特定于测试的数据。你知道,约定优于配置之类的。

145150 次浏览

JUnit 4没有任何开箱即用的机制让测试用例获得自己的名称(包括在设置和拆卸过程中)。

我建议您将测试方法名称与测试数据集分离。我将建模一个DataLoaderFactory类,它从您的资源中加载/缓存测试数据集,然后在您的测试用例中调用一些接口方法,该方法为测试用例返回一组测试数据。将测试数据绑定到测试方法名称假设测试数据只能使用一次,在大多数情况下,我建议在多个测试中使用相同的测试数据来验证业务逻辑的各个方面。

一种复杂的方法是通过子类化org.junit.runners.BlockJUnit4ClassRunner来创建自己的Runner。

然后你可以这样做:

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

然后,对于每个测试类,您需要添加一个@RunWith(nameawarerener .class)注释。或者,如果不想每次都记住它,也可以将该注释放在Test超类上。当然,这限制了跑步者的选择,但这可能是可以接受的。

此外,将当前测试名称从Runner中取出并放入框架可能需要一些功夫,但这至少为您提供了名称。

JUnit 4.7似乎使用TestName-Rule添加了这个特性。看起来这将为您提供方法名称:

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

考虑使用SLF4J (Java的简单日志Facade)使用参数化消息提供了一些整洁的改进。将SLF4J与JUnit 4规则实现相结合可以提供更有效的测试类日志记录技术。

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


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

JUnit 4.9。X及以上

自JUnit 4.9以来,TestWatchman类已被弃用,取而代之的是TestWatcher类,它具有以下调用:

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

注意:包含类必须声明为public

JUnit 4.7。X - 4.8.x

下面的方法将打印类中所有测试的方法名称:

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

试试这个吧:

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

输出如下所示:

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

注意:如果你的测试是TestCase!测试运行了,但@Rule代码从未运行。

你可以使用Slf4jTestWatcher来实现

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

基于前面的评论,并进一步考虑我创建了一个TestWather的扩展,你可以在你的JUnit测试方法中使用:

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


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


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

测试助手类是下一个:

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

享受吧!

JUnit 5及更高版本

JUnit 5中,你可以注入TestInfo,这简化了测试元数据注入到测试方法。例如:

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

更多信息:JUnit 5用户指南TestInfo javadoc

在JUnit 5中,TestInfo充当了JUnit 4中的TestName规则的替代物。

从文档中可以看到:

TestInfo用于注入当前测试或 容器到@Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll和@AfterAll 方法。< / p >

要检索当前执行测试的方法名,您有两个选项:String TestInfo.getDisplayName()Method TestInfo.getTestMethod()。< / p > 如果只检索当前测试方法的名称TestInfo.getDisplayName()可能不够,因为测试方法的默认显示名称是methodName(TypeArg1, TypeArg2, ... TypeArg3).
@DisplayName("..")中复制方法名并不一定是个好主意。< / p >

作为你可以使用的替代方案 TestInfo.getTestMethod()返回一个Optional<Method>对象 如果在测试方法中使用检索方法,则甚至不需要测试Optional包装的值。< / p >

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

JUnit 5通过ExtensionContext

优势:

你可以通过覆盖afterEach(ExtensionContext context)来增加ExtensionContext的功能。

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


}

我有一个扩展TestCase的Junit4测试类,所以@Rule的示例不能工作(正如在其他回答中提到的那样)。

然而,如果你的类扩展了TestCase,你可以使用getName()来获取当前的测试名称,这样可以工作:

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


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

我通常使用这样的方法:

/** 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();
};
这正是Exception在打印堆栈跟踪时所使用的。 根据具体的上下文,您可能需要计算出正确的偏移值。它是粗糙的,原始的,坚韧的,没有使用任何花哨的现代期货