web-dev-qa-db-fra.com

Comment puis-je tester avec junit qu'un avertissement a été enregistré avec log4j?

Je teste une méthode qui enregistre les avertissements en cas de problème et renvoie null.
quelque chose comme:

private static final Logger log = Logger.getLogger(Clazz.class.getName());
....
if (file == null || !file.exists()) {
  // if File not found
  log.warn("File not found: "+file.toString());
} else if (!file.canWrite()) {
  // if file is read only
  log.warn("File is read-only: "+file.toString());
} else {
  // all checks passed, can return an working file.
  return file;
}
return null;

je voudrais tester avec junit qu'un avertissement a été émis, en plus de retourner null, dans tous les cas (par exemple, fichier non trouvé, fichier en lecture seule).
des idées?
merci, asaf :-)


MISE À JOUR

Ma mise en œuvre de la réponse d'Aaron (plus la remarque de Peter):

public class UnitTest {
...

@BeforeClass
public static void setUpOnce() {
  appenders = new Vector<Appender>(2);
  // 1. just a printout appender:
  appenders.add(new ConsoleAppender(new PatternLayout("%d [%t] %-5p %c - %m%n")));
  // 2. the appender to test against:
  writer = new StringWriter();
  appenders.add(new WriterAppender(new PatternLayout("%p, %m%n"),writer));
}

@Before
public void setUp() {
  // Unit Under Test:
  unit = new TestUnit();
  // setting test appenders:
  for (Appender appender : appenders) {
    TestUnit.log.addAppender(appender);
  }
  // saving additivity and turning it off:
  additivity = TestUnit.log.getAdditivity();
  TestUnit.log.setAdditivity(false);
}

@After
public void tearDown() {
  unit = null;
  for (Appender appender : appenders) {
    TestUnit.log.removeAppender(appender);
  }
  TestUnit.log.setAdditivity(additivity);
}

@Test
public void testGetFile() {
  // start fresh:
  File file;
  writer.getBuffer().setLength(0);

  // 1. test null file:
  System.out.println(" 1. test null file.");
  file = unit.getFile(null);
  assertNull(file);
  assertTrue(writer.toString(), writer.toString().startsWith("WARN, File not found"));
  writer.getBuffer().setLength(0);

  // 2. test empty file:
  System.out.println(" 2. test empty file.");
  file = unit.getFile("");
  assertNull(file);
  assertTrue(writer.toString(), writer.toString().startsWith("WARN, File not found"));
  writer.getBuffer().setLength(0);
}

merci les gars,

47
Asaf

Dans la configuration du test unitaire:

  1. Obtenez le même enregistreur
  2. Rendez-le non additif
  3. Ajoutez un appender qui mémorise les messages dans une liste:

    public class TestAppender extends AppenderSkeleton {
        public List<String> messages = new ArrayList<String>();
    
        public void doAppend(LoggingEvent event) {
            messages.add( event.getMessage().toString() );
        }
    }
    
  4. Ajouter l'appendice à l'enregistreur

Vous pouvez maintenant appeler votre code. Après le test, vous trouverez tous les messages de journal dans la liste. Ajoutez le niveau de journalisation si vous le souhaitez (messages.add( event.getLevel() + " " + event.getMessage() );).

Dans tearDown(), supprimez à nouveau l'appendice et activez l'additivité.

38
Aaron Digulla

En utilisant Mockito, vous pouvez tester la journalisation qui s'est produite pendant votre test avec un code de plaque de chaudière minimal, un exemple simple est:

@RunWith(MockitoJUnitRunner.class)
public class TestLogging {

  @Mock AppenderSkeleton appender;
  @Captor ArgumentCaptor<LoggingEvent> logCaptor;


  @Test
  public void test() {
    Logger.getRootLogger().addAppender(appender);

    ...<your test code here>...

    verify(appender).doAppend(logCaptor.capture());
    assertEquals("Warning message should have been logged", "Caution!", logCaptor.getValue().getRenderedMessage());
  }
}
15
josle

Les exemples de ce post ont été très utiles, mais je les ai trouvés peu déroutants.
J'ajoute donc une version simplifiée pour ce qui précède avec quelques changements mineurs.

