[英]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(),因此引發了異常並且測試失敗。
現在,我的問題是:
編輯
問題的快速設置:
分析
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.