簡體   English   中英

如何在不使Mockito進行額外調用的情況下使用Mockito檢查對函數的調用次數

[英]How to use Mockito to check number of calls to a function without making Mockito make an extra call

我們已經建立了自己的框架,可以輕松建立分析管道。 每次分析結束時,都會調用finish()。 finish()上傳分析期間生成的文件。 為了確保正確使用框架,我們進行了一次檢查,以確保未兩次調用finish()。

現在,我想測試針對管道中的特定步驟調用了finish()。 為此,我在測試中調用了以下命令:

verify(consumer).finish();

但是顯然,verify()也調用finish(),因此引發了異常並且測試失敗。

現在,我的問題是:

  • 如何避免兩次調用finish()?

編輯

問題的快速設置:

分析

package mockitoTwice;

public class Analysis extends Finishable {
    @Override
    public void finishHelper() {
        System.out.println("Calling finishHelper()");
    }
}

最終處理

package mockitoTwice;

public abstract class Finishable {
    protected boolean finished = false;

    public final void finish() {
        System.out.println("Calling finish()");
        if (finished) {
            throw new IllegalStateException();
        }
        finished = true;
        finishHelper();
    }

    public abstract void finishHelper();
}

管道

package mockitoTwice;

public class Pipeline {
    private Analysis analysis;

    public Pipeline(Analysis analysis) {
        this.analysis = analysis;
    }

    public void runAnalyses() {
        analysis.finish();
    }
}

PipelineTest

package mockitoTwice;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import org.junit.Test;

public class PipelineTest {
    @Test
    public void test() {
        Analysis analysis = mock(Analysis.class);
        Pipeline pipeline = new Pipeline(analysis);
        pipeline.runAnalyses();
        verify(analysis).finish();
    }
}

測試框架都有其獨特之處,但是當您遇到此類問題時,第一步就是評估您的課程和測試設計。

首先,我注意到AnalysisTest並未真正測試Analysis類。 它是在模擬Analysis並實際測試Pipeline類。 正確的Analysis測試如下所示:

@Test
public void testFinishTwice() {

    Analysis analysis = new Analysis();

    try {
        analysis.finish();
        analysis.finish();
        Assert.fail();
    } catch (IllegalStateException ex) {
       // Optionally assert something about ex
    }
}

這將驗證當多次調用finish()時Analysis拋出IllegalStateException的隱含約定。 有多種解決方案可以解決您的問題,其中大多數都取決於對此的驗證。

接下來,帶有final finish()方法的抽象Finishable類看起來並不十分安全。 由於finishHelper方法具有受保護的訪問權限,因此包中的任何類仍可以直接訪問該方法。 因此,在您的示例中,如果Pipeline和Analysis位於同一軟件包中,則Pipeline可以直接調用finishHelper。 我想這是實際完成代碼被調用兩次的最大風險。 意外讓您的IDE自動完成完成finishHelper有多容易? 即使您的單元測試按照您的要求進行,它也無法抓住這一點。

現在,我已經解決了這個問題,我們可以找到問題的根源。 finish方法標記為final,因此Mockito無法覆蓋它。 通常,Mockito會為其創建一個存根方法,但是在這里它必須使用Finishable中的原始代碼。 當調用finish時,真正的模擬程序甚至都不會打印“ Calling finish()”。 由於它停留在原始實現上,因此真正的finish方法既可以通過管道調用,也可以通過verify(analysis).finish()再次調用。

那么我們該怎么辦? 沒有完美的答案,這實際上取決於具體情況。

最簡單的方法是將final關鍵字放在finish方法上。 然后,只需確保Analysis和Pipeline不在同一程序包中。 您編寫的測試確保了“僅管道”調用完成一次。 我建議的測試可確保在Analysis上調用第二次完成的異常。 即使會覆蓋完成,也會發生這種情況。 您仍然可以濫用它,但是您必須刻意去做。

您也可以將Finishable切換為接口,並將當前類AbstractFinishable重命名為基本實現。 然后將Analysis切換到擴展Finishable的接口,並創建一個擴展AbstractFinishable並實現Analysis的ExampleAnalysis類。 然后,管道引用分析接口。 我們必須這樣做,因為否則它可以訪問finishHelper,我們回到了起點。 這是代碼的草圖:

public interface Finishable {
    public void finish();
}

public abstract class AbstractFinishable implements Finishable {
    // your current Finishable class with final finish method goes here                                                                                                                   
}

public interface Analysis extends Finishable {
    // Other analysis methods that Pipeline needs go here                                                                                                        
}

public ExampleAnalysis extends AbstractFinishable implements Analysis {
    // Implementations of Analysis methods go here                                                                                                               
}

所以這是做到這一點的一種方法。 實質上是將要編碼的類切換到其依賴項的接口,而不是特定的類實現。 通常,這更容易模擬和測試。 您還可以使用委托模式,僅在ExampleAnalysis上放置Finishable,而不是擴展AbstractFinishable。 也有其他方法,這些只是想法。 您應該充分了解項目的細節,以決定最佳路線。

我這樣驗證: verify(object, times(1)).doStuff();

可以通過捕獲框架異常來解決此問題,如下所示:

@Rule
public ExpectedException exception;

@Test
public void test() {
    Analysis analysis = mock(Analysis.class);
    Pipeline pipeline = new Pipeline(analysis);
    pipeline.runAnalyses();
    exception.expect(IllegalStateException.class);
    verify(analysis).finish();
}

如果finish()的調用次數太少,則驗證會按預期處理該問題。

如果finish()被調用太多,則在pipeline.runAnalyses()上調用該異常。

否則,測試成功。

暫無
暫無

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

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