3

我想單元測試調用類「B」的靜態方法的類「A」。 'B'類本質上有一個谷歌guava緩存,它從緩存中獲取一個給定密鑰的值(對象),或者使用服務適配器將對象加載到緩存中(如果緩存未命中)。服務適配器類又具有其他自動裝配的依賴關係來檢索對象。單元測試調用靜態方法的類

這些是類用於說明目的:

A類

public class A { 
    public Object getCachedObject(String key) { 
     return B.getObjectFromCache(key); 
    } 
} 

B類

public class B { 

    private ServiceAdapter serviceAdapter; 

    public void setServiceAdapter(ServiceAdapter serAdapt) { 
     serviceAdapter = serAdapt; 
    } 

    private static final LoadingCache<String, Object> CACHE = CacheBuilder.newBuilder() 
       .maximumSize(100) 
       .expireAfterWrite(30, TimeUnit.MINUTES) 
       .build(new MyCacheLoader()); 

    public static Object getObjectFromCache(final String key) throws ExecutionException { 
     return CACHE.get(warehouseId); 
    } 

    private static class MyCacheLoader extends CacheLoader<String, Object> { 

     @Override 
     public Object load(final String key) throws Exception { 
      return serviceAdapter.getFromService(key) 
     } 
    } 
} 

服務的適配器類別

public class ServiceAdapter { 
     @Autowired 
     private MainService mainService 

     public Object getFromService(String key) { 
      return mainService.getTheObject(key); 
     } 
    } 

我能夠做成功集成測試和提取(或負載)從(或到)高速緩存中的價值。但是,我不能寫單元測試類A.這是我曾嘗試:

單元測試A級

@RunWith(EasyMocker.class) 
public class ATest { 
    private final static String key = "abc"; 
    @TestSubject 
    private A classUnderTest = new A(); 

    @Test 
    public void getCachedObject_Success() throws Exception { 
     B.setServiceAdapter(new ServiceAdapter()); 
     Object expectedResponse = createExpectedResponse(); //some private method 
     expect(B.getObjectFromCache(key)).andReturn(expectedResponse).once(); 
     Object actualResponse = classUnderTest.getCachedObject(key); 
     assertEquals(expectedResponse, actualResponse); 
    } 
} 

當我運行單元測試,它在調用mainService.getTheObject(key)的ServiceAdapter類中發生NullPointerException失敗。

如何嘲笑ServiceAdapter的依賴,而單元測試A級。我不應該只關注眼前的依賴類A有,即。 B.

我相信我做的事根本錯誤的。我應該如何編寫A班的單元測試?

+0

你類'B'甚至沒有編譯 –

+0

我只是想說明這些類是怎麼樣的。爲了說明的目的,我抽象了很多,這不是真正的類。 – user1639485

+2

一般來說,如果你不得不嘲笑對靜態方法的調用,這意味着你的代碼編寫不正確,應該重寫爲可測試的,所以如果你想知道如何重寫代碼 –

回答

3

現在你知道爲什麼靜態的方法被認爲是單元測試, 因爲他們使嘲諷幾乎是不可能的,尤其是不好的做法。如果他們是有狀態的。

它因此是更實際重構乙static方法成一組非靜態公立的。

A類應該得到類B的一個實例注入,或者通過構造或setter注入。在你的ATest中,你用類B的模擬實例化類A,並根據你的測試用例返回任何你喜歡的東西,並根據你的斷言進行判斷。

通過這樣做你真正測試單元,這到底應該是A類的公有接口(這也是爲什麼我喜歡一個類只有一個在理想世界中的公共方法。)


關於你的具體例子:B的模擬也不應該關心自己的依賴關係。您目前寫在你的測試:

B.setServiceAdapter(new ServiceAdapter());  

您在ATest。不在BTestATest應該只有模擬B,因此不需要傳遞ServiceAdapter的實例。

你只需要關心A的公共方法如何表現,而且這可能會隨着B公共方法的某些迴應而改變。

我還發現奇怪的是,你想測試的方法基本上只是一個包裝到B.也許這在你的情況是有道理的,但這也暗示我,你可能想要在A中注入一個Object而不是B的一個實例。

如果你不想迷失在嘲諷地獄裏,真正有助於減少每個類的公共方法,而這些公共方法反過來具有儘可能少的依賴關係。我在每個班級爭取三個附屬項目,並在特殊場合允許五個附屬項目。 (每個依賴可能會對模擬開銷產生巨大影響。)

如果您擁有太多依賴關係,那麼某些部分肯定會被移至其他/新服務。

+0

我想你也應該解釋一下,爲了清楚起見,除了將類B的實例注入到A中,getObjectFromCache方法需要作爲實例方法而不是靜態方法。試圖建議使CACHE字段和實例字段不是靜態的,即使它是私有的。 – tonicsoft

+0

@tonicsoft有效點。我會補充一點。 – k0pernikus

0

重寫代碼以使其更具可測性已在另一個答案中進行了解釋。有時很難避免這些情況。

如果你真的想模擬靜態調用,你可以使用PowerMock。你需要在你的類中使用@PrepareForTest({CACHE.class})註釋,然後在單元測試中使用下面的代碼。

 Object testObject = null; // Change this 
     PowerMock.mockStatic(CACHE.class); 
     expect(CACHE.get(anyString())).andReturn(testObject); 
     PowerMock.replay(CACHE.class); 
0

爲了得到這個你身邊一旦你有一個接口,那麼你可以存根它用於測試目的可以環繞B類的接口庫類型的類。

這樣做,你從B中的內部運作隔離A和只專注於導致B的行動(我想這是說來劃去的接口,而不是一個具體類的另一種方式)

相關問題