Получить имя выполняемого в данный момент теста в JUnit 4
В JUnit 3 я мог получить имя текущего запущенного теста, например:
public class MyTest extends TestCase
{
public void testSomething()
{
System.out.println("Current test is " + getName());
...
}
}
который напечатал бы "Текущий тест - testSomething".
Есть ли какой-либо из готовых или простой способ сделать это в JUnit 4?
Справочная информация: Очевидно, я не хочу просто печатать название теста. Я хочу загрузить специфичные для теста данные, которые хранятся в ресурсе с тем же именем, что и тест. Вы знаете, соглашение о конфигурации и все такое.
17 ответов
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());
}
};
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());
}
};
В Юнит 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.
Попробуйте это вместо этого:
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 (Simple Logging Facade для 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() {
}
}
Сложным способом является создание собственного Runner путем создания подкласса org.junit.runners.BlockJUnit4ClassRunner.
Затем вы можете сделать что-то вроде этого:
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 в вашу среду, но это, по крайней мере, даст вам имя.
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) {
}
}
JUnit 4 не имеет никакого готового механизма для тестового случая, чтобы получить свое собственное имя (в том числе во время установки и демонтажа).
На основании предыдущего комментария и дальнейшего рассмотрения я создал расширение 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());
}
}
Наслаждайтесь!
В Юнит 5 TestInfo
выступает в качестве замены для правила TestName из JUnit 4.
Из документации:
TestInfo используется для добавления информации о текущем тесте или контейнере в методы @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll и @AfterAll.
Чтобы получить имя метода текущего выполненного теста, у вас есть два варианта: String TestInfo.getDisplayName()
а также Method TestInfo.getTestMethod()
,
Получить только имя текущего метода тестирования TestInfo.getDisplayName()
может быть недостаточно, поскольку отображаемое имя по умолчанию для метода тестирования methodName(TypeArg1, TypeArg2, ... TypeArg3)
,
Дублирование имен методов в @DisplayName("..")
не нужна хорошая идея.
В качестве альтернативы вы можете использовать TestInfo.getTestMethod()
который возвращает Optional<Method>
объект.
Если метод извлечения используется внутри тестового метода, вам даже не нужно тестировать 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
Преимущество:
Вы получите дополнительные функции ExtensionContext
отвергая 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();
}
}
У меня есть тестовый класс Junit4, который расширяет TestCase, поэтому пример с @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 использует при печати трассировки стека. В зависимости от точного контекста вам, возможно, придется выяснить правильное значение смещения. Он груб и примитивен и не использует никаких причудливых современных фьючерсов.
@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, который загружает / кэширует наборы тестовых данных из ваших ресурсов, а затем в вашем тестовом примере cam вызывает некоторый интерфейсный метод, который возвращает набор тестовых данных для тестового примера. Привязка тестовых данных к имени метода теста предполагает, что тестовые данные могут использоваться только один раз, тогда как в большинстве случаев я бы рекомендовал использовать одни и те же тестовые данные в нескольких тестах для проверки различных аспектов вашей бизнес-логики.
Вы можете добиться этого, используя 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());
}
};
Более простой способ — поместить эту логику в методы setUp() и tearDown() .
Обратитесь к приведенному ниже коду для большей ясности,
import java.lang.reflect.Method;
@BeforeMethod
void setUp(Method method) {
log.info("###############################################");
log.info("Running Test: {}", method.getName());
}
@AfterMethod
void tearDown(Method method) {
log.info("Finished Test: {}", method.getName());
log.info("###############################################");
}
@Test
public void testMethodName() {
// Method logic implementation...
}
Вот результат выполнения вышеуказанного теста,
#############################################################
Running Test: testMethodName
// Logs related to method execution...
Finished Test: testMethodName
#############################################################