source

로거 메시지에서 JUnit 아사트를 실행하는 방법

bestscript 2023. 1. 26. 09:15

로거 메시지에서 JUnit 아사트를 실행하는 방법

Java 로거를 호출하여 상태를 보고하는 테스트 대상 코드가 있습니다.JUnit 테스트 코드에서 이 로거에 올바른 로그 엔트리가 작성되었는지 확인하고 싶습니다.다음과 같은 것들이 있습니다.

methodUnderTest(bool x){
    if(x)
        logger.info("x happened")
}

@Test tester(){
    // perhaps setup a logger first.
    methodUnderTest(true);
    assertXXXXXX(loggedLevel(),Level.INFO);
}

이것은 특별히 개조된 로거(또는 핸들러 또는 포메터)로 할 수 있다고 생각합니다만, 기존의 솔루션을 재사용하고 싶습니다.(솔직히 말하면 로거에서 log Record에 접속하는 방법은 명확하지 않지만, 그것이 가능하다고 가정합니다.)

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★아래 작은 샘플을 준비했는데, 고객님의 요구에 맞게 조정해 주시기 바랍니다.만의 것을 요.Appender원하는 로거에 추가합니다.모든 것을 수집하려면 루트 로거를 시작하는 것이 좋습니다. 그러나 원하는 경우 더 구체적인 항목을 사용할 수 있습니다.'Appender' 'Appender' 'Appender' 'Appender' 'Appender' 'Appender' 。 그렇지 않으면 메모리 누수가 발생할 수 있습니다.는 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★」setUp ★★★★★★★★★★★★★★★★★」@Before ★★★★★★★★★★★★★★★★★」tearDown ★★★★★★★★★★★★★★★★★」@After니즈에 따라서는 더 좋은 곳이 될 수도 있어

을 ,, 하, a, 든, 든 등으로 합니다.List기억 속에.로그를 많이 기록할 경우 지루한 엔트리를 삭제하거나 디스크의 임시 파일에 로그를 쓰는 필터를 추가하는 것을 고려할 수 있습니다(힌트:LoggingEventSerializable따라서 로그 메시지가 있는 경우 이벤트개체만 시리얼화할 수 있습니다).

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);
    }
}

다음은 심플하고 효율적인 로그백 솔루션입니다.
새 클래스를 추가/생성할 필요가 없습니다.
로그 엔트리가 추가되는 화이트박스 로그백어펜더에 의존합니다.public 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 어설션은 리스트 요소의 특정 속성을 어설션하는 데 적합하지 않은 것 같습니다.
AsertJ의 Hamcrest의 Matcher/Assertion의 Matcher/Assertion의 Matcher/Assertion의 Matcher/Assertion에 합니다.

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));

이러한 (놀랍게도) 신속하고 도움이 되는 답변을 주셔서 감사합니다.솔루션의 올바른 길로 인도해 줍니다.

코드 베이스는 이것을 사용하고 싶었지만, java.util.logging을 로거 메커니즘으로 사용하고 있었습니다.이 코드에서는 log4j 또는 logger 인터페이스/faces로 완전히 변경할 수 있을 만큼 편안하지 않습니다.하지만 이런 제안들을 바탕으로 J.U.L. 확장자를 '해킹'했고 그게 치료 효과가 있었어요

하다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(){}
}

하, 하, 하, from, from, from, , from, from, from, from, from, from, from, from, from, from, from, the, the, 만큼 할 수 있습니다.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여러 가지 개선책을 강구할 수 있지만, 그렇게 되면 이 프레젠테이션이 복잡해집니다.)

Junit 5(Jupiter) 스프링 출력 캡처용확장은 매우 유용합니다.Spring Boot 2.2 이후 사용 가능하며 Spring-boot-test 아티팩트에서 사용할 수 있습니다.

예(javadoc에서 인용):

@ExtendWith(OutputCaptureExtension.class)
class MyTest {
    @Test
    void test(CapturedOutput output) {
        System.out.println("ok");
        assertThat(output).contains("ok");
        System.err.println("error");
    }

