Obtenir le nom du test en cours d'exécution dans JUnit 4

240

Dans JUnit 3, je pouvais obtenir le nom du test en cours d'exécution comme ceci:

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

qui afficherait "Le test actuel est testSomething".

Existe-t-il un moyen prêt à l'emploi ou simple de le faire dans JUnit 4?

Contexte: Évidemment, je ne veux pas simplement imprimer le nom du test. Je souhaite charger des données spécifiques au test stockées dans une ressource portant le même nom que le test. Vous savez, convention sur la configuration et tout ça.

Dave Ray
la source
Que vous donne le code ci-dessus dans JUnit 4?
Bill the Lizard
5
Les tests JUnit 3 étendent TestCase où getName () est défini. Les tests JUnit 4 n'étendent pas une classe de base, il n'y a donc pas du tout de méthode getName ().
Dave Ray
J'ai un problème similaire où je veux <b> définir </b> le nom du test car j'utilise le programme d'exécution paramétré qui ne me donne que des cas de test numérotés.
Volker Stolz
Belle solution en utilisant Test ou TestWatcher ... vous vous demandez simplement (à haute voix) s'il devrait jamais y avoir un besoin pour cela? Vous pouvez déterminer si un test s'exécute lentement en consultant les tableaux de sortie de synchronisation fournis par Gradle. Vous ne devriez jamais avoir besoin de connaître l'ordre dans lequel les tests fonctionnent ...?
mike rongeur du

Réponses:

378

JUnit 4.7 a ajouté cette fonctionnalité qui semble utiliser TestName-Rule . On dirait que cela vous donnera le nom de la méthode:

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
la source
4
Notez également que TestName n'est pas disponible dans @before :( Voir: old.nabble.com/…
jm.
41
Apparemment, des versions plus récentes de JUnit sont exécutées @Ruleauparavant @Before- je suis nouveau sur JUnit et TestNameje dépendais de moi @Beforesans aucune difficulté.
MightyE
9
Il existe des moyens plus efficaces de le faire .
Duncan Jones
2
Si vous utilisez des tests paramétrés "name.getMethodName ()" renverra {testA [0], testA [1], etc} donc j'utilise certains comme: assertTrue (name.getMethodName (). Matches ("testA (\ [\ \ré\])?"));
Legna
2
@DuncanJones Pourquoi l'alternative proposée est "plus efficace"?
Stephan
117

JUnit 4.9.x et supérieur

Depuis JUnit 4.9, la TestWatchmanclasse est déconseillée au profit de la TestWatcherclasse, qui a l'invocation:

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

Remarque: La classe contenant doit être déclarée public.

JUnit 4.7.x - 4.8.x

L'approche suivante imprime les noms de méthode pour tous les tests d'une classe:

@Rule
public MethodRule watchman = new TestWatchman() {
   public void starting(FrameworkMethod method) {
      System.out.println("Starting test: " + method.getName());
   }
};
Duncan Jones
la source
1
@takacsot C'est surprenant. Pouvez-vous s'il vous plaît poster une nouvelle question à ce sujet et me cingler le lien ici?
Duncan Jones
Pourquoi utiliser un publicchamp?
Raffi Khatchadourian
1
@RaffiKhatchadourian Voir stackoverflow.com/questions/14335558/…
Duncan Jones
16

JUnit 5 et supérieur

Dans JUnit 5, vous pouvez injecter TestInfoce qui simplifie le test des métadonnées fournissant des méthodes de test. Par exemple:

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

Voir plus: JUnit 5 User guide , TestInfo javadoc .

Andrii Abramov
la source
9

Essayez plutôt ceci:

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

La sortie ressemble à ceci:

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

REMARQUE: cela ne fonctionne pas si votre test est une sous-classe de TestCase ! Le test s'exécute mais le code @Rule ne s'exécute jamais.

Yavin5
la source
3
Que Dieu vous bénisse pour votre NOTE tout au long de l'exemple.
user655419
"Cela NE FONCTIONNE PAS" - cas d'espèce - le concombre ignore les annotations
@Rule
8

Pensez à utiliser SLF4J (Simple Logging Facade for Java) qui offre des améliorations intéressantes à l'aide de messages paramétrés. La combinaison de SLF4J avec des implémentations de règles JUnit 4 peut fournir des techniques de journalisation de classe de test plus efficaces.

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

  }
}
compagnon
la source
6

Une façon compliquée consiste à créer votre propre Runner en sous-classant org.junit.runners.BlockJUnit4ClassRunner.

Vous pouvez alors faire quelque chose comme ceci:

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

