我有一些正在测试的代码,它调用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();
}
对于我来说,您可以通过使用JUnit和Mockito来简化您的测试。
我提出以下解决方案:
import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.Mockito.times;
@RunWith(MockitoJUnitRunner.class)
public class MyLogTest {
private static final String FIRST_MESSAGE = "First message";
private static final String SECOND_MESSAGE = "Second message";
@Mock private Appender appender;
@Captor private ArgumentCaptor<LoggingEvent> captor;
@InjectMocks private MyLog;
@Before
public void setUp() {
LogManager.getRootLogger().addAppender(appender);
}
@After
public void tearDown() {
LogManager.getRootLogger().removeAppender(appender);
}
@Test
public void shouldLogExactlyTwoMessages() {
testedClass.foo();
then(appender).should(times(2)).doAppend(captor.capture());
List<LoggingEvent> loggingEvents = captor.getAllValues();
assertThat(loggingEvents).extracting("level", "renderedMessage").containsExactly(
tuple(Level.INFO, FIRST_MESSAGE)
tuple(Level.INFO, SECOND_MESSAGE)
);
}
}
这就是为什么我们对不同消息量的测试有很好的灵活性
我为log4j回答了一个类似的问题,请参阅how-can-i-test-with-junit-that-a-warning-was-logged-with-log4
这是更新的Log4j2(用2.11.2测试)和junit 5的示例;
package com.whatever.log;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.*;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
class TestLogger {
private TestAppender testAppender;
private LoggerConfig loggerConfig;
private final Logger logger = (Logger)
LogManager.getLogger(ClassUnderTest.class);
@Test
@DisplayName("Test Log Junit5 and log4j2")
void test() {
ClassUnderTest.logMessage();
final LogEvent loggingEvent = testAppender.events.get(0);
//asset equals 1 because log level is info, change it to debug and
//the test will fail
assertTrue(testAppender.events.size()==1,"Unexpected empty log");
assertEquals(Level.INFO,loggingEvent.getLevel(),"Unexpected log level");
assertEquals(loggingEvent.getMessage().toString()
,"Hello Test","Unexpected log message");
}
@BeforeEach
private void setup() {
testAppender = new TestAppender("TestAppender", null);
final LoggerContext context = logger.getContext();
final Configuration configuration = context.getConfiguration();
loggerConfig = configuration.getLoggerConfig(logger.getName());
loggerConfig.setLevel(Level.INFO);
loggerConfig.addAppender(testAppender,Level.INFO,null);
testAppender.start();
context.updateLoggers();
}
@AfterEach
void after(){
testAppender.stop();
loggerConfig.removeAppender("TestAppender");
final LoggerContext context = logger.getContext();
context.updateLoggers();
}
@Plugin( name = "TestAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
static class TestAppender extends AbstractAppender {
List<LogEvent> events = new ArrayList();
protected TestAppender(String name, Filter filter) {
super(name, filter, null);
}
@PluginFactory
public static TestAppender createAppender(
@PluginAttribute("name") String name,
@PluginElement("Filter") Filter filter) {
return new TestAppender(name, filter);
}
@Override
public void append(LogEvent event) {
events.add(event);
}
}
static class ClassUnderTest {
private static final Logger LOGGER = (Logger) LogManager.getLogger(ClassUnderTest.class);
public static void logMessage(){
LOGGER.info("Hello Test");
LOGGER.debug("Hello Test");
}
}
}
使用以下maven依赖项
<dependency>
<artifactId>log4j-core</artifactId>
<packaging>jar</packaging>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>