    @AfterEach
    void after(CapturedOutput output) {
        assertThat(output.getOut()).contains("ok");
        assertThat(output.getErr()).contains("error");
    }
}

사실상 종속 클래스의 부작용을 검정하고 있습니다.유닛 테스트의 경우 다음 사항만 확인하면 됩니다.

logger.info()

올바른 파라미터로 호출되었습니다.따라서 모킹 프레임워크를 사용하여 로거를 에뮬레이트하면 클래스 동작을 테스트할 수 있습니다.

또 다른 옵션은 Appender를 조롱하고 메시지가 이 Appender에 기록되었는지 확인하는 것입니다.Log4j 1.2.x 및 mockito의 예:

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

public class MyTest {

    private final Appender appender = mock(Appender.class);
    private final Logger logger = Logger.getRootLogger();

    @Before
    public void setup() {
        logger.addAppender(appender);
    }

    @Test
    public void test() {
        // when
        Logger.getLogger(MyTest.class).info("Test");

        // then
        ArgumentCaptor<LoggingEvent> argument = ArgumentCaptor.forClass(LoggingEvent.class);
        verify(appender).doAppend(argument.capture());
        assertEquals(Level.INFO, argument.getValue().getLevel());
        assertEquals("Test", argument.getValue().getMessage());
        assertEquals("MyTest", argument.getValue().getLoggerName());
    }

    @After
    public void cleanup() {
        logger.removeAppender(appender);
    }
}

저도 같은 도전을 만나서 이 페이지를 보게 되었습니다.질문에 답하기엔 11년이나 늦었지만, 다른 사람들에게도 도움이 될 수 있을 것 같았다.이 나왔어요.davidxxxLogback 및 ListAppander가 매우 유용하게 사용됩니다.여러 프로젝트에서 동일한 구성을 사용했지만, 무언가를 변경해야 할 때 복사/붙여넣고 모든 버전을 유지하는 것은 그다지 즐겁지 않았습니다.나는 그것을 가지고 도서관을 만들어서 지역사회에 다시 기여하는 것이 더 낫다고 생각했다.SLFJ4, Log4j, Log4j2, Java Utilit Logging And 및 Lombok 주석과 연동됩니다.자세한 예시와 프로젝트에 추가하는 방법에 대해서는 여기를 참조하십시오.LogCaptor 를 참조해 주세요.

예:

public class FooService {

    private static final Logger LOGGER = LoggerFactory.getLogger(FooService.class);

    public void sayHello() {
        LOGGER.warn("Congratulations, you are pregnant!");
    }

}

LogCaptor를 사용한 장치 테스트 예:

import nl.altindag.log.LogCaptor;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class FooServiceTest {

    @Test
    public void sayHelloShouldLogWarnMessage() {
        LogCaptor logCaptor = LogCaptor.forClass(FooService.class);

        FooService fooService = new FooService();
        fooService.sayHello();

        assertThat(logCaptor.getWarnLogs())
            .contains("Congratulations, you are pregnant!");
    }
}

「나의 라이브러리」를 홍보하기 위한 방법이라고 생각되기 때문에, 여기에 투고하는 것이 좋을지 잘 모르겠습니다만, 같은 과제를 안고 있는 개발자에게도 도움이 될 수 있다고 생각했습니다.

여기서 모킹은 어렵지만 로거는 일반적으로 프라이빗 스태틱 파이널이기 때문에 모킹 로거를 설정하는 것은 식은 죽 먹기나 테스트 대상 클래스의 수정이 필요합니다.

커스텀 Appender(또는 어떤 이름으로 불리든)를 생성하여 테스트 전용 구성 파일 또는 런타임(로깅 프레임워크에 따라 다름)을 통해 등록할 수 있습니다.그런 다음 해당 Appender(설정 파일에 선언된 경우 정적 또는 런타임에 연결 중인 경우 현재 참조)를 가져와 내용을 확인할 수 있습니다.

