programing

PowerMock 및 Mockito로 Logger 및 LoggerFactory 모의

sourcetip 2021. 1. 14. 23:44
반응형

PowerMock 및 Mockito로 Logger 및 LoggerFactory 모의


나는 다음과 같은 Logger를 모의하고 싶지만 로그 항목을 확인하기 위해 콘텐츠가 아닌 호출됩니다.

private static Logger logger = 
        LoggerFactory.getLogger(GoodbyeController.class);

LoggerFactory.getLogger ()에 사용되는 모든 클래스를 모의하고 싶지만 그 방법을 찾을 수 없습니다. 이것이 내가 지금까지 끝낸 것입니다.

@Before
public void performBeforeEachTest() {
    PowerMockito.mockStatic(LoggerFactory.class);
    when(LoggerFactory.getLogger(GoodbyeController.class)).
        thenReturn(loggerMock);

    when(loggerMock.isDebugEnabled()).thenReturn(true);
    doNothing().when(loggerMock).error(any(String.class));

    ...
}

나는 알고 싶습니다:

  1. LoggerFactory.getLogger()모든 클래스에서 작동 하도록 정적 모의 할 수 있습니까 ?
  2. 나는 단지에서 실행 when(loggerMock.isDebugEnabled()).thenReturn(true);하는 @Before것처럼 보일 수 있으므로 방법별로 특성을 변경할 수 없습니다. 이 문제를 해결할 방법이 있습니까?

결과 수정 :

나는 이미 이것을 시도했지만 작동하지 않는다고 생각했습니다.

 when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock);

그러나 그것이 작동했던 것처럼 감사합니다.

그러나 나는 다음과 같은 수많은 변형을 시도했습니다.

when(loggerMock.isDebugEnabled()).thenReturn(true);

loggerMock @Before외부에서 동작을 변경할 수는 없지만 Coburtura 에서만 발생합니다. Clover를 사용하면 적용 범위가 100 %로 표시되지만 어느 쪽이든 여전히 문제가 있습니다.

이 간단한 수업이 있습니다.

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

    public String getMessage() {        
    if(logger.isDebugEnabled()){
        logger.debug("isDebugEnabled");
        logger.debug("isDebugEnabled");
    }
    return "Hello world!";
    }
    ...
}

그런 다음이 테스트가 있습니다.

@RunWith(PowerMockRunner.class)
@PrepareForTest({LoggerFactory.class})
public class ExampleServiceTests {

    @Mock
    private Logger loggerMock;
    private ExampleServiceservice = new ExampleService();

    @Before
    public void performBeforeEachTest() {
        PowerMockito.mockStatic(LoggerFactory.class);
        when(LoggerFactory.getLogger(any(Class.class))).
            thenReturn(loggerMock);

        //PowerMockito.verifyStatic(); // fails
    }

    @Test
    public void testIsDebugEnabled_True() throws Exception {
        when(loggerMock.isDebugEnabled()).thenReturn(true);
        doNothing().when(loggerMock).debug(any(String.class));

        assertThat(service.getMessage(), is("Hello null: 0"));
        //verify(loggerMock, atLeast(1)).isDebugEnabled(); // fails
    }

    @Test
    public void testIsDebugEnabled_False() throws Exception {
        when(loggerMock.isDebugEnabled()).thenReturn(false);
        doNothing().when(loggerMock).debug(any(String.class));

        assertThat(service.getMessage(), is("Hello null: 0"));
        //verify(loggerMock, atLeast(1)).isDebugEnabled(); // fails
    }
}

