簡體   English   中英

Mockito無法驗證來自org.slf4j.Logger的多個方法調用

[英]Mockito fails to verify multiple invocations of methods from org.slf4j.Logger

我有一個包含2個條件的方法。 在每個條件中,都會調用Logger.error方法。 驗證該方法調用的第一個測試成功,但任何其他測試都失敗了

通緝但沒有被引用......實際上,與這個模擬沒有任何交互。

有誰知道為什么會這樣?

下面,我提供了一個示例類和一個單元測試,它將生成問題:

package packageName;

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

public class X {

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

    public void execute(boolean handle1stCase) {
        if (handle1stCase) {
            LOGGER.error("rumpampam");
        } else {
            LOGGER.error("latida");
        }
    }
}

考試:

package packageName;

import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
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.Mockito.*;
import static org.powermock.api.mockito.PowerMockito.mockStatic;

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

    @Mock
    private Logger loggerMock;

    private X x;

    @Before
    public void construct() {
        MockitoAnnotations.initMocks(this);

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

        x = new X();
    }

    @Test
    public void whenFirstCaseErrorLogged() throws Exception {
        x.execute(true);
        verify(loggerMock, times(1)).error("rumpampam");
    }

    @Test
    public void whenSecondCaseErrorLogged() throws Exception {
        x.execute(false);
        verify(loggerMock, times(1)).error("latida");
    }
}

結果:

通緝但未調用:loggerMock.error(“latida”); - > at packageName.XTest.whenSecondCaseErrorLogged(XTest.java:51)
實際上,與這個模擬沒有任何交互。

編輯:
我簡短地回答了為什么除了第一個測試之外的每個測試都沒有在這個答案評論中失敗。

我對問題的解決方案
在測試中提供:

public static Logger loggerMockStatic;  

比只為所有測試創建一個實例並在靜態變量中提供它,並使用靜態loggerMockStatic而不是on。 所以你會:

    ...  
    MockitoAnnotations.initMocks(this);

    if (loggerMockStatic == null) {
        loggerMockStatic = loggerMock;
    }

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

並在驗證方法中使用loggerMockStatic而不是loggerMock。

關於方法的一些想法
對我來說這很好,因為
1.它不破壞設計(如果你認為所需的變量應該是一個常數,那么它將保持這種方式)。
2.它在測試中只添加了4行,允許您測試常量(在本例中為記錄器)行為。 污染程度不大,測試用例仍然很清楚。

正如我在本回答中所解釋的那樣,“刪除最終並提供setter”方法會使系統面臨漏洞。 沒有人需要將記錄器設置為類,我總是希望系統根據需要打開。 不需要僅為需要測試而提供設定器。 測試應該適用於實現,而不是相反。

特別是在測試日志記錄時,我不認為應該在一般(大多數)情況下測試日志記錄。 記錄應該是應用程序的一個方面。 當你有其他輸出來測試某個路徑時,應測試那些輸出。 但是在這種情況下(也許是其他情況),某個路徑沒有其他輸出,例如在特定條件下記錄和返回,需要測試日志(根據我)。 我想知道即使有人改變了條件,日志消息仍將被記錄。 如果沒有日志,並且如果有人以錯誤的方式更改條件,則無法知道錯誤存在於這段代碼中(除了調試之外)。

我正和一些同事討論過,有一個單獨的類進行日志記錄就可以了。 這樣,常量在另一個類中被隔離,並且您將能夠僅使用Mockito檢查行為。 他們進一步說,如果您想將日志發送到電子郵件,這將更容易更改。
首先,我認為這是一個過早的模塊化,如果你不是在不久的將來瞄准在日志記錄方式之間切換。
其次,只使用Mockito +有另一個類和+3行代碼VS我的一行代碼(logger.error(...))+使用PowerMockito,我會再次使用后者。 在測試期間添加其他依賴項不會使您的生產代碼變得更慢,更笨重。 也許在考慮繼續集成並且測試也與其他階段一樣重要時,您可能會說這會使測試過程變得更慢更笨重,但我會犧牲它 - 對我來說似乎沒什么大不了的。

您的記錄器是靜態的,因此在加載類時不是在初始化對象時加載它。 你沒有guaratee,你的模擬將按時准備好,有時它可能有時不工作。

這就是為什么這不起作用:

類X中的字段是static和final,它允許僅在第一次加載類時設置它。 由於我在第一個答案中所寫的內容,這很危險。 在你的情況下,你很幸運,這不會發生,但......

Junit按以下順序執行測試用例:construct()whenFirstCaseErrorLogged()construct()whenSecondCaseErrorLogged()

現在假設在第一次調用construct()之后,XTest的字段loggerMock指向駐留在地址0001的對象。然后,LoggerFactory使用該對象初始化x對象的LOGGER字段。 然后從whenFirstCaseErrorLogged()調用x.error,因為loggerMock和X :: Logger都指向同一個對象。

現在我們到達第二個構造()。 你的loggerMock被重新初始化,現在它指向一個不同的對象,假設它存儲在地址0002的內存中。這是一個與先前創建的不同的新對象。 現在因為你的X :: LOGGER是靜態的,所以它不會被重新初始化,因此它仍然指向存儲在地址0001的對象。當你試圖驗證在loggerMock上調用的方法時,你會得到錯誤,因為在該對象上沒有執行任何操作而是調用前一個對象的錯誤方法。

以下是我的一些想法。 也許他們會顯得有幫助。 我想在將來你應該重新考慮使用靜態兩次。 為什么你想在不恆定時做一些事情? 在第二次運行后,您的參考變量是否具有相同的值? 當然可能會發生,但這種可能性很小。 靜態最終可以阻止你改變對象的狀態嗎? 當然不是它們只會阻止您將LOGGER重新分配給其他實例。 您在之前的評論中提到,您不希望代碼的用戶為您的LOGGER提供空引用。 沒關系,但你可以通過在提供異常或使用不同的空處理機制時拋出異常來防止這種情況。

關於使用static關鍵字已經說了很多。 有些人認為它是純粹的邪惡,有些人沒有,有些人仍然喜歡單身人士:)

無論你怎么想,你必須知道靜態對測試和線程沒有好處。 當像PI或euler數字那樣是靜態的時候我使用靜態final,但是對於具有可變狀態的對象我不使用static final。 我對實用程序類使用靜態方法,這些實用程序類不存儲狀態,只是進行一些處理(解析,計數等)並返回結果imediatelly。 一個很好的例子是像電力這樣的數學函數。

我認為這將是有用的;)

X類添加一個方法以允許設置記錄器,並從中刪除final記錄。 然后在測試中做這樣的事情。

@Mock private Logger mockLogger;
private X toTest = new X();

...
@Before
public void setUp() throws Exception {
    toTest.setLogger(mockLogger);
}

@Test
public void logsRumpampamForFirstCall() throws Exception {
    toTest.execute(true);
    verify(mockLogger).error("rumpampam");
}

@Test
public void logsLatidaForOtherCalls() throws Exception {
    toTest.execute(false);
    verify(mockLogger).error("latida");
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM