我如何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的设置中:
- 获取相同的logging器
- 使其不具有添加性
-
添加一个logging列表中消息的appender:
public class TestAppender extends AppenderSkeleton { public List<String> messages = new ArrayList<String>(); public void doAppend(LoggingEvent event) { messages.add( event.getMessage().toString() ); } }
-
将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")); }