@RonaldBlaschke의 솔루션에서 영감을 얻어 다음과 같은 아이디어를 얻었습니다.

public class Log4JTester extends ExternalResource {
    TestAppender appender;

    @Override
    protected void before() {
        appender = new TestAppender();
        final Logger rootLogger = Logger.getRootLogger();
        rootLogger.addAppender(appender);
    }

    @Override
    protected void after() {
        final Logger rootLogger = Logger.getRootLogger();
        rootLogger.removeAppender(appender);
    }

    public void assertLogged(Matcher<String> matcher) {
        for(LoggingEvent event : appender.events) {
            if(matcher.matches(event.getMessage())) {
                return;
            }
        }
        fail("No event matches " + matcher);
    }

    private static class TestAppender extends AppenderSkeleton {

        List<LoggingEvent> events = new ArrayList<LoggingEvent>();

        @Override
        protected void append(LoggingEvent event) {
            events.add(event);
        }

        @Override
        public void close() {

        }

        @Override
        public boolean requiresLayout() {
            return false;
        }
    }

}

...이것에 의해, 다음과 같은 일이 가능하게 됩니다.

@Rule public Log4JTester logTest = new Log4JTester();

@Test
public void testFoo() {
     user.setStatus(Status.PREMIUM);
     logTest.assertLogged(
        stringContains("Note added to account: premium customer"));
}

햄크레스트를 좀 더 스마트하게 사용할 수 있을 것 같은데, 이쯤에서 그만뒀어요.

log4j2의 경우 AppenderSkeleton을 사용할 수 없기 때문에 솔루션이 약간 다릅니다.또한 Mockito 또는 이와 유사한 라이브러리를 사용하여 ArgumentCaptor를 사용하여 Appender를 만드는 것은 MutableLogEvent가 여러 로그 메시지로 재사용되기 때문에 여러 로깅 메시지를 예상하는 경우 작동하지 않습니다.log4j2에서 찾은 최적의 솔루션은 다음과 같습니다.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.AbstractAppender;

private static MockedAppender mockedAppender;
private static Logger logger;

@Before
public void setup() {
    mockedAppender.message.clear();
}

/**
 * For some reason mvn test will not work if this is @Before, but in eclipse it works! As a
 * result, we use @BeforeClass.
 */
@BeforeClass
public static void setupClass() {
    mockedAppender = new MockedAppender();
    logger = (Logger)LogManager.getLogger(MatchingMetricsLogger.class);
    logger.addAppender(mockedAppender);
    logger.setLevel(Level.INFO);
}

@AfterClass
public static void teardown() {
    logger.removeAppender(mockedAppender);
}

@Test
public void test() {
    // do something that causes logs
    for (String e : mockedAppender.message) {
        // add asserts for the log messages
    }
}

private static class MockedAppender extends AbstractAppender {

    List<String> message = new ArrayList<>();

    protected MockedAppender() {
        super("MockedAppender", null, null);
    }

    @Override
    public void append(LogEvent event) {
        message.add(event.getMessage().getFormattedMessage());
    }
}

와, 왜 이렇게 힘들었는지 모르겠어요.slf4j에서 log4j2를 사용하고 있었기 때문에 위의 코드 샘플을 사용할 수 없었습니다.제 솔루션은 다음과 같습니다.

public class SpecialLogServiceTest {

  @Mock
  private Appender appender;

  @Captor
  private ArgumentCaptor<LogEvent> captor;

  @InjectMocks
  private SpecialLogService specialLogService;

  private LoggerConfig loggerConfig;

  @Before
  public void setUp() {
    // prepare the appender so Log4j likes it
    when(appender.getName()).thenReturn("MockAppender");
    when(appender.isStarted()).thenReturn(true);
    when(appender.isStopped()).thenReturn(false);

    final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
    final Configuration config = ctx.getConfiguration();
    loggerConfig = config.getLoggerConfig("org.example.SpecialLogService");
    loggerConfig.addAppender(appender, AuditLogCRUDService.LEVEL_AUDIT, null);
  }

