我有一些正在测试的代码,它调用Java记录器来报告其状态。
在JUnit测试代码中,我想验证在这个日志记录器中创建了正确的日志条目。大致如下:
methodUnderTest(bool x){
if(x)
logger.info("x happened")
}
@Test tester(){
// perhaps setup a logger first.
methodUnderTest(true);
assertXXXXXX(loggedLevel(),Level.INFO);
}
我认为这可以用一个经过特别调整的记录器(或处理程序或格式化程序)来完成,但我更愿意重用现有的解决方案。(而且,老实说,我不清楚如何从记录器获得logRecord,但假设这是可能的。)
非常感谢这些(令人惊讶的)快速而有用的回答;他们让我找到了正确的解决方法。
我想要使用的代码库使用java.util.logging作为其记录器机制,我对这些代码感到不够熟悉,无法完全将其更改为log4j或记录器接口/facade。但基于这些建议,我“破解”了一个j.u.l handler扩展,这是一种享受。
下面是一个简短的总结。延长java.util.logging.Handler:
class LogHandler extends Handler
{
Level lastLevel = Level.FINEST;
public Level checkLevel() {
return lastLevel;
}
public void publish(LogRecord record) {
lastLevel = record.getLevel();
}
public void close(){}
public void flush(){}
}
显然,您可以从LogRecord中存储您喜欢/想要/需要的任何数量,或者将它们全部推入堆栈,直到溢出。
在junit-test的准备过程中,你创建了一个java.util.logging.Logger,并添加这样一个新的LogHandler:
@Test tester() {
Logger logger = Logger.getLogger("my junit-test logger");
LogHandler handler = new LogHandler();
handler.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
logger.addHandler(handler);
logger.setLevel(Level.ALL);
对setUseParentHandlers()的调用将使正常的处理程序静默,以便(对于这个junit-test运行)不会发生不必要的日志记录。做任何你的测试代码需要使用这个记录器,运行测试和assertEquality:
libraryUnderTest.setLogger(logger);
methodUnderTest(true); // see original question.
assertEquals("Log level as expected?", Level.INFO, handler.checkLevel() );
}
(当然,您可以将大部分工作移到@Before方法中,并进行各种其他改进,但这会使演示变得混乱。)
通过添加Appender进行单元测试并不能真正测试Logger的配置。因此,我认为这是一种独特的情况,在这种情况下,单元测试没有带来那么多价值,而集成测试带来了很多价值(特别是如果您的日志有一些审计目的)。
为了为它创建集成测试,让我们假设您正在运行一个简单的ConsoleAppender,并希望测试它的输出。然后,您应该测试如何将消息从System.out写入到它自己的ByteArrayOutputStream。
从这个意义上说,我会做以下事情(我使用JUnit 5):
public class Slf4jAuditLoggerTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
@BeforeEach
public void beforeEach() {
System.setOut(new PrintStream(outContent));
}
通过这种方式,你可以简单地测试它的输出:
@Test
public void myTest() {
// Given...
// When...
// Then
assertTrue(outContent.toString().contains("[INFO] My formatted string from Logger"));
}
如果你这样做了,你将为你的项目带来更多的价值,而不需要使用内存中的实现,创建一个新的Appender,或者其他什么。
I've needed this several times as well. I've put together a small sample below, which you'd want to adjust to your needs. Basically, you create your own Appender and add it to the logger you want. If you'd want to collect everything, the root logger is a good place to start, but you can use a more specific if you'd like. Don't forget to remove the Appender when you're done, otherwise you might create a memory leak. Below I've done it within the test, but setUp or @Before and tearDown or @After might be better places, depending on your needs.
此外,下面的实现将所有内容收集到内存中的List中。如果您记录了很多日志,您可能会考虑添加一个过滤器来删除无聊的条目,或者将日志写入磁盘上的临时文件(提示:LoggingEvent是可序列化的,因此如果您的日志消息是可序列化的,那么您应该能够序列化事件对象)。
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class MyTest {
@Test
public void test() {
final TestAppender appender = new TestAppender();
final Logger logger = Logger.getRootLogger();
logger.addAppender(appender);
try {
Logger.getLogger(MyTest.class).info("Test");
}
finally {
logger.removeAppender(appender);
}
final List<LoggingEvent> log = appender.getLog();
final LoggingEvent firstLogEntry = log.get(0);
assertThat(firstLogEntry.getLevel(), is(Level.INFO));
assertThat((String) firstLogEntry.getMessage(), is("Test"));
assertThat(firstLogEntry.getLoggerName(), is("MyTest"));
}
}
class TestAppender extends AppenderSkeleton {
private final List<LoggingEvent> log = new ArrayList<LoggingEvent>();
@Override
public boolean requiresLayout() {
return false;
}
@Override
protected void append(final LoggingEvent loggingEvent) {
log.add(loggingEvent);
}
@Override
public void close() {
}
public List<LoggingEvent> getLog() {
return new ArrayList<LoggingEvent>(log);
}
}
正如前面提到的,您可以使用mock框架。为此,您必须在类中公开记录器(尽管我可能更倾向于使其包私有而不是创建公共setter)。
另一种解决方案是手工创建一个假记录器。您必须编写伪记录器(更多的fixture代码),但在这种情况下,我更喜欢使用模拟框架中保存的代码来增强测试的可读性。
我会这样做:
class FakeLogger implements ILogger {
public List<String> infos = new ArrayList<String>();
public List<String> errors = new ArrayList<String>();
public void info(String message) {
infos.add(message);
}
public void error(String message) {
errors.add(message);
}
}
class TestMyClass {
private MyClass myClass;
private FakeLogger logger;
@Before
public void setUp() throws Exception {
myClass = new MyClass();
logger = new FakeLogger();
myClass.logger = logger;
}
@Test
public void testMyMethod() {
myClass.myMethod(true);
assertEquals(1, logger.infos.size());
}
}