Получить имя выполняемого в данный момент теста в JUnit 4

240

В JUnit 3 я мог получить имя текущего запущенного теста, например:

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

который напечатал бы "Текущий тест - testSomething".

Есть ли какой-либо из готовых или простой способ сделать это в JUnit 4?

Фон: Очевидно, я не хочу просто печатать название теста. Я хочу загрузить специфичные для теста данные, которые хранятся в ресурсе с тем же именем, что и тест. Вы знаете, соглашение о конфигурации и все такое.

Дэйв Рэй
источник
Что вышеупомянутый код дает вам в JUnit 4?
Билл Ящерица
5
Тесты JUnit 3 расширяют TestCase, где определяется getName (). Тесты JUnit 4 не расширяют базовый класс, поэтому метод getName () вообще отсутствует.
Дэйв Рэй
У меня похожая проблема, когда я хочу <b> установить </ b> имя теста, так как я использую параметризованный бегун, который дает мне только пронумерованные тесты.
Volker Stolz
Прекрасное решение с использованием Test или TestWatcher ... просто интересно (вслух), должна ли когда-нибудь быть в этом необходимость? Вы можете узнать, работает ли тест медленно, посмотрев на временные графики выхода, данные Gradle. Вам никогда не нужно знать порядок, в котором работают тесты ...?
Майк Грызун

Ответы:

378

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());
    }
}
Fromage
источник
4
Также обратите внимание, что TestName недоступен в @before :( См .: old.nabble.com/…
jm.
41
Видимо, более новые версии JUnit выполнялись @Ruleраньше @Before- я новичок в JUnit и зависел от TestNameмоего @Beforeбез каких-либо трудностей.
MightyE
2
Если вы используете параметризованные тесты, «name.getMethodName ()» вернет {testA [0], testA [1] и т. Д.}, Поэтому я использую некоторые подобные: assertTrue (name.getMethodName (). Match ("testA (\ [\ \ д \]) "));
Легна
2
@DuncanJones Почему предлагаемая альтернатива является «более эффективной»?
Стефан
117

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());
   }
};
Дункан Джонс
источник
1
@takacsot Это удивительно. Не могли бы вы опубликовать свежий вопрос по этому поводу и отправить мне ссылку здесь?
Дункан Джонс
Зачем использовать publicполе?
Раффи Хачадурян
1
@RaffiKhatchadourian См stackoverflow.com/questions/14335558/...
Дункан Джонс
16

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 .

Андрей Абрамов
источник
9

Попробуйте это вместо этого:

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 просто никогда не запускается.

Yavin5
источник
3
Да благословит вас Бог за ваше ПРИМЕЧАНИЕ на самом примере.
user655419 29.09.13
«Это НЕ РАБОТАЕТ» - в данном случае - огурец игнорирует аннотации
@Rule
8

Подумайте об использовании 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() {

  }
}
подмастерье
источник
6

Сложным способом является создание собственного 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 в вашу среду, но это по крайней мере даст вам имя.

chris.f.jones
источник
Концептуально, по крайней мере, эта идея кажется мне довольно простой. Моя точка зрения такова: я бы не назвал это запутанным.
user98761 13.12.12
«на тестовом суперклассе ...» - пожалуйста, не надо больше шаблонов проектирования на основе ужасного наследования. Это так JUnit3!
Oberlies
3

JUnit 4 не имеет никакого готового механизма для тестового случая, чтобы получить свое собственное имя (в том числе во время установки и демонтажа).

cordellcp3
источник
1
Существует ли какой-нибудь нестандартный механизм, кроме проверки стека?
Дэйв Рэй
4
Не тот случай, учитывая ответы ниже! может назначить правильный ответ кому-то еще?
Тоби
3
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) {
    }
}
jnorris
источник
1
Я могу утверждать, что он только хотел показать решение проблемы .. не понимаю, почему голосование "за" .... @downvoter: по крайней мере, добавить полезную информацию ..
Виктор
1
@skaffman Мы все рады видеть весь спектр альтернативных решений. Это самое близкое из того, что я ищу: получить имя теста не непосредственно в классе теста, а в классе, который используется во время теста (например, где-нибудь в компоненте регистратора). Там аннотации к тестам больше не работают.
Даниэль Олдер
3

На основании предыдущего комментария и дальнейшего рассмотрения я создал расширение 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, я получаю сообщение об ошибке, кажется, это класс регистратора, у меня есть больше информации? Спасибо
Sylhare
1
Именованный класс является просто примером тестового класса JUnit: пользователь JUnitHelper. Я исправлю пример использования.
Csaba Tenkes
Ах, теперь я чувствую себя глупо, это было так очевидно. Большое спасибо! ;)
Sylhare
1
@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 );
    }
};
leojkelav
источник
0

Я бы посоветовал вам отделить название метода тестирования от набора тестовых данных. Я бы смоделировал класс DataLoaderFactory, который загружает / кэширует наборы тестовых данных из ваших ресурсов, а затем в вашем тестовом примере cam вызывает некоторый интерфейсный метод, который возвращает набор тестовых данных для тестового примера. Привязка тестовых данных к имени метода теста предполагает, что тестовые данные могут использоваться только один раз, тогда как в большинстве случаев я рекомендую использовать одни и те же тестовые данные в нескольких тестах для проверки различных аспектов вашей бизнес-логики.

emeraldjava
источник
0

Вы можете достичь этого, используя 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());
    }
};
кодировщик
источник
0

В Юнит 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>объект.
Если метод извлечения используется внутри метода test, вам даже не нужно проверять 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());
}
davidxxx
источник
0

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

}
Серебряный
источник