  @After
  public void tearDown() {
    loggerConfig.removeAppender("MockAppender");
  }

  @Test
  public void writeLog_shouldCreateCorrectLogMessage() throws Exception {
    SpecialLog specialLog = new SpecialLogBuilder().build();
    String expectedLog = "this is my log message";

    specialLogService.writeLog(specialLog);

    verify(appender).append(captor.capture());
    assertThat(captor.getAllValues().size(), is(1));
    assertThat(captor.getAllValues().get(0).getMessage().toString(), is(expectedLog));
  }
}

다른 것들로부터 언급되었듯이, 당신은 조롱의 틀을 사용할 수 있습니다.이를 위해서는 클래스 내의 로거를 공개해야 합니다(퍼블릭세터를 작성하는 대신 패키지를 비공개로 하는 것이 좋습니다).

또 다른 해결책은 손으로 가짜 로거를 만드는 것입니다.페이크 로거(더 많은 고정 코드)를 작성해야 하는데, 이 경우 모의 프레임워크에서 저장된 코드에 대한 테스트의 가독성을 강화했으면 합니다.

저는 이렇게 하고 싶어요.

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());
    }
}

로그백을 위해 한 일은 다음과 같습니다.

TestAppender 클래스를 만들었습니다.

public class TestAppender extends AppenderBase<ILoggingEvent> {

    private Stack<ILoggingEvent> events = new Stack<ILoggingEvent>();

    @Override
    protected void append(ILoggingEvent event) {
        events.add(event);
    }

    public void clear() {
        events.clear();
    }

    public ILoggingEvent getLastEvent() {
        return events.pop();
    }
}

그런 다음 testng 유닛 테스트 클래스의 부모에서 메서드를 만들었습니다.

protected TestAppender testAppender;

@BeforeClass
public void setupLogsForTesting() {
    Logger root = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
    testAppender = (TestAppender)root.getAppender("TEST");
    if (testAppender != null) {
        testAppender.clear();
    }
}

src/test/resources에 logback-test.xml 파일이 정의되어 있으며 테스트어펜더를 추가했습니다.

<appender name="TEST" class="com.intuit.icn.TestAppender">
    <encoder>
        <pattern>%m%n</pattern>
    </encoder>
</appender>

root appender에 다음 appender를 추가했습니다.

<root>
    <level value="error" />
    <appender-ref ref="STDOUT" />
    <appender-ref ref="TEST" />
</root>

부모 테스트 클래스에서 확장된 테스트 클래스에서는 마지막 메시지를 기록하고 메시지, 수준 및 폐기 가능 여부를 확인할 수 있습니다.

ILoggingEvent lastEvent = testAppender.getLastEvent();
assertEquals(lastEvent.getMessage(), "...");
assertEquals(lastEvent.getLevel(), Level.WARN);
assertEquals(lastEvent.getThrowableProxy().getMessage(), "...");
Here is the sample code to mock log, irrespective of the version used for junit or sping, springboot.

import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.Appender;
import org.mockito.ArgumentMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.Test;

import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class MyTest {
  private static Logger logger = LoggerFactory.getLogger(MyTest.class);

    @Test
    public void testSomething() {
    ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
    final Appender mockAppender = mock(Appender.class);
    when(mockAppender.getName()).thenReturn("MOCK");
    root.addAppender(mockAppender);

    //... do whatever you need to trigger the log

    verify(mockAppender).doAppend(argThat(new ArgumentMatcher() {
      @Override
      public boolean matches(final Object argument) {
        return ((LoggingEvent)argument).getFormattedMessage().contains("Hey this is the message I want to see");
      }
    }));
  }
}

Log4J 2.x 에서는 퍼블릭인터페이스에는,setAppender() ★★★★★★★★★★★★★★★★★」removeAppender()★★★★★★★★★★★★★★★★★★.

그러나 너무 화려한 것을 하고 있지 않다면, 그것을 구현 클래스에 캐스팅할 수 있어야 합니다.그렇게 하면, 그러한 방법이 확실히 드러납니다.

