Obtenga el nombre de la prueba que se está ejecutando actualmente en JUnit 4

240

En JUnit 3, podría obtener el nombre de la prueba actualmente en ejecución de esta manera:

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

que imprimiría "La prueba actual es testSomething".

¿Hay alguna forma inmediata o simple de hacer esto en JUnit 4?

Antecedentes: Obviamente, no solo quiero imprimir el nombre de la prueba. Quiero cargar datos específicos de la prueba que se almacenan en un recurso con el mismo nombre que la prueba. Ya sabes, la convención sobre la configuración y todo eso.

Dave Ray
fuente
¿Qué le da el código anterior en JUnit 4?
Bill the Lizard
55
Las pruebas JUnit 3 extienden TestCase donde se define getName (). Las pruebas JUnit 4 no extienden una clase base, por lo que no hay ningún método getName ().
Dave Ray
Tengo un problema similar en el que quiero <b> establecer </b> el nombre de la prueba ya que estoy usando el corredor Parametrizado que solo me da casos de prueba numerados.
Volker Stolz
Una solución encantadora usando Test o TestWatcher ... solo preguntándome (en voz alta) si alguna vez debería ser necesario. Puede encontrar si una prueba se ejecuta lentamente mirando los gráficos de salida de tiempo dados por Gradle. Nunca debería necesitar saber el orden en que operan las pruebas ...?
Mike rodent

Respuestas:

378

JUnit 4.7 agregó esta característica que parece usar TestName-Rule . Parece que esto te dará el nombre del 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
fuente
44
También tenga en cuenta que TestName no está disponible en @before :( Ver: old.nabble.com/…
jm.
41
Aparentemente, las versiones más nuevas de JUnit se ejecutan @Ruleantes @Before: soy nuevo en JUnit y dependía de TestNamemi @Beforesin ninguna dificultad.
MightyE
2
Si está utilizando pruebas parametrizadas, "name.getMethodName ()" devolverá {testA [0], testA [1], etc.}, por lo tanto, uso algunos como: afirmar (nombre.getMethodName (). Coincide ("testA (\ [\ \ \ \re\])?"));
Legna
2
@DuncanJones ¿Por qué la alternativa propuesta es "más eficiente"?
Stephan
117

JUnit 4.9.xy superior

Desde JUnit 4.9, la TestWatchmanclase ha quedado en desuso a favor de la TestWatcherclase, que tiene invocación:

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

Nota: La clase que contiene debe declararse public.

JUnit 4.7.x - 4.8.x

El siguiente enfoque imprimirá los nombres de los métodos para todas las pruebas en una clase:

@Rule
public MethodRule watchman = new TestWatchman() {
   public void starting(FrameworkMethod method) {
      System.out.println("Starting test: " + method.getName());
   }
};
Duncan Jones
fuente
1
@takacsot Eso es sorprendente. ¿Pueden publicar una nueva pregunta sobre esto y enviarme el enlace aquí?
Duncan Jones
¿Por qué usar un publiccampo?
Raffi Khatchadourian
1
@RaffiKhatchadourian Vea stackoverflow.com/questions/14335558/…
Duncan Jones
16

JUnit 5 y superior

En JUnit 5 puede inyectar, lo TestInfoque simplifica los metadatos de prueba que proporcionan los métodos de prueba. Por ejemplo:

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

Ver más: JUnit 5 Guía del usuario , TestInfo javadoc .

Andrii Abramov
fuente
9

Intenta esto en su lugar:

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

El resultado se ve así:

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

NOTA: ¡Esto NO funciona si su prueba es una subclase de TestCase ! La prueba se ejecuta pero el código @Rule simplemente nunca se ejecuta.

Yavin5
fuente
3
Dios te bendiga por tu NOTA en el mismo ejemplo.
user655419
"Esto NO funciona" - caso en cuestión - pepino ignora las anotaciones de
@Rule
8

Considerar el uso de SLF4J (Fachada de registro simple para Java) proporciona algunas mejoras ordenadas usando mensajes parametrizados. La combinación de SLF4J con implementaciones de reglas JUnit 4 puede proporcionar técnicas de registro de clase de prueba más 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() {

  }
}
oficial
fuente
6

Una forma complicada es crear su propio Runner subclasificando org.junit.runners.BlockJUnit4ClassRunner.

