我有一些正在测试的代码,它调用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,但假设这是可能的。)
另一个值得提及的想法是创建一个CDI生成器来注入记录器,这样模拟就变得容易了,尽管这是一个较老的主题。(而且它还提供了不必再声明“整个logger语句”的优势,但这已经跑题了)
例子:
创建要注入的记录器:
public class CdiResources {
@Produces @LoggerType
public Logger createLogger(final InjectionPoint ip) {
return Logger.getLogger(ip.getMember().getDeclaringClass());
}
}
限定符:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface LoggerType {
}
在生产代码中使用记录器:
public class ProductionCode {
@Inject
@LoggerType
private Logger logger;
public void logSomething() {
logger.info("something");
}
}
在测试代码中测试记录器(给出一个easyMock示例):
@TestSubject
private ProductionCode productionCode = new ProductionCode();
@Mock
private Logger logger;
@Test
public void testTheLogger() {
logger.info("something");
replayAll();
productionCode.logSomething();
}
非常感谢这些(令人惊讶的)快速而有用的回答;他们让我找到了正确的解决方法。
我想要使用的代码库使用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方法中,并进行各种其他改进,但这会使演示变得混乱。)
这里有一个简单有效的Logback解决方案。
它不需要添加/创建任何新类。
它依赖于ListAppender:一个白盒回logback appender,其中将日志条目添加到公共List字段中,我们可以使用该字段来进行断言。
这里有一个简单的例子。
Foo类:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Foo {
static final Logger LOGGER = LoggerFactory.getLogger(Foo .class);
public void doThat() {
LOGGER.info("start");
//...
LOGGER.info("finish");
}
}
FooTest类:
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
public class FooTest {
@Test
void doThat() throws Exception {
// get Logback Logger
Logger fooLogger = (Logger) LoggerFactory.getLogger(Foo.class);
// create and start a ListAppender
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
listAppender.start();
// add the appender to the logger
// addAppender is outdated now
fooLogger.addAppender(listAppender);
// call method under test
Foo foo = new Foo();
foo.doThat();
// JUnit assertions
List<ILoggingEvent> logsList = listAppender.list;
assertEquals("start", logsList.get(0)
.getMessage());
assertEquals(Level.INFO, logsList.get(0)
.getLevel());
assertEquals("finish", logsList.get(1)
.getMessage());
assertEquals(Level.INFO, logsList.get(1)
.getLevel());
}
}
JUnit断言听起来不太适合断言列表元素的某些特定属性。
像AssertJ或Hamcrest这样的Matcher/断言库似乎更好:
使用AssertJ,它将是:
import org.assertj.core.api.Assertions;
Assertions.assertThat(listAppender.list)
.extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel)
.containsExactly(Tuple.tuple("start", Level.INFO), Tuple.tuple("finish", Level.INFO));