Asserire messaggi di registro con JUnit

1. Introduzione

In questo tutorial, vedremo come possiamo coprire i log generati nei test di JUnit .

Useremo slf4j-api e l' implementazione del logback e creeremo un appender personalizzato che possiamo usare per l'asserzione del log .

2. Dipendenze di Maven

Prima di iniziare, aggiungiamo la dipendenza logback . Poiché implementa in modo nativo slf4j-api , viene automaticamente scaricato e iniettato nel progetto dalla transitività di Maven:

 ch.qos.logback logback-classic. 1.2.3 

AssertJ offre funzioni molto utili durante il test, quindi aggiungiamo anche la sua dipendenza al progetto:

 org.assertj assertj-core 3.15.0 test 

3. Una funzione aziendale di base

Ora creiamo un oggetto che genererà log su cui basare i nostri test.

Il nostro oggetto BusinessWorker esporrà solo un metodo. Questo metodo genererà un registro con lo stesso contenuto per ogni livello di registro. Sebbene questo metodo non sia così utile nel mondo reale, sarà utile per i nostri scopi di 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. Verifica dei registri

Vogliamo generare i log, quindi creiamo un file logback.xml nella cartella src / test / resources . Manteniamolo il più semplice possibile e reindirizziamo tutti i registri a un appender CONSOLE :

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

4.1. MemoryAppender

Ora creiamo un appender personalizzato che mantiene i registri in memoria . Ci estendere il ListAppender che logback offerte , e ci arricchiamo con un paio di metodi utili:

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 gestisce un elenco che viene popolato automaticamente dal sistema di registrazione.

Espone una varietà di metodi per coprire una vasta gamma di scopi di test:

  • reset () : cancella l'elenco
  • contiene (msg, livello) : restituisce true solo se l'elenco contiene un ILoggingEvent corrispondente al contenuto e al livello di gravità specificati
  • countEventForLoggers (loggerName) - restituisce il numero di ILoggingEvent generati dal logger denominato
  • search (msg) - restituisce un elenco di ILoggingEvent corrispondente al contenuto specifico
  • search (msg, level) - restituisce un elenco di ILoggingEvent che corrisponde al contenuto e al livello di gravità specificati
  • getSize () - restituisce il numero di ILoggingEvent s
  • getLoggedEvents () - restituisce una vista non modificabile degli elementi ILoggingEvent

4.2. Test unitario

Successivamente, creiamo un test JUnit per il nostro lavoratore aziendale.

Dichiareremo il nostro MemoryAppender come campo e lo inseriremo programmaticamente nel sistema di log. Quindi, avvieremo l'appender.

Per i nostri test, imposteremo il livello su 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(); }

Ora possiamo creare un semplice test in cui istanziamo la nostra classe BusinessWorker e chiamiamo il metodo generateLogs . Possiamo quindi fare affermazioni sui log che genera:

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

Questo test utilizza tre funzionalità di MemoryAppender :

  • Sono stati generati quattro registri: dovrebbe essere presente una voce per gravità, con il livello di traccia filtrato
  • Solo una voce di registro con il messaggio di contenuto con il livello di gravità INFO
  • Nessuna voce di registro è presente con messaggio di contenuto e TRACE di gravità

Se intendiamo utilizzare la stessa istanza di questa classe all'interno della stessa classe di test durante la generazione di molti log, l'utilizzo della memoria aumenterà. Possiamo richiamare il metodo MemoryAppender.clear () prima di ogni test per liberare memoria ed evitare OutOfMemoryException .

In questo esempio, abbiamo ridotto l'ambito dei registri conservati al pacchetto LOGGER_NAME , che abbiamo definito " com.baeldung.junit.log ". Potremmo potenzialmente conservare tutti i log con LoggerFactory.getLogger (Logger.ROOT_LOGGER_NAME), ma dovremmo evitarlo quando possibile in quanto può consumare molta memoria .

5. conclusione

Con questo tutorial, abbiamo dimostrato come coprire la generazione di log nei nostri unit test .

Come sempre, il codice può essere trovato su GitHub.