다음은 Mockito 및 AssertJ입니다.

// Import the implementation class rather than the API interface
import org.apache.logging.log4j.core.Logger;
// Cast logger to implementation class to get access to setAppender/removeAppender
Logger log = (Logger) LogManager.getLogger(MyClassUnderTest.class);

// Set up the mock appender, stubbing some methods Log4J needs internally
Appender appender = mock(Appender.class);
when(appender.getName()).thenReturn("Mock Appender");
when(appender.isStarted()).thenReturn(true);

log.addAppender(appender);
try {
    new MyClassUnderTest().doSomethingThatShouldLogAnError();
} finally {
    log.removeAppender(appender);
}

// Verify that we got an error with the expected message
ArgumentCaptor<LogEvent> logEventCaptor = ArgumentCaptor.forClass(LogEvent.class);
verify(appender).append(logEventCaptor.capture());
LogEvent logEvent = logEventCaptor.getValue();
assertThat(logEvent.getLevel()).isEqualTo(Level.ERROR);
assertThat(logEvent.getMessage().getFormattedMessage()).contains(expectedErrorMessage);

에는 ''을 '간단하게'로 하면 .JUnitMockito이를 위해 다음과 같은 솔루션을 제안합니다.

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)
        );
    }
}

그렇기 때문에 메시지 이 다른 테스트에 대해 뛰어난 유연성을 가지고 있습니다.

클래스 구현에서 하드 코딩된 정적 글로벌 로거를 사용할 필요가 없습니다. 기본 생성자에 기본 로거를 제공한 다음 특정 생성자를 사용하여 제공된 로거에 대한 참조를 설정할 수 있습니다.

class MyClassToTest {
    private final Logger logger;
    
    public MyClassToTest() {
      this(SomeStatic.logger);
    };
    
    MyClassToTest(Logger logger) {
      this.logger = logger;
    };
    
    public void someOperation() {
        logger.warn("warning message");
        // ...
    };
};

class MyClassToTestTest {
    
    @Test
    public warnCalled() {
        Logger loggerMock = mock(Logger.class);
        MyClassTest myClassToTest = new MyClassToTest(logger);
        myClassToTest.someOperation();
        verify(loggerMock).warn(anyString());
    };
}

이 라이브러리를 체크합니다.https://github.com/Hakky54/log-captor

maven 파일에 라이브러리에 대한 참조를 포함합니다.

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>logcaptor</artifactId>
    <version>2.5.0</version>
    <scope>test</scope>
</dependency>

Java 코드 테스트 방법에서는 다음을 포함해야 합니다.

LogCaptor logCaptor = LogCaptor.forClass(MyClass.class);

 // do the test logic....

assertThat(logCaptor.getLogs()).contains("Some log to assert");

하시는 java.util.logging.Logger이 문서는 매우 도움이 될 수 있습니다.이 문서는 새로운 핸들러를 생성하여 로그에 어설션을 합니다.출력: http://octodecillion.com/blog/jmockit-test-logging/

Log4J2의 API는 약간 다릅니다.또한 비동기 Appender를 사용하고 있을 수도 있습니다.이에 대한 잠금 첨부 파일을 만들었습니다.

    public static class LatchedAppender extends AbstractAppender implements AutoCloseable {

    private final List<LogEvent> messages = new ArrayList<>();
    private final CountDownLatch latch;
    private final LoggerConfig loggerConfig;

    public LatchedAppender(Class<?> classThatLogs, int expectedMessages) {
        this(classThatLogs, null, null, expectedMessages);
    }
    public LatchedAppender(Class<?> classThatLogs, Filter filter, Layout<? extends Serializable> layout, int expectedMessages) {
        super(classThatLogs.getName()+"."+"LatchedAppender", filter, layout);
        latch = new CountDownLatch(expectedMessages);
        final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
        final Configuration config = ctx.getConfiguration();
        loggerConfig = config.getLoggerConfig(LogManager.getLogger(classThatLogs).getName());
        loggerConfig.addAppender(this, Level.ALL, ThresholdFilter.createFilter(Level.ALL, null, null));
        start();
    }

    @Override
    public void append(LogEvent event) {
        messages.add(event);
        latch.countDown();
    }

    public List<LogEvent> awaitMessages() throws InterruptedException {
        assertTrue(latch.await(10, TimeUnit.SECONDS));
        return messages;
    }

    @Override
    public void close() {
        stop();
        loggerConfig.removeAppender(this.getName());
    }
}

