web-dev-qa-db-ja.com

log4jで警告が記録されたことをjunitでテストするにはどうすればよいですか?

何か問題が発生したときに警告をログに記録し、nullを返すメソッドをテストしています。
何かのようなもの:

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;

すべての場合(たとえば、ファイルが見つからない、ファイルが読み取り専用であるなど)にnullを返すことに加えて、警告が発行されたことをjunitでテストしたいと思います。
何か案は?
ありがとう、asaf :-)


更新

アーロンの答えの私の実装(さらにピーターの発言):

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

みんなありがとう、

47
Asaf

単体テストのセットアップ:

  1. 同じロガーを取得する
  2. 非加算的にする
  3. リスト内のメッセージを記憶するアペンダーを追加します。

    _public class TestAppender extends AppenderSkeleton {
        public List<String> messages = new ArrayList<String>();
    
        public void doAppend(LoggingEvent event) {
            messages.add( event.getMessage().toString() );
        }
    }
    _
  4. ロガーにアペンダーを追加します

これで、コードを呼び出すことができます。テスト後、リストにすべてのログメッセージが表示されます。必要に応じてログレベルを追加します(messages.add( event.getLevel() + " " + event.getMessage() );)。

tearDown()で、アペンダーを再度削除し、加算性を有効にします。

38
Aaron Digulla

Mockitoを使用すると、最小限のボイラープレートコードでテスト中に発生したログをテストできます。簡単な例を次に示します。

@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

この投稿の例は非常に役立ちましたが、少し混乱していることがわかりました。
そのため、上記の簡易バージョンにいくつかの小さな変更を加えて追加しています。

  • ルートロガーにアペンダーを追加しています。

この方法で、デフォルトで相加的であると仮定すると、ロガー階層によるイベントの損失を心配する必要はありません。これがlog4j.propertiesファイルの構成に合っていることを確認してください。

  • DoAppendではなくappendをオーバーライドしています。

AppenderSkeletonのAppendは、レベルフィルタリングを処理するので、見逃したくありません。
doAppendは、レベルが正しい場合にappendを呼び出します。

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

Aaronのソリューションの代替案は、 WriterAppenderStringWriter を付加して構成することです。テストの終了時に、ログ出力文字列の内容を確認できます。

これは実装が少し簡単ですが(カスタムコードは不要)、出力をプレーンテキストとしてのみ取得するため、結果のチェックに関して柔軟性が低くなります。場合によっては、アーロンのソリューションよりも出力の検証が難しくなる場合があります。

7
Péter Török

Log4jを直接呼び出す代わりに、クラスで保護されたメソッドを使用します。

何かのようなもの:

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

次に、このメソッドをoevrrideするテスト対象クラスのサブクラスを作成して、期待どおりに呼び出されていることを確認できるようにします。

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

そして、somethingLoggedに基づいてアサートします。予想されるメッセージ/レベルに基づいて、オーバーライドメソッドtテストに条件ロジックを追加できます。

さらに進んですべての呼び出しを記録し、ログに記録されたメッセージを検索したり、正しい順序でログに記録されたことを確認したりできます...

2
PaulJWilliams

私はHaimの答えをもっと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);
    }
}

そして、使い方は単純です:

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