Affirmation des messages de journal avec JUnit

1. Introduction

Dans ce didacticiel, nous verrons comment nous pouvons couvrir les journaux générés dans les tests JUnit .

Nous utiliserons slf4j-api et l' implémentation logback et créerons un appender personnalisé que nous pouvons utiliser pour l'assertion de journal .

2. Dépendances de Maven

Avant de commencer, ajoutons la dépendance logback . Comme il implémente nativement le slf4j-api , il est automatiquement téléchargé et injecté dans le projet par la transitivité Maven:

 ch.qos.logback logback-classic. 1.2.3 

AssertJ offre des fonctions très utiles lors des tests, ajoutons donc également sa dépendance au projet:

 org.assertj assertj-core 3.15.0 test 

3. Une fonction commerciale de base

Maintenant, créons un objet qui générera des journaux sur lesquels nous pouvons baser nos tests.

Notre objet BusinessWorker n'exposera qu'une seule méthode. Cette méthode générera un journal avec le même contenu pour chaque niveau de journal. Bien que cette méthode ne soit pas si utile dans le monde réel, elle nous servira bien à nos fins de test:

public class BusinessWorker { private static Logger LOGGER = LoggerFactory.getLogger(BusinessWorker.class); public void generateLogs(String msg) { LOGGER.trace(msg); LOGGER.debug(msg); LOGGER.info(msg); LOGGER.warn(msg); LOGGER.error(msg); } }

4. Test des journaux

Nous voulons générer des journaux, alors créons un fichier logback.xml dans le dossier src / test / resources . Gardons les choses aussi simples que possible et redirigeons tous les journaux vers un appender CONSOLE :

     %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n       

4.1. MemoryAppender

Maintenant, créons un appender personnalisé qui garde les journaux en mémoire . Nous allons étendre la ListAppender que Logback offres , et nous l' enrichissons avec quelques méthodes utiles:

public class MemoryAppender extends ListAppender { public void reset() { this.list.clear(); } public boolean contains(String string, Level level) { return this.list.stream() .anyMatch(event -> event.getMessage().toString().contains(string) && event.getLevel().equals(level)); } public int countEventsForLogger(String loggerName) { return (int) this.list.stream() .filter(event -> event.getLoggerName().contains(loggerName)) .count(); } public List search(String string) { return this.list.stream() .filter(event -> event.getMessage().toString().contains(string)) .collect(Collectors.toList()); } public List search(String string, Level level) { return this.list.stream() .filter(event -> event.getMessage().toString().contains(string) && event.getLevel().equals(level)) .collect(Collectors.toList()); } public int getSize() { return this.list.size(); } public List getLoggedEvents() { return Collections.unmodifiableList(this.list); } }

La classe MemoryAppender gère une liste qui est automatiquement remplie par le système de journalisation.

Il expose une variété de méthodes afin de couvrir un large éventail d'objectifs de test:

  • reset () - efface la liste
  • contains (msg, level) - renvoie true uniquement si la liste contient un ILoggingEvent correspondant au contenu et au niveau de gravité spécifiés
  • countEventForLoggers (loggerName) - renvoie le nombre d' ILoggingEvent généré par le logger nommé
  • search (msg) - renvoie une liste d' ILoggingEvent correspondant au contenu spécifique
  • search (msg, level) - renvoie une liste d' ILoggingEvent correspondant au contenu et au niveau de gravité spécifiés
  • getSize () - renvoie le nombre de ILoggingEvent s
  • getLoggedEvents () - renvoie une vue non modifiable des éléments ILoggingEvent

4.2. Test de l'unité

Ensuite, créons un test JUnit pour notre collaborateur.

Nous déclarerons notre MemoryAppender en tant que champ et l'injecterons par programme dans le système de journalisation. Ensuite, nous allons démarrer l'appender.

Pour nos tests, nous définirons le niveau sur DEBUG :

@Before public void setup() { Logger logger = (Logger) LoggerFactory.getLogger(LOGGER_NAME); memoryAppender = new MemoryAppender(); memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory()); logger.setLevel(Level.DEBUG); logger.addAppender(memoryAppender); memoryAppender.start(); }

Nous pouvons maintenant créer un test simple où nous instancions notre classe BusinessWorker et appelons la méthode generateLogs . On peut alors faire des assertions sur les logs qu'il génère:

@Test public void test() { BusinessWorker worker = new BusinessWorker(); worker.generateLogs(MSG); assertThat(memoryAppender.countEventsForLogger(LOGGER_NAME)).isEqualTo(4); assertThat(memoryAppender.search(MSG, Level.INFO).size()).isEqualTo(1); assertThat(memoryAppender.contains(MSG, Level.TRACE)).isFalse(); }

Ce test utilise trois fonctionnalités de MemoryAppender :

  • Quatre journaux ont été générés - une entrée par gravité doit être présente, avec le niveau de trace filtré
  • Une seule entrée de journal avec le message de contenu avec le niveau de gravité INFO
  • No log entry is present with content message and severity TRACE

If we plan to use the same instance of this class inside the same test class when generating a lot of logs, the memory usage will creep up. We can invoke the MemoryAppender.clear() method before each test to free memory and avoid OutOfMemoryException.

In this example, we've reduced the scope of the retained logs to the LOGGER_NAME package, which we defined as “com.baeldung.junit.log“. We could potentially retain all logs with LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME), but we should avoid this whenever possible as it can consume a lot of memory.

5. Conclusion

Avec ce didacticiel, nous avons montré comment couvrir la génération de journaux dans nos tests unitaires .

Comme toujours, le code peut être trouvé sur GitHub.