다음과 같이 사용합니다.

        try (LatchedAppender appender = new LatchedAppender(ClassUnderTest.class, 1)) {

        ClassUnderTest.methodThatLogs();
        List<LogEvent> events = appender.awaitMessages();
        assertEquals(1, events.size());
        //more assertions here

    }//appender removed

가장 쉬운 방법

  @ExtendWith(OutputCaptureExtension.class)
  class MyTestClass { 
    
          @Test
          void my_test_method(CapturedOutput output) {
               assertThat(output).contains("my test log.");
          }
  }

언급할 가치가 있는 또 다른 아이디어는 비록 오래된 주제이긴 하지만 CDI 프로듀서를 만들어 로거를 삽입하는 것입니다.그것은 조롱이 쉬워집니다.(또한 "전체 로거 진술"을 더 이상 선언할 필요가 없다는 장점도 있지만, 주제에서 벗어납니다.)

예:

주입할 로거 만들기:

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();
}

Jmockit (1.21)을 사용하여 이 간단한 테스트를 작성할 수 있었습니다.이 테스트에서는 특정 오류 메시지가 한 번만 호출되는지 확인합니다.

@Test
public void testErrorMessage() {
    final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger( MyConfig.class );

    new Expectations(logger) {{
        //make sure this error is happens just once.
        logger.error( "Something went wrong..." );
        times = 1;
    }};

    new MyTestObject().runSomethingWrong( "aaa" ); //SUT that eventually cause the error in the log.    
}

Appender를 조롱하면 로그 행을 캡처하는 데 도움이 됩니다.샘플 검색: http://clearqa.blogspot.co.uk/2016/12/test-log-lines.html

// Fully working test at: https://github.com/njaiswal/logLineTester/blob/master/src/test/java/com/nj/Utils/UtilsTest.java

@Test
public void testUtilsLog() throws InterruptedException {

    Logger utilsLogger = (Logger) LoggerFactory.getLogger("com.nj.utils");

    final Appender mockAppender = mock(Appender.class);
    when(mockAppender.getName()).thenReturn("MOCK");
    utilsLogger.addAppender(mockAppender);

    final List<String> capturedLogs = Collections.synchronizedList(new ArrayList<>());
    final CountDownLatch latch = new CountDownLatch(3);

    //Capture logs
    doAnswer((invocation) -> {
        LoggingEvent loggingEvent = invocation.getArgumentAt(0, LoggingEvent.class);
        capturedLogs.add(loggingEvent.getFormattedMessage());
        latch.countDown();
        return null;
    }).when(mockAppender).doAppend(any());

    //Call method which will do logging to be tested
    Application.main(null);

    //Wait 5 seconds for latch to be true. That means 3 log lines were logged
    assertThat(latch.await(5L, TimeUnit.SECONDS), is(true));

    //Now assert the captured logs
    assertThat(capturedLogs, hasItem(containsString("One")));
    assertThat(capturedLogs, hasItem(containsString("Two")));
    assertThat(capturedLogs, hasItem(containsString("Three")));
}

다음 코드를 사용합니다.로그백을 로그백으로 사용하는 스프링 통합 테스트에서도 같은 코드를 사용하고 있습니다.로그에 인쇄된 텍스트를 아사트하려면 assertJobIsScheduled 메서드를 사용합니다.

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.Appender;

private Logger rootLogger;
final Appender mockAppender = mock(Appender.class);

