在 JUnit 3 中,我可以像这样获取当前正在运行的测试的名称:
public class MyTest extends TestCase
{
public void testSomething()
{
System.out.println("Current test is " + getName());
...
}
}
这将打印“当前测试是 testSomething”。
在 JUnit 4 中是否有任何开箱即用或简单的方法可以做到这一点?
背景:显然,我不想只打印测试的名称。我想加载存储在与测试同名的资源中的测试特定数据。你知道,convention over configuration 等等。
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());
}
}
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
字段?
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 User guide、TestInfo javadoc。
@BeforeEach
和 @AfterEach
的方法。您还可以在其他测试类继承/扩展的抽象基类中定义这些方法,这样您只需将日志语句放在一个位置,而不是在每个测试类中重复。
试试这个:
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 代码永远不会运行。
考虑使用 SLF4J(Java 的简单日志记录外观)使用参数化消息提供了一些巧妙的改进。将 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() {
}
}
一个复杂的方法是通过继承 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(NameAwareRunner.class) 注释。或者,如果您不想每次都记住它,可以将该注释放在 Test 超类上。当然,这会限制您对跑步者的选择,但这可能是可以接受的。
此外,从 Runner 中获取当前测试名称并进入您的框架可能需要一点功夫,但这至少可以让您获得名称。
JUnit 4 没有任何开箱即用的机制让测试用例获得自己的名称(包括在设置和拆卸期间)。
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) {
}
}
基于之前的评论并进一步考虑,我创建了 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());
}
}
享受!
ImportUtilsTest
,我收到一个错误,它似乎是一个记录器类,我有更多信息吗?谢谢
在 JUnit 5 中,TestInfo
充当 JUnit 4 中 TestName 规则的替代品。
从文档中:
TestInfo 用于将有关当前测试或容器的信息注入到 @Test、@RepeatedTest、@ParameterizedTest、@TestFactory、@BeforeEach、@AfterEach、@BeforeAll 和 @AfterAll 方法中。
要检索当前执行的测试的方法名称,您有两个选项:String TestInfo.getDisplayName()
和 Method TestInfo.getTestMethod()
。
仅检索当前测试方法 TestInfo.getDisplayName()
的名称可能不够,因为测试方法的默认显示名称是 methodName(TypeArg1, TypeArg2, ... TypeArg3)
。
在 @DisplayName("..")
中复制方法名称不是一个好主意。
作为替代方案,您可以使用返回 Optional<Method>
对象的 TestInfo.getTestMethod()
。
如果在测试方法中使用检索方法,您甚至不需要测试 Optional
包装的值。
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();
}
}
@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 );
}
};
我建议您将测试方法名称与测试数据集分离。我将建模一个 DataLoaderFactory 类,该类从您的资源中加载/缓存测试数据集,然后在您的测试用例中调用一些接口方法,该方法返回一组测试用例的测试数据。将测试数据绑定到测试方法名称假设测试数据只能使用一次,在大多数情况下,我建议在多个测试中使用相同的测试数据来验证业务逻辑的各个方面。
您可以使用 Slf4j
和 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());
}
};
我有一个扩展 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();
};
这正是打印堆栈跟踪时使用的异常。根据确切的上下文您可能必须找出正确的偏移值。它粗犷而原始坚韧,没有使用任何花哨的现代期货。
不定期副业成功案例分享
@Before
之前执行@Rule
- 我是 JUnit 的新手,并且在我的@Before
中依赖TestName
没有任何困难。