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.


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

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


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

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


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 .


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.


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

  }
}

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.


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).


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

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!


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

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.


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

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

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

}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.