@Before
public void setUp() throws Exception {
    initMocks(this);
    when(mockAppender.getName()).thenReturn("MOCK");
    rootLogger = (Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
    rootLogger.addAppender(mockAppender);
}

private void assertJobIsScheduled(final String matcherText) {
    verify(mockAppender).doAppend(argThat(new ArgumentMatcher() {
        @Override
        public boolean matches(final Object argument) {
            return ((LoggingEvent)argument).getFormattedMessage().contains(matcherText);
        }
    }));
}

테스트하려고 하는 것은 두 가지가 있습니다.

  • 프로그램 오퍼레이터가 관심 있는 이벤트가 있을 때 프로그램이 해당 이벤트를 오퍼레이터에게 알릴 수 있는 적절한 로깅 작업을 수행합니까?
  • 프로그램이 로깅 작업을 수행할 때 생성되는 로그 메시지에 올바른 텍스트가 포함되어 있는지 확인합니다.

그 두 가지는 사실 다른 것이기 때문에 개별적으로 테스트할 수 있습니다.단, 두 번째(메시지 텍스트) 테스트는 매우 문제가 있기 때문에 전혀 하지 않는 것이 좋습니다.메시지 텍스트 테스트는 최종적으로 1개의 텍스트 문자열(예상 메시지텍스트)이 로깅코드에서 사용되는 텍스트 문자열과 같거나 그 문자열에서 파생될 수 있는지 확인하는 것으로 구성됩니다.

  • 이러한 테스트에서는 프로그램 로직은 전혀 테스트되지 않습니다.한 리소스(문자열)가 다른 리소스와 동등함을 테스트합니다.
  • 테스트는 취약합니다.로그 메시지 형식을 조금만 수정해도 테스트가 실패합니다.
  • 이 테스트는 로깅인터페이스의 국제화(변환)와 호환되지 않습니다.이 테스트에서는 메시지 텍스트는 1개뿐이며, 따라서 인간의 언어도 1개뿐이라고 가정합니다.

프로그램 코드(일부 비즈니스 로직 구현)를 텍스트 로깅 인터페이스에 직접 호출하는 것은 좋지 않은 설계입니다(그러나 불행히도 매우 단순합니다).비즈니스 로직을 담당하는 코드는 로그 정책 및 로그 메시지의 텍스트도 결정합니다.비즈니스 로직과 사용자 인터페이스 코드를 혼합합니다(예, 로그 메시지는 프로그램 사용자 인터페이스의 일부입니다).그것들은 분리되어야 한다.

따라서 비즈니스 로직이 로그 메시지의 텍스트를 직접 생성하지 않는 것이 좋습니다.대신 로깅 개체에 위임합니다.

  • 로깅 개체의 클래스는 텍스트 문자열이 아닌 도메인 모델의 개체를 사용하여 발생한 이벤트를 비즈니스 개체가 표현하는 데 사용할 수 있는 적절한 내부 API를 제공해야 합니다.
  • 로깅 클래스의 구현은 이러한 도메인 객체의 텍스트 표현을 생성하고 이벤트에 대한 적절한 텍스트 설명을 렌더링한 다음 해당 텍스트 메시지를 낮은 수준의 로깅 프레임워크(예: JUL, log4j 또는 slf4j)로 전송합니다.
  • 비즈니스 로직은 로거 클래스의 내부 API의 올바른 메서드를 호출하여 올바른 도메인 개체를 전달하고 발생한 실제 이벤트를 설명하는 역할만 수행합니다.
  • 인 클래스 " " " 」implements한 사람interface비즈니스 로직에서 사용할 수 있는 내부 API를 설명합니다.
  • 비즈니스 로직을 구현하고 로깅을 수행해야 하는 클래스에 위임할 로깅 개체에 대한 참조가 있습니다.는 추상적인 입니다.interface.
  • 종속성 주입을 사용하여 로거에 대한 참조를 설정합니다.

그런 다음 내부 로깅 API를 구현하는 모의 로거를 만들고 테스트 설정 단계에서 종속성 주입을 사용하여 비즈니스 로직 클래스가 이벤트에 대해 로깅인터페이스에 올바르게 통지하는 테스트를 수행할 수 있습니다.

다음과 같이 합니다.

 public class MyService {// The class we want to test
    private final MyLogger logger;

    public MyService(MyLogger logger) {
       this.logger = Objects.requireNonNull(logger);
    }

    public void performTwiddleOperation(Foo foo) {// The method we want to test
       ...// The business logic
       logger.performedTwiddleOperation(foo);
    }
 };

 public interface MyLogger {
    public void performedTwiddleOperation(Foo foo);
    ...
 };

 public final class MySl4jLogger: implements MyLogger {
    ...

    @Override
    public void performedTwiddleOperation(Foo foo) {
       logger.info("twiddled foo " + foo.getId());
    }
 }

 public final void MyProgram {
    public static void main(String[] argv) {
       ...
       MyLogger logger = new MySl4jLogger(...);
       MyService service = new MyService(logger);
       startService(service);// or whatever you must do
       ...
    }
 }

 public class MyServiceTest {
    ...

    static final class MyMockLogger: implements MyLogger {
       private Food.id id;
       private int nCallsPerformedTwiddleOperation;
       ...

       @Override
       public void performedTwiddleOperation(Foo foo) {
          id = foo.id;
          ++nCallsPerformedTwiddleOperation;
       }

       void assertCalledPerformedTwiddleOperation(Foo.id id) {
          assertEquals("Called performedTwiddleOperation", 1, nCallsPerformedTwiddleOperation);
          assertEquals("Called performedTwiddleOperation with correct ID", id, this.id);
       }
    };

    @Test
    public void testPerformTwiddleOperation_1() {
       // Setup
       MyMockLogger logger = new MyMockLogger();
       MyService service = new MyService(logger);
       Foo.Id id = new Foo.Id(...);
       Foo foo = new Foo(id, 1);

       // Execute
       service.performedTwiddleOperation(foo);

       // Verify
       ...
       logger.assertCalledPerformedTwiddleOperation(id);
    }
 }

(너무 취약한 정확한 log 문을 확인하는 것이 아니라) 일부 문자열이 로깅된 것을 확인하는 것만으로 StdOut을 버퍼로 리다이렉트하고 contains를 실행한 후 StdOut을 리셋합니다.

PrintStream original = System.out;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
System.setOut(new PrintStream(buffer));

// Do something that logs

assertTrue(buffer.toString().contains(myMessage));
System.setOut(original);

log4j에 대해서도 같은 질문에 답했습니다.how-can-i-test-with-junit-that-a-was-logged4를 참조하십시오.

이것은 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");
        }
    }
}