Ensuite, pour chaque classe de test, vous devrez ajouter une annotation @RunWith (NameAwareRunner.class). Alternativement, vous pouvez mettre cette annotation dans une superclasse de test si vous ne voulez pas vous en souvenir à chaque fois. Bien entendu, cela limite votre sélection de coureurs, mais cela peut être acceptable.

En outre, cela peut prendre un peu de kung-fu pour obtenir le nom du test actuel du Runner et dans votre framework, mais cela vous donne au moins le nom.

chris.f.jones
la source
Conceptuellement au moins, cette idée me semble assez simple. Mon point étant: je n'appellerais pas cela compliqué.
user98761
"sur une superclasse de test ..." - S'il vous plaît, plus des horribles modèles de conception basés sur l'héritage. C'est tellement JUnit3!
oberlies du
3

JUnit 4 n'a pas de mécanisme prêt à l'emploi pour un cas de test pour obtenir son propre nom (y compris lors de l'installation et du démontage).

cordellcp3
la source
1
Existe-t-il un mécanisme non prêt à l'emploi autre que l'inspection de la pile?
Dave Ray
4
Pas le cas étant donné les réponses ci-dessous! peut-être attribuer la bonne réponse à quelqu'un d'autre?
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
la source
1
Je peux affirmer qu'il voulait seulement montrer une solution .. je ne vois pas pourquoi le vote négatif .... @downvoter: au moins, au moins, ajouter des informations utiles ..
Victor
1
@skaffman Nous aimons tous voir la gamme complète de solutions alternatives. C'est le plus proche de ce que je recherche: obtenir le nom du test non pas directement dans la classe de test mais dans la classe qui est utilisée pendant le test (par exemple quelque part dans un composant logger). Là, les annotations relatives aux tests ne fonctionnent plus.
Daniel Alder
3

Sur la base du commentaire précédent et considérant que j'ai créé une extension de TestWather que vous pouvez utiliser dans vos méthodes de test JUnit avec ceci:

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

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

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

La classe d'aide au test est la suivante:

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

Prendre plaisir!

Csaba Tenkes
la source
Salut qu'est-ce que c'est ImportUtilsTest, je reçois une erreur, il semble que ce soit une classe de logger, ai-je plus d'informations? Merci
Sylhare
1
La classe nommée n'est qu'un exemple d'une classe de test JUnit: l'utilisateur de JUnitHelper. Je vais corriger l'exemple d'utilisation.
Csaba Tenkes
Ah maintenant je me sens stupide, c'était tellement évident. Merci beaucoup! ;)
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
la source
0

Je vous suggère de dissocier le nom de la méthode de test de votre ensemble de données de test. Je modéliserais une classe DataLoaderFactory qui charge / met en cache les ensembles de données de test de vos ressources, puis dans votre scénario de test, appelez une méthode d'interface qui renvoie un ensemble de données de test pour le scénario de test. Le fait d'associer les données de test au nom de la méthode de test suppose que les données de test ne peuvent être utilisées qu'une seule fois, alors que dans la plupart des cas, je suggère que les mêmes données de test soient utilisées dans plusieurs tests pour vérifier divers aspects de votre logique métier.

emeraldjava
la source
0

Vous pouvez y parvenir en utilisant Slf4jetTestWatcher

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());
    }
};
Codeur
la source
0

Dans JUnit 5 TestInfo agit comme un remplacement de remplacement pour la règle TestName de JUnit 4.

De la documentation:

TestInfo est utilisé pour injecter des informations sur le test ou le conteneur en cours dans les méthodes @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll et @AfterAll.

Pour récupérer le nom de la méthode du test exécuté en cours, vous avez deux options: String TestInfo.getDisplayName()et Method TestInfo.getTestMethod() .

Pour récupérer uniquement le nom de la méthode de test actuelle TestInfo.getDisplayName()peut ne pas être suffisant car le nom d'affichage par défaut de la méthode de test est methodName(TypeArg1, TypeArg2, ... TypeArg3).
Duplication des noms de méthode dans@DisplayName("..") n'est pas nécessairement une bonne idée.

Comme alternative, vous pouvez utiliser TestInfo.getTestMethod()qui retourne un Optional<Method>objet.
Si la méthode de récupération est utilisée dans une méthode de test, vous n'avez même pas besoin de tester la Optionalvaleur encapsulée.

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
la source
0

JUnit 5 via ExtensionContext

Avantage:

Vous obtenez les fonctionnalités supplémentaires de ExtensionContexten remplaçant 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();
    }

}
argent
la source