Obter o nome do teste atualmente em execução no JUnit 4

240

No JUnit 3, eu poderia obter o nome do teste atualmente em execução assim:

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

que imprimiria "Teste atual é testSomething".

Existe alguma maneira simples ou imediata de fazer isso no JUnit 4?

Antecedentes: Obviamente, não quero apenas imprimir o nome do teste. Desejo carregar dados específicos de teste armazenados em um recurso com o mesmo nome que o teste. Você sabe, convenção sobre configuração e tudo mais.

Dave Ray
fonte
O que o código acima fornece no JUnit 4?
Bill o Lagarto
5
Os testes da JUnit 3 estendem o TestCase onde getName () é definido. Os testes JUnit 4 não estendem uma classe base, portanto, não há método getName ().
23411 Dave Ray
Eu tenho um problema semelhante em que eu quero <b> definir </b> o nome do teste, pois estou usando o corredor Parametrizado que apenas me fornece casos de teste numerados.
Volker Stolz
Solução adorável usando Test ou TestWatcher ... apenas imaginando (em voz alta) se alguma vez deveria haver necessidade disso? Você pode descobrir se um teste está sendo executado lentamente, observando os gráficos de saída de tempo fornecidos por Gradle. Você nunca precisa saber a ordem em que os testes operam ...?
mike roedor

Respostas:

378

O JUnit 4.7 adicionou esse recurso, aparentemente usando a regra TestName . Parece que você receberá o nome do método:

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
fonte
4
Observe também que TestName não está disponível em @Before :( Ver: old.nabble.com/...
jm.
41
Aparentemente, as versões mais recentes do JUnit são executadas @Ruleantes @Before- eu sou novo no JUnit e dependia do TestNamemeu @Beforesem nenhuma dificuldade.
MightyE 16/04
2
Se você estiver usando testes parametrizados, "name.getMethodName ()" retornará {testA [0], testA [1], etc}, portanto, utilizarei alguns como: assertTrue (name.getMethodName (). Fósforos ("testA (\ [\ \ d \])? "));
Legna 28/05
2
@DuncanJones Por que a alternativa proposta é "mais eficiente"?
8138 Stephan
117

JUnit 4.9.xe superior

Desde a JUnit 4.9, a TestWatchmanclasse foi preterida em favor da TestWatcherclasse, que foi chamada:

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

Nota: A classe que contém deve ser declarada public.

JUnit 4.7.x - 4.8.x

A abordagem a seguir imprimirá nomes de métodos para todos os testes em uma classe:

@Rule
public MethodRule watchman = new TestWatchman() {
   public void starting(FrameworkMethod method) {
      System.out.println("Starting test: " + method.getName());
   }
};
Duncan Jones
fonte
1
@takacsot Isso é surpreendente. Você pode postar uma nova pergunta sobre isso e enviar um ping para mim aqui?
Duncan Jones
Por que usar um publiccampo?
Raffi Khatchadourian 19/03/16
1
@RaffiKhatchadourian Veja stackoverflow.com/questions/14335558/…
Duncan Jones
16

JUnit 5 e superior

No JUnit 5, você pode injetar, o TestInfoque simplifica os metadados de teste, fornecendo aos métodos de teste. Por exemplo:

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

Veja mais: Guia do usuário do JUnit 5 , TestInfo javadoc .

Andrii Abramov
fonte
9

Tente isso:

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

A saída é assim:

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

NOTA: Isso NÃO funciona se o seu teste for uma subclasse do TestCase ! O teste é executado, mas o código @Rule simplesmente nunca é executado.

Yavin5
fonte
3
Deus o abençoe por sua NOTA no próprio exemplo.
user655419
"Este trabalho não" - caso em questão - pepino ignora anotações @Rule
Benjineer
8

Considere que o uso do SLF4J (Simple Logging Facade for Java) fornece algumas melhorias simples usando mensagens parametrizadas. A combinação de implementações de regra SLF4J com JUnit 4 pode fornecer técnicas de log de classe de teste mais eficientes.

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

  }
}
viajante
fonte
6

Uma maneira complicada é criar seu próprio Runner subclassificando org.junit.runners.BlockJUnit4ClassRunner.

Você pode fazer algo assim:

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