클로버에서는 if(logger.isDebugEnabled()){블록 의 100 % 커버리지를 보여줍니다 . 하지만 다음을 확인하려고하면 loggerMock:

verify(loggerMock, atLeast(1)).isDebugEnabled();

상호 작용이 없습니다. 나는 또한 시도했다 PowerMockito.verifyStatic(); @Before있지만 또한 제로 상호 작용을 가지고있다.

Cobertura if(logger.isDebugEnabled()){가 100 % 완전하지 않은 것으로 보여주고 Clover는 그렇다고했지만 둘 다 확인이 실패한다는 데 동의하는 것은 이상하게 보입니다 .


@Mick, 정적 필드의 소유자도 준비하십시오. 예 :

@PrepareForTest({GoodbyeController.class, LoggerFactory.class})

EDIT1 : 방금 작은 예를 만들었습니다. 먼저 컨트롤러 :

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Controller {
    Logger logger = LoggerFactory.getLogger(Controller.class);

    public void log() { logger.warn("yup"); }
}

그런 다음 테스트 :

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({Controller.class, LoggerFactory.class})
public class ControllerTest {

    @Test
    public void name() throws Exception {
        mockStatic(LoggerFactory.class);
        Logger logger = mock(Logger.class);
        when(LoggerFactory.getLogger(any(Class.class))).thenReturn(logger);

        new Controller().log();

        verify(logger).warn(anyString());
    }
}

수입에주의하십시오! 클래스 경로의 주목할만한 라이브러리 : Mockito, PowerMock, JUnit, logback-core, logback-clasic, slf4j


EDIT2 : 인기있는 질문 인 것 같기 때문에 이러한 로그 메시지가 중요 하고 테스트가 필요한 경우 즉, 시스템의 기능 / 비즈니스 부분 인 경우 명확한 종속성을 도입 한다는 점을 지적하고 싶습니다. 이러한 로그는 로거의 표준 및 기술 클래스의 정적 코드에 의존하는 대신 전체 시스템 설계에서 훨씬 더 나은 기능입니다 .

이 문제에 관해서 나는 공예 같은 = A에게 추천 할 것입니다 Reporter같은 방법으로 클래스 reportIncorrectUseOfYAndZForActionXreportProgressStartedForActionX. 이것은 코드를 읽는 모든 사람이 기능을 볼 수 있도록하는 이점이 있습니다. 그러나 테스트를 수행하고이 특정 기능의 구현 세부 사항을 변경하는데도 도움이됩니다.

따라서 PowerMock과 같은 정적 조롱 도구가 필요하지 않습니다. 제 생각에는 정적 코드가 괜찮을 수 있지만 테스트에서 정적 동작을 확인하거나 모의하려면 즉시 리팩토링하고 명확한 종속성을 도입해야합니다.


파티에 다소 늦었습니다. 비슷한 일을하고 있었는데 몇 가지 조언이 필요했고 결국 여기까지 왔습니다. 신용 없음-나는 Brice로부터 모든 코드를 가져 왔지만 Cengiz가 얻은 것보다 "제로 상호 작용"을 얻었습니다.

jheriks 및 Joseph Lust가 제시 한 지침을 사용하여 이유를 알고 있다고 생각합니다. 테스트 대상인 개체를 Brice와 달리 @Before에서 새로 작성했습니다. 그런 다음 실제 로거는 모의가 아니라 jhriks가 제안한대로 실제 클래스가 초기화되었습니다.

일반적으로 테스트 대상 개체에 대해이 작업을 수행하여 각 테스트에 대해 새로운 개체를 얻습니다. 필드를 로컬로 이동하고 테스트에서 새로 만들었을 때 정상적으로 실행되었습니다. 그러나 두 번째 테스트를 시도하면 테스트의 모의가 아니라 첫 번째 테스트의 모의 였고 다시 제로 상호 작용을 얻었습니다.

@BeforeClass에 모의 생성을 넣을 때 테스트 대상 개체의 로거는 항상 모의이지만이 문제에 대해서는 아래 참고를 참조하십시오.

테스트중인 클래스

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyClassWithSomeLogging  {

    private static final Logger LOG = LoggerFactory.getLogger(MyClassWithSomeLogging.class);

    public void doStuff(boolean b) {
        if(b) {
            LOG.info("true");
        } else {
            LOG.info("false");
        }

    }
}

테스트

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.mockito.Mockito.*;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.*;
import static org.powermock.api.mockito.PowerMockito.when;


@RunWith(PowerMockRunner.class)
@PrepareForTest({LoggerFactory.class})
public class MyClassWithSomeLoggingTest {

    private static Logger mockLOG;

    @BeforeClass
    public static void setup() {
        mockStatic(LoggerFactory.class);
        mockLOG = mock(Logger.class);
        when(LoggerFactory.getLogger(any(Class.class))).thenReturn(mockLOG);
    }

    @Test
    public void testIt() {
        MyClassWithSomeLogging myClassWithSomeLogging = new MyClassWithSomeLogging();
        myClassWithSomeLogging.doStuff(true);

        verify(mockLOG, times(1)).info("true");
    }

    @Test
    public void testIt2() {
        MyClassWithSomeLogging myClassWithSomeLogging = new MyClassWithSomeLogging();
        myClassWithSomeLogging.doStuff(false);

        verify(mockLOG, times(1)).info("false");
    }

    @AfterClass
    public static void verifyStatic() {
        verify(mockLOG, times(1)).info("true");
        verify(mockLOG, times(1)).info("false");
        verify(mockLOG, times(2)).info(anyString());
    }
}

노트

If you have two tests with the same expectation I had to do the verify in the @AfterClass as the invocations on the static are stacked up - verify(mockLOG, times(2)).info("true"); - rather than times(1) in each test as the second test would fail saying there where 2 invocation of this. This is pretty pants but I couldn't find a way to clear the invocations. I'd like to know if anyone can think of a way round this....


In answer to your first question, it should be as simple as replacing:

   when(LoggerFactory.getLogger(GoodbyeController.class)).thenReturn(loggerMock);

with

   when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock);

Regarding your second question (and possibly the puzzling behavior with the first), I think the problem is that logger is static. So,

private static Logger logger = LoggerFactory.getLogger(GoodbyeController.class);

is executed when the class is initialized, not the when the object is instantiated. Sometimes this can be at about the same time, so you'll be OK, but it's hard to guarantee that. So you set up LoggerFactory.getLogger to return your mock, but the logger variable may have already been set with a real Logger object by the time your mocks are set up.

You may be able to set the logger explicitly using something like ReflectionTestUtils (I don't know if that works with static fields) or change it from a static field to an instance field. Either way, you don't need to mock LoggerFactory.getLogger because you'll be directly injecting the mock Logger instance.


I think you can reset the invocations using Mockito.reset(mockLog). You should call this before every test, so inside @Before would be a good place.


Use explicit injection. No other approach will allow you for instance to run tests in parallel in the same JVM.

Patterns that use anything classloader wide like static log binder or messing with environmental thinks like logback.XML are bust when it comes to testing.

Consider the parallelized tests I mention , or consider the case where you want to intercept logging of component A whose construction is hidden behind api B. This latter case is easy to deal with if you are using a dependency injected loggerfactory from the top, but not if you inject Logger as there no seam in this assembly at ILoggerFactory.getLogger.

And its not all about unit testing either. Sometimes we want integration tests to emit logging. Sometimes we don't. Someone's we want some of the integration testing logging to be selectively suppressed, eg for expected errors that would otherwise clutter the CI console and confuse. All easy if you inject ILoggerFactory from the top of your mainline (or whatever di framework you might use)

So...

Either inject a reporter as suggested or adopt a pattern of injecting the ILoggerFactory. By explicit ILoggerFactory injection rather than Logger you can support many access/intercept patterns and parallelization.

ReferenceURL : https://stackoverflow.com/questions/8948916/mocking-logger-and-loggerfactory-with-powermock-and-mockito

반응형