Entonces puedes hacer algo como esto:

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

Luego, para cada clase de prueba, deberá agregar una anotación @RunWith (NameAwareRunner.class). Alternativamente, puede poner esa anotación en una superclase de prueba si no desea recordarla cada vez. Esto, por supuesto, limita su selección de corredores, pero eso puede ser aceptable.

Además, puede tomar un poco de kung fu para obtener el nombre de la prueba actual del Runner y en su marco, pero esto al menos le da el nombre.

chris.f.jones
fuente
Conceptualmente, al menos, esta idea me parece bastante sencilla. Mi punto es: no lo llamaría complicado.
user98761
"en una superclase de prueba ..." - Por favor, no más de los horribles patrones de diseño basados ​​en herencia. Esto es tan JUnit3!
Oberlies
3

JUnit 4 no tiene ningún mecanismo listo para usar para que un caso de prueba obtenga su propio nombre (incluso durante la configuración y el desmontaje).

cordellcp3
fuente
1
¿Existe un mecanismo fuera de la caja que no sea la inspección de la pila?
Dave Ray el
44
¡No es el caso dadas las respuestas a continuación! tal vez asignar la respuesta correcta a otra persona?
Toby
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
fuente
1
Puedo argumentar que solo quería mostrar una solución ... no veo por qué el voto negativo ... @downvoter: al menos, al menos, agregue información útil ...
Victor
1
@skaffman A todos nos encanta ver la gama completa de soluciones alternativas. Este es el más cercano para lo que estoy buscando: obtener el nombre de la prueba no directamente en la clase de prueba sino en la clase que se usa durante la prueba (por ejemplo, en algún lugar de un componente de registrador). Allí, las anotaciones relevantes para la prueba ya no funcionan.
Daniel Alder
3

Basado en el comentario anterior y considerando más, creé una extensión de TestWather que puedes usar en tus métodos de prueba JUnit con esto:

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

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

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

La clase auxiliar de prueba es la siguiente:

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

¡Disfrutar!

Csaba Tenkes
fuente
Hola, ¿qué es eso? ImportUtilsTestRecibo un error, parece ser una clase de registrador, ¿tengo más información? Gracias
Sylhare
1
La clase nombrada es solo un ejemplo de una clase de prueba JUnit: el usuario de JUnitHelper. Corregiré el ejemplo de uso.
Csaba Tenkes
Ah, ahora me siento tonto, era tan obvio. ¡Muchas gracias! ;)
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
fuente
0

Te sugiero que desacoples el nombre del método de prueba de tu conjunto de datos de prueba. Modelaría una clase DataLoaderFactory que carga / almacena en caché los conjuntos de datos de prueba de sus recursos, y luego en su caso de prueba puede llamar a algún método de interfaz que devuelve un conjunto de datos de prueba para el caso de prueba. Tener los datos de prueba vinculados al nombre del método de prueba supone que los datos de prueba solo se pueden usar una vez, donde en la mayoría de los casos sugeriría que los mismos datos de prueba se utilicen en múltiples pruebas para verificar varios aspectos de su lógica comercial.

esmeralda
fuente
0

Puede lograr esto usando Slf4jyTestWatcher

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

En JUnit 5 TestInfoactúa como un reemplazo directo para la regla TestName de JUnit 4.

De la documentación:

TestInfo se usa para inyectar información sobre la prueba o el contenedor actual en los métodos @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll y @AfterAll.

Para recuperar el nombre del método de la prueba ejecutada actual, tiene dos opciones: String TestInfo.getDisplayName()y Method TestInfo.getTestMethod().

Para recuperar solo el nombre del método de prueba actual TestInfo.getDisplayName()puede no ser suficiente ya que el nombre para mostrar predeterminado del método de prueba es methodName(TypeArg1, TypeArg2, ... TypeArg3).
Duplicar nombres de métodos @DisplayName("..")no es necesariamente una buena idea.

Como alternativa, podría usar TestInfo.getTestMethod()eso que devuelve un Optional<Method>objeto.
Si el método de recuperación se usa dentro de un método de prueba, ni siquiera necesita probar el Optionalvalor ajustado.

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

JUnit 5 a través de ExtensionContext

Ventaja:

Puede tener las funcionalidades adicionales de ExtensionContextanulando 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();
    }

}
plata
fuente