我如何testing与junit log4jlogging了一个警告?

我正在testing一个方法,当出现错误时会logging警告并返回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; 

我想testing与junit发出警告,除了返回null,在所有情况下(例如文件未find,文件是只读的)。
有任何想法吗?
谢谢,阿萨夫:-)


UPDATE

我实施亚伦的回答(加上彼得的评论):

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

多谢你们,

在unit testing的设置中:

  1. 获取相同的logging器
  2. 使其不具有添加性
  3. 添加一个logging列表中消息的appender:

     public class TestAppender extends AppenderSkeleton { public List<String> messages = new ArrayList<String>(); public void doAppend(LoggingEvent event) { messages.add( event.getMessage().toString() ); } } 
  4. 将appender添加到logging器

现在你可以调用你的代码。 testing之后,您会在列表中find所有日志消息。 如果需要,添加日志级别( messages.add( event.getLevel() + " " + event.getMessage() ); )。

tearDown() ,再次移除appender并启用additivity。

使用Mockito,您可以使用最小的锅炉板代码来testing在testing期间发生的日志logging,一个简单的例子是:

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

Aaron的解决scheme的替代scheme是configuration一个WriterAppender与一个附加的StringWriter 。 在testing结束时,您可以validation日志输出string的内容。

这实现起来更容易一些(不需要定制代码),但是在检查结果方面灵活性较差,因为您只能以纯文本格式输出。 在某些情况下,比Aaron的解决scheme更难以validation输出。

在这篇文章中的例子是非常有帮助的,但我发现他们有点混乱。
所以我在上面加了一个简单的版本,做了一些小的修改。

  • 我将我的appender添加到根logging器。

这样,并假设默认情况下是真实的,我不需要担心由于logging器层次结构而丢失我的事件。 确保这符合你的log4j.properties文件configuration。

  • 我是压倒一切的追求,而不是附加。

追加AppenderSkeleton处理级别过滤,所以我不想错过。
如果级别正确,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 

不要直接调用log4j,而要在你的类中使用受保护的方法。

就像是:

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

然后创build一个被testing的类的子类,它包含这个方法,这样就可以validation它是否按照预期被调用。

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

然后根据somethingLogged断言。 您可以根据预期的消息/级别在重写方法ttesting中添加条件逻辑。

你可以进一步logging所有的调用,然后searchlogging的消息,或者检查他们是否以正确的顺序login等等。

我正在将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")); }