Java 如何使用 junit 测试 log4j 是否记录了警告?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3717402/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I test with junit that a warning was logged with log4j?
提问by Asaf
I'm testing a method that logs warnings when something went wrong and returns null.
something like:
我正在测试一种方法,该方法在出现问题时记录警告并返回 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;
i'd like to test with junit that a warning was issued, in addition to returning null, in all cases (e.g. file not found, file is read-only).
any ideas?
thanks, asaf :-)
我想用 junit 测试发出警告,除了返回 null,在所有情况下(例如文件未找到,文件是只读的)。
有任何想法吗?
谢谢,阿萨夫:-)
UPDATE
更新
My implementation of Aaron's answer (plus peter's remark):
我对 Aaron 的回答的实现(加上彼得的评论):
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);
}
thanks guys,
谢谢你们,
采纳答案by Aaron Digulla
In the setup of the unit test:
在单元测试的设置中:
- Get the same logger
- Make it non-additive
Add an appender which remembers the messages in a list:
public class TestAppender extends AppenderSkeleton { public List<String> messages = new ArrayList<String>(); public void doAppend(LoggingEvent event) { messages.add( event.getMessage().toString() ); } }
Add the appender to the logger
- 获取相同的记录器
- 使其无添加
添加一个能够记住列表中消息的 appender:
public class TestAppender extends AppenderSkeleton { public List<String> messages = new ArrayList<String>(); public void doAppend(LoggingEvent event) { messages.add( event.getMessage().toString() ); } }
将 appender 添加到记录器
Now you can call your code. After the test, you will find all log messages in the list. Add the log level if you want (messages.add( event.getLevel() + " " + event.getMessage() );
).
现在您可以调用您的代码。测试后,您将在列表中找到所有日志消息。如果需要,请添加日志级别 ( messages.add( event.getLevel() + " " + event.getMessage() );
)。
In tearDown()
, remove the appender again and enable additivity.
在 中tearDown()
,再次删除 appender 并启用可加性。
回答by PaulJWilliams
Instead of calling log4j directly, use a protected method in your class.
不要直接调用 log4j,而是在您的类中使用受保护的方法。
Something like:
就像是:
protected void log(String message, Level level)
{
//delegates to log4j
}
Then create a subclass of the class under test that oevrrides this method, so that you can verify it is being called as expected.
然后创建一个被测类的子类来调用此方法,以便您可以验证它是否按预期调用。
class MyTest extends <class under test>
{
boolean somethingLogged = false;
protected void log(String message, Level level)
{
somethingLogged = true;
}
}
and then assert based on somethingLogged. You can add conditional logic in the overriding method t test based on expected message/level.
然后根据 somethingLogged 断言。您可以根据预期的消息/级别在覆盖方法 t test 中添加条件逻辑。
You could go further and record all the invocations, and then search through the logged messages, or check they were logged in the right order etc...
您可以进一步记录所有调用,然后搜索记录的消息,或检查它们是否以正确的顺序记录等等......
回答by Péter T?r?k
An alternative to Aaron's solution would be to configure a WriterAppenderwith an attached StringWriter. At the end of the test, you can verify the contents of the log output string.
Aaron 解决方案的替代方案是配置WriterAppender并附加一个StringWriter。在测试结束时,您可以验证日志输出字符串的内容。
This is a bit easier to implement (no need for custom code), however is less flexible with regards to checking the results, as you only get the output as plain text. In some cases that may make it more difficult to verify the output than with Aaron's solution.
这更容易实现(不需要自定义代码),但是在检查结果方面不太灵活,因为您只能以纯文本形式获得输出。在某些情况下,与 Aaron 的解决方案相比,这可能会使验证输出更加困难。
回答by Haim Raman
The examples in this post were very helpful, but I found them little confusing.
So I am adding a simplified version for the above with some minor changes.
这篇文章中的例子非常有帮助,但我发现它们有点令人困惑。
因此,我为上述内容添加了一个简化版本,并进行了一些小改动。
- I am adding my appender to the root logger.
- 我正在将我的 appender 添加到根记录器中。
This way, and assuming additively is true by default, I will not need to worry about losing my events due to logger hierarchy. Make sure this meet your log4j.properties file configuration.
这样,并假设默认情况下附加为 true,我将无需担心由于记录器层次结构而丢失我的事件。确保这符合您的 log4j.properties 文件配置。
- I am overriding append and not doAppend.
- 我正在覆盖 append 而不是 doAppend。
Append in AppenderSkeleton deals with level filtering, so I do not want to miss that.
doAppend will call append if the level is right.
Append in 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.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
回答by josle
Using Mockito you can test the logging that occurred during your test with minimal boiler plate code, a simple example is:
使用 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());
}
}
回答by ytoledano
I'm adapting Haim's answer to something that's more RAII:
我正在将 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);
}
}
And then the usage is simply:
然后用法很简单:
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"));
}