  • J'ajoute mon appender à l'enregistreur racine.

De cette façon, et en supposant que l'addition est vraie par défaut, je n'aurai pas à me soucier de perdre mes événements en raison de la hiérarchie des enregistreurs. Assurez-vous que cela correspond à la configuration de votre fichier log4j.properties.

  • J'écrase append et non doAppend.

Ajouter dans AppenderSkeleton traite du filtrage de niveau, donc je ne veux pas manquer ça.
doAppend appellera append si le niveau est correct.

public class TestLogger {
    @Test
    public void test() {
        TestAppender testAppender = new TestAppender();

        Logger.getRootLogger().addAppender(testAppender);
        ClassUnderTest.logMessage();
        LoggingEvent loggingEvent = testAppender.events.get(0);
        //asset equals 1 because log level is info, change it to debug and 
        //the test will fail
        assertTrue("Unexpected empty log",testAppender.events.size()==1);               
        assertEquals("Unexpected log level",Level.INFO,loggingEvent.getLevel());
        assertEquals("Unexpected log message"
                        ,loggingEvent.getMessage().toString()
                        ,"Hello Test");
    }

    public static class TestAppender extends AppenderSkeleton{
        public List<LoggingEvent> events = new ArrayList<LoggingEvent>();
        public void close() {}
        public boolean requiresLayout() {return false;}
        @Override
        protected void append(LoggingEvent event) {
                      events.add(event);
          }     
    }

    public static class ClassUnderTest {
        private static final Logger LOGGER = 
            Logger.getLogger(ClassUnderTest.class);
        public static void logMessage(){
          LOGGER.info("Hello Test");
          LOGGER.debug("Hello Test");
        }
    }
}

log4j.properties

log4j.rootCategory=INFO, CONSOLE
log4j.appender.CONSOLE=org.Apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.Apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d %p  [%c] - %m%n
# un-comment this will fail the test   
#log4j.logger.com.haim.logging=DEBUG
8
Haim Raman

Une alternative à la solution d'Aaron serait de configurer un WriterAppender avec un StringWriter attaché. À la fin du test, vous pouvez vérifier le contenu de la chaîne de sortie du journal.

Ceci est un peu plus facile à implémenter (pas besoin de code personnalisé), mais est moins flexible en ce qui concerne la vérification des résultats, car vous obtenez uniquement la sortie en texte brut. Dans certains cas, cela peut rendre la vérification de la sortie plus difficile qu'avec la solution d'Aaron.

7
Péter Török

Au lieu d'appeler directement log4j, utilisez une méthode protégée dans votre classe.

Quelque chose comme:

protected void log(String message, Level level)
{
    //delegates to log4j
}

Créez ensuite une sous-classe de la classe testée qui contourne cette méthode, afin que vous puissiez vérifier qu'elle est appelée comme prévu.

class MyTest extends <class under test>
{
    boolean somethingLogged = false;
    protected void log(String message, Level level)
    {
        somethingLogged = true;
    }
}

puis affirmer en fonction de quelque chose de journalisé. Vous pouvez ajouter une logique conditionnelle dans le test de la méthode prioritaire en fonction du message/niveau attendu.

Vous pouvez aller plus loin et enregistrer toutes les invocations, puis rechercher dans les messages enregistrés, ou vérifier qu'ils ont été enregistrés dans le bon ordre, etc.

2
PaulJWilliams

J'adapte la réponse de Haim à quelque chose de plus RAII:

public static class TestAppender extends AppenderSkeleton {
    @Override
    protected void append(LoggingEvent event) {
        messages.add(event.getRenderedMessage());
    }

    @Override
    public void close() { }

    @Override
    public boolean requiresLayout() { return false; }

    protected final List<String> messages = new ArrayList<>();
}

static class LogGuard implements AutoCloseable {
    protected final TestAppender appender;

    LogGuard(Level level) {
        appender = new TestAppender();
        appender.setThreshold(level);
        Logger.getRootLogger().addAppender(appender);
    }

    @Override
    public void close() throws Exception {
        Logger.getRootLogger().removeAppender(appender);
    }
}

Et puis l'utilisation est tout simplement:

try (LogGuard log = new LogGuard(Level.WARN)) { // if you want WARN or higher
    // do what causes the logging
   Assert.assertTrue(log.appender.messages.stream().anyMatch(m -> m.equals("expected"));
}
1
ytoledano