Dans JUnit 3, je pourrais 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 testQuelque chose".
Existe-t-il un moyen simple ou original de faire cela dans JUnit 4?
Fond: Évidemment, je ne veux pas simplement imprimer le nom du test. Je souhaite charger des données spécifiques au test qui sont stockées dans une ressource portant le même nom que le test. Vous savez, convention sur la configuration et tout ça.
JUnit 4.7 a ajouté cette fonctionnalité, semble-t-il, avec 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());
}
}
Depuis JUnit 4.9, la classe TestWatchman
est obsolète au profit de la classe TestWatcher
qui a l'invocation:
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
System.out.println("Starting test: " + description.getMethodName());
}
};
L’approche suivante permet d’imprimer 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());
}
};
Dans JUnit 5, il existe une injection TestInfo
qui simplifie les métadonnées de test fournies aux 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 Guide de l'utilisateur , TestInfo javadoc .
Pensez à utiliser SLF4J (Façade de journalisation simple pour Java) fournit quelques améliorations intéressantes en utilisant des 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() {
}
}
Une méthode compliquée consiste à créer votre propre Runner en sous-classant org.junit.runners.BlockJUnit4ClassRunner.
Vous pouvez ensuite 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 sur une super-classe Test si vous ne voulez pas vous en rappeler à chaque fois. Ceci, bien sûr, limite votre sélection de coureurs mais cela peut être acceptable.
En outre, il faudra peut-être un peu de kung-fu pour obtenir le nom du test actuel hors du Runner et dans votre framework, mais cela vous donne au moins le nom.
Essayez ceci à la place:
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: CeciNE FONCTIONNE PASsi votre test est une sous-classe de TestCase ! Le test s'exécute mais le code @Rule ne s'exécute jamais.
Sur la base du commentaire précédent et en tenant compte de cela, 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'assistance de 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!
JUnit 4 ne dispose pas de mécanisme prêt à l'emploi permettant à un scénario de test d'obtenir son propre nom (y compris lors de la configuration et du démontage).
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) {
}
}
@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 );
}
};
Dans JUnit 5 TestInfo
se substitue à la règle TestName de JUnit 4.
De la documentation:
TestInfo est utilisé pour injecter des informations sur le test en cours ou conteneur dans @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll et @AfterAll méthodes.
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)
.
La duplication des noms de méthodes dans @DisplayName("..")
n’est pas une bonne idée.
Vous pouvez également utiliser TestInfo.getTestMethod()
qui retourne un objet Optional<Method>
.
Si la méthode d'extraction est utilisée dans une méthode de test, vous n'avez même pas besoin de tester la valeur encapsulée 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());
}
Vous pouvez y parvenir en utilisant Slf4j
et 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());
}
};
Je vous suggérerais de découpler 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 à partir de vos ressources, puis appelle dans votre cas de test une méthode d'interface qui renvoie un ensemble de données de test pour le cas de test. 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. 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.