Em seguida, para cada classe de teste, você precisará adicionar uma anotação @RunWith (NameAwareRunner.class). Como alternativa, você pode colocar essa anotação em uma superclasse de Teste, se não quiser lembrá-la todas as vezes. Obviamente, isso limita sua seleção de corredores, mas isso pode ser aceitável.

Além disso, pode levar um pouco de kung fu para tirar o nome do teste atual do Runner e entrar em sua estrutura, mas isso pelo menos dá o nome a você.

chris.f.jones
fonte
Conceitualmente, pelo menos, essa ideia me parece bastante direta. Meu argumento é: eu não chamaria isso de complicado.
user98761
"em uma superclasse de teste ..." - Por favor, não há mais os horríveis padrões de design baseados em herança. Isso é tão JUnit3!
Oberlies
3

O JUnit 4 não possui nenhum mecanismo pronto para uso para que um caso de teste obtenha seu próprio nome (inclusive durante a instalação e desmontagem).

cordellcp3
fonte
1
Existe um mecanismo não pronto para uso que não seja a inspeção da pilha?
Dave Ray
4
Não é o caso, dadas as respostas abaixo! talvez atribua a resposta correta a outra pessoa?
quer
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
fonte
1
I pode argumentar que ele só queria mostrar uma solução .. não vejo por que o voto negativo .... @downvoter: pelo menos, pelo menos, adicionar informações úteis ..
Victor
1
@skaffman Todos gostamos de ver toda a gama de soluções alternativas. Este é o mais próximo do que estou procurando: Obter o nome do teste não diretamente na classe de teste, mas na classe que é usada durante o teste (por exemplo, em algum lugar de um componente de logger). Lá, as anotações relevantes para o teste não funcionam mais.
Daniel Alder
3

Com base no comentário anterior e considerando ainda mais, criei uma extensão do TestWather que você pode usar nos métodos de teste JUnit com isso:

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

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

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

A classe auxiliar de teste é a seguinte:

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

Aproveitar!

Csaba Tenkes
fonte
Oi, o que é isso ImportUtilsTest, recebo um erro, parece ser uma classe de logger, tenho mais informações? Obrigado
Sylhare
1
A classe nomeada é apenas um exemplo de uma classe de teste JUnit: o usuário do JUnitHelper. Vou corrigir o exemplo de uso.
perfil completo de Csaba Tenkes
Ah agora me sinto burro, era tão óbvio. Muito obrigado! ;)
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
fonte
0

Sugiro que você dissocie o nome do método de teste do seu conjunto de dados de teste. Eu modelaria uma classe DataLoaderFactory que carrega / armazena em cache os conjuntos de dados de teste de seus recursos e, em seu caso de teste, cam chama algum método de interface que retorna um conjunto de dados de teste para o caso de teste. Ter os dados de teste vinculados ao nome do método de teste pressupõe que os dados de teste possam ser usados ​​apenas uma vez, onde na maioria dos casos eu sugeriria que os mesmos dados de teste sejam usados ​​em vários testes para verificar vários aspectos da lógica de negócios.

emeraldjava
fonte
0

Você pode conseguir isso usando Slf4jeTestWatcher

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

No JUnit 5 TestInfoatua como um substituto para a regra TestName da JUnit 4.

A partir da documentação:

TestInfo é usado para injetar informações sobre o teste ou contêiner atual nos métodos @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll e @AfterAll.

Para recuperar o nome do método do teste executado atualmente, você tem duas opções: String TestInfo.getDisplayName()e Method TestInfo.getTestMethod().

Recuperar apenas o nome do método de teste atual TestInfo.getDisplayName()pode não ser suficiente, pois o nome de exibição padrão do método de teste é methodName(TypeArg1, TypeArg2, ... TypeArg3).
Duplicar nomes de métodos @DisplayName("..")não é uma boa idéia.

Como alternativa, você pode usar TestInfo.getTestMethod()isso retorna um Optional<Method>objeto.
Se o método de recuperação for usado dentro de um método de teste, você nem precisará testar o Optionalvalor agrupado.

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
fonte
0

JUnit 5 via ExtensionContext

Vantagem:

Você obtém as funcionalidades adicionais ExtensionContextsubstituindo 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();
    }

}
prata
fonte