다음 종속성 사용

 <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>

제 경우 다음과 같은 문제를 해결했습니다.

Logger root = (Logger) LoggerFactory.getLogger(CSVTasklet.class); //CSVTasklet is my target class
    final Appender mockAppender = mock(Appender.class);
    root.addAppender(mockAppender); 

verify(mockAppender).doAppend(argThat((ArgumentMatcher) argument -> ((LoggingEvent) argument).getMessage().contains("No projects."))); // I checked "No projects." in the log

Appender를 추가하여 유닛을 테스트해도 Logger의 설정은 실제로 테스트되지 않습니다.따라서 유닛 테스트에서는 그다지 큰 가치를 얻을 수 없지만 통합 테스트에서는 많은 가치를 얻을 수 있는 독특한 케이스 중 하나라고 생각합니다(특히 로그에 감사 목적이 있는 경우).

하기 위해 통합 테스트를 가정해 .ConsoleAppender그리고 출력을 테스트하고 싶습니다.출력을 테스트하고 싶습니다.그럼, 음 is own메다어 its작테합 the to다니지 then, how야해 should message트는 test되스 you게성 written떻가지시그런 is then, .ByteArrayOutputStream부에서System.out.

그런 의미에서 다음 작업을 수행합니다(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 작성 등의 작업을 할 필요가 없습니다.

언급URL : https://stackoverflow.com/questions/1827677/how-to-do-a-junit-assert-on-a-message-in-a-logger