66

モックアウトしたい次のロガーがありますが、ログエントリが呼び出されていることを検証するために、コンテンツに対してではありません。

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はそうしていますが、どちらも検証が失敗することに同意しています。

4

8 に答える 8

74

編集2020-09-21:3.4.0以降、Mockitoは静的メソッドのモックをサポートしています。APIはまだインキュベーション中であり、特にスタブと検証に関して変更される可能性があります。mockito-inlineアーティファクトが必要です。また、テストを準備したり、特定のランナーを使用したりする必要はありません。あなたがする必要があるのは:

@Test
public void name() {
    try (MockedStatic<LoggerFactory> integerMock = mockStatic(LoggerFactory.class)) {
        final Logger logger = mock(Logger.class);
        integerMock.when(() -> LoggerFactory.getLogger(any(Class.class))).thenReturn(logger);
        new Controller().log();
        verify(logger).warn(any());
    }
}

このコードの2つの重要な側面は、静的モックが適用されるとき、つまりこのtryブロック内でスコープを設定する必要があることです。また、オブジェクトからスタブと検証のAPIを呼び出す必要がありMockedStaticます。


@ミック、静的フィールドの所有者も準備してみてください。例:

@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:よくある質問のようですが、これらのログメッセージが非常に重要であり、テストする必要がある場合、つまり、システムの機能/ビジネス部分である場合は、明確にする実際の依存関係を導入することを指摘しておきます。これらのログは、ロガーの標準クラスと技術クラスの静的コードに依存するのではなく、システム設計全体ではるかに優れた機能です。

この問題については、またはReporterなどのメソッドを使用して=クラスのようなものを作成することをお勧めします。これには、コードを読んでいるすべての人に機能が表示されるという利点があります。ただし、テストを実行したり、この特定の機能の実装の詳細を変更したりするのにも役立ちます。reportIncorrectUseOfYAndZForActionXreportProgressStartedForActionX

したがって、PowerMockのような静的なモックツールは必要ありません。私の意見では、静的コードは問題ない可能性がありますが、テストで静的な動作を検証またはモックする必要があるとすぐに、リファクタリングして明確な依存関係を導入する必要があります。

于 2012-01-29T11:44:33.580 に答える
19

パーティーに少し遅れました-私は似たようなことをしていて、いくつかのポインターが必要で、ここに行き着きました。クレジットを受け取らない-私はBriceからすべてのコードを取得しましたが、Cengizが取得したよりも「相互作用がゼロ」でした。

jheriksとJosephLustが書いたものからのガイダンスを使用して、私は理由を知っていると思います-私はフィールドとしてテスト中のオブジェクトを持っていて、Briceとは異なり@Beforeでそれを新しくしました。次に、実際のロガーはモックではなく、jhriksが提案したように実際のクラスが初期化されました...

私は通常、テスト対象のオブジェクトに対してこれを実行して、テストごとに新しいオブジェクトを取得します。フィールドをローカルに移動し、テストで新しくしたとき、問題なく実行されました。ただし、2番目のテストを試した場合、それは私のテストのモックではなく、最初のテストのモックであり、相互作用はゼロになりました。

モックの作成を@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());
    }
}

ノート

同じ期待値を持つ2つのテストがある場合、静的な呼び出しが積み重なっているため、@ AfterClassで検証を行う必要がありました。2verify(mockLOG, times(2)).info("true"); 番目のテストでは2回の呼び出しが失敗するため、各テストのtimes(1)ではありません。これの。これはかわいいパンツですが、呼び出しをクリアする方法を見つけることができませんでした。誰かがこれを回避する方法を考えることができるかどうか知りたいです....

于 2014-06-11T20:57:37.973 に答える
6

あなたの最初の質問に答えて、それは置き換えるのと同じくらい簡単でなければなりません:

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

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

2番目の質問(そしておそらく最初の質問での不可解な振る舞い)に関して、問題はロガーが静的であるということだと思います。それで、

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

オブジェクトがインスタンス化されるときではなく、クラスが初期化されるときに実行されます。これがほぼ同時に発生することもあるので、大丈夫ですが、それを保証するのは困難です。したがって、モックを返すようにLoggerFactory.getLoggerを設定しましたが、モックが設定されるまでに、ロガー変数が実際のLoggerオブジェクトですでに設定されている可能性があります。

ReflectionTestUtils(静的フィールドで機能するかどうかはわかりません)などを使用してロガーを明示的に設定したり、静的フィールドからインスタンスフィールドに変更したりできる場合があります。いずれにせよ、モックLoggerインスタンスを直接注入するため、LoggerFactory.getLoggerをモックする必要はありません。

于 2012-01-21T00:36:36.910 に答える
2

Mockito.reset(mockLog)を使用して呼び出しをリセットできると思います。すべてのテストの前にこれを呼び出す必要があるため、@Before内が適切な場所になります。

