2012-01-20 115 views
34

我有以下Logger我想模擬出來,但是驗證日誌條目是被調用的,而不是內容。使用PowerMock和Mockito嘲笑Logger和LoggerFactory

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發生。使用三葉草,覆蓋率顯示爲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確實如此,但都同意驗證失敗。

+0

你試過了@MockPolicy嗎? [示例](https://code.google.com/p/powermock/wiki/MockPolicies)適用於EasyMock風格的嘲笑,但可以適用於Mockito。 –

回答

40

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

注意進口! 值得注意庫在classpath:的Mockito,PowerMock,JUnit中,的logback核心,的logback-CLASIC,SLF4J


EDIT2:因爲它似乎成爲一種流行的問題,我想指出的是,如果這些日誌消息是重要的,需要測試,即他們是系統的功能/業務部分然後引入一個真正的依賴關係,使清除這些日誌功能將是一個更好的整個系統設計,而不是依靠標準和技術類的記錄器的靜態代碼。

對於這個問題,我會建議用類似reportIncorrectUseOfYAndZForActionXreportProgressStartedForActionX等方法來製作類似= a Reporter類的東西。這將有助於使閱讀代碼的任何人都可以看到該功能。但它也將有助於實現測試,更改此特定功能的實現細節。

因此,你不需要像PowerMock這樣的靜態模擬工具。 在我看來,靜態代碼可以很好,但只要測試需要驗證或嘲笑靜態行爲,就需要重構並引入清晰的依賴關係。

+3

注意:如果您在那裏嘗試第二次@測試,您將遇到問題。在另外的測試中再次調用verify()將不起作用。如果您使用@ Before或新的var名稱,則不需要。 classLoader只會生成其中的一個,所以你不能有兩個不同的靜態類。把你的測試分成單獨的課程。 –

+4

不幸的是,我不能得到它的工作... Mockito說..其實,這與模擬零交互。 – Cengiz

+0

適合我(包括驗證)。確保您使用的是@PrepareForTest,並確認您已嘲笑日誌工廠的正確日誌查找方法。 –

5

在回答你的第一個問題,它應該是簡單的更換:

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

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

關於你提到的第二個問題(並且可能與第一令人費解的行爲),我認爲問題在於記錄器是靜態的。所以,

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

被執行時被初始化,而不是當對象被實例化。有時這可能幾乎是在同一時間,所以你會沒事的,但很難保證。所以你設置了LoggerFactory.getLogger來返回你的模擬,但是當你的mock被設置時,logger變量可能已經被設置了一個真正的Logger對象。

您可以使用類似ReflectionTestUtils(我不知道是否適用於靜態字段)或將其從靜態字段更改爲實例字段來顯式設置記錄器。無論如何,您不需要模擬LoggerFactory.getLogger,因爲您將直接注入模擬Logger實例。

7

對派對稍遲 - 我做了類似的事情,需要一些指示,最終在這裏。沒有信用 - 我拿走了Brice的所有代碼,但得到了Cengiz得到的「零交互」。

利用約瑟夫·拉斯特所做的jheriks和約瑟夫·拉斯特的指導,我想我知道爲什麼 - 我把我的對象作爲一個領域進行測試,並且在@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()); 
    } 
} 

注意

如果你有兩個測試用相同的期望,我不得不做的@AfterClass驗證作爲靜態的調用都疊起來 - verify(mockLOG, times(2)).info("true"); - 而不是時間(1)在每個測試作爲第二測試會失敗,說有2調用這個。這很漂亮,但我找不到清除調用的方法。我想知道是否有人可以想到圍繞此....

+1

馬庫斯溫德爾的建議使用重置爲我工作。 –

2

我想你可以重置使用Mockito.reset(mockLog)的調用。你應該在每次測試之前調用這個,所以@Before裏面會是個好地方。

0

使用顯式注入。 沒有其他方法可以讓您在同一個JVM中並行運行測試。

使用類似於靜態日誌綁定程序或類似環境的任何類加載器的模式認爲像logback.XML在測試時就會崩潰。

考慮一下我提到的並行化測試,或者考慮你想截獲其構造被隱藏在API後面的組件A的日誌記錄的情況。後一種情況很容易處理,如果你使用依賴注入loggerfactory頂部,但不是如果你注入記錄器,因爲在ILoggerFactory.getLogger的這個程序集中沒有接縫。

而且它不是全部關於單元測試。有時我們想要集成測試來發布日誌記錄。有時候我們沒有。有人希望某些集成測試日誌記錄能夠被有選擇地抑制,例如,預期的錯誤會導致CI控制檯混亂並混淆。所有容易,如果你從主線的頂部注入ILoggerFactory(或任何DI框架您可以使用)

所以......

無論是注入了記者的建議或採用注射ILoggerFactory的模式。通過顯式的ILoggerFactory注入而不是Logger,你可以支持許多訪問/截取模式和並行化。