于 2014-11-05T12:06:50.560 に答える
1

明示的なインジェクションを使用します。たとえば、同じJVMでテストを並行して実行できるアプローチは他にありません。

静的ログバインダーのようにクラスローダー全体を使用するパターンや、logback.XMLのように環境をいじるパターンは、テストに関しては破綻しています。

私が言及した並列化されたテストを検討するか、APIBの背後に構造が隠されているコンポーネントAのロギングをインターセプトする場合を検討してください。この後者のケースは、依存関係が注入されたloggerfactoryを上から使用している場合は簡単に処理できますが、そうではありません。 ILoggerFactory.getLoggerのこのアセンブリに継ぎ目がないため、ロガーを注入した場合。

そして、それはユニットテストのすべてではありません。統合テストでログを出力したい場合があります。時々私達はしません。統合テストのログの一部を選択的に抑制したい人がいます。たとえば、CIコンソールを混乱させて混乱させる可能性のある、予想されるエラーなどです。メインライン(または使用する可能性のあるdiフレームワーク)の上部からILoggerFactoryを注入すれば、すべて簡単です。

それで...

提案されたようにレポーターを注入するか、ILoggerFactoryを注入するパターンを採用します。Loggerではなく明示的なILoggerFactoryインジェクションにより、多くのアクセス/インターセプトパターンと並列化をサポートできます。

于 2015-11-30T19:08:48.253 に答える
1
class SomeService{
  private static final Logger logger = LogManager.getLogger(SomeService.class);

  public void callSomeMethod(){
   ...
   ...
   if (logger.isDebugEnabled()) {
        logger.debug(String.format("some message ....."));
    }
  }

}

JUNITテストカバレッジ-

import java.io.IOException;
 
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
 
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
 

@RunWith(MockitoJUnitRunner.class)
public class SomeServiceTest {
  @Mock
  private Appender mockAppender;
 
  private Logger logger;
 
  @Before
  public void setup() {
    when(mockAppender.getName()).thenReturn("MockAppender");
    when(mockAppender.isStarted()).thenReturn(true);
    when(mockAppender.isStopped()).thenReturn(false);
 
    logger = (Logger)LogManager.getLogger(SomeService.class);
    logger.addAppender(mockAppender);
    logger.setLevel(Level.DEBUG);
  }
 
  
 
  @Test
  public void callSomeMethodTest() {
    //write test cases, logger.isDebugEnabled() will call.
  }
 
}
于 2021-06-10T16:13:50.633 に答える
0

以下は、クラスで指定されたプライベート静的ファイナルロガーをモックするテストクラスlogですLogUtil

ファクトリコールをモックすることに加えて、getLoggerリフレクションを使用してフィールドを明示的に設定する必要があります。@BeforeClass

public class LogUtilTest {

    private static Logger logger;

    private static MockedStatic<LoggerFactory> loggerFactoryMockedStatic;

    /**
     * Since {@link LogUtil#log} being a static final variable it is only initialized once at the class load time
     * So assertions are also performed against the same mock {@link LogUtilTest#logger}
     */
    @BeforeClass
    public static void beforeClass() {
        logger = mock(Logger.class);
        loggerFactoryMockedStatic = mockStatic(LoggerFactory.class);
        loggerFactoryMockedStatic.when(() -> LoggerFactory.getLogger(anyString())).thenReturn(logger);
        Whitebox.setInternalState(LogUtil.class, "log", logger);
    }

    @AfterClass
    public static void after() {
        loggerFactoryMockedStatic.close();
    }
} 
于 2021-01-21T09:05:32.687 に答える
0

1回のテストPowerMockでなくても動作します。Mockitoを使用して2つのテストを実行すると、クラス内のすべてのテストを実行verifyすると、2番目のテストが失敗し始めました。no interactions with this mock別々に両方のテストが機能しました。

いくつかの実験の後、私がモックロガーを作ったときだけそれは私のために働きましたstatic

@RunWith(MockitoJUnitRunner.class)
public class MyTest {
    private static MockedStatic<LoggerFactory> loggerFactoryMockedStatic;
    private static final Logger logger = mock(Logger.class);
    
    @BeforeClass
    public static void beforeAll() {
        loggerFactoryMockedStatic = mockStatic(LoggerFactory.class);
        loggerFactoryMockedStatic.when(() -> LoggerFactory.getLogger(MyServiceUnderTest.class))
                .thenReturn(logger);
    }

    @AfterClass
    public static void afterAll() {
        loggerFactoryMockedStatic.close();
    }


    @Before
    public void init() {
        Mockito.reset(logger); // important as it's static
        // ...

    @Test
    public void myTest() throws Exception {
        // ...
        verifyNoInteractions(logger);
    }
}
于 2021-09-10T11:00:35.807 に答える