四、使用Mock對象


很多情況下,代碼需要與外部依賴打交道,如一個REST地址,數據庫鏈接、外部IO等;這些依賴有些速度過慢、有些不夠穩定,不符合單元測試要求的快速、可重復等原則性要求,因此引入了Mock對象這一概念。與Mock相關的還有Stub這個單詞。
  • stub 樁,它針對指定的輸入緩存了行為
  • mock 模擬對象,增加了對輸入條件校驗、注入等功能,簡單來說,它保證在收到預期參數時表現出預定義的行為,常用的有兩個框架
    • mockito 較為易用
    • powermock 功能更加強大,能夠對靜態方法和私有函數進行Mock
一般來說,在編寫stub之后,需要將其注入依賴對象中,也即依賴注入(DI),框架上有Spring DI和Google Guice等。

修改代碼結構使其更具可測性

為了使得測試更加容易,有時需要修改代碼,如將依賴以成員變量的形式傳入被測類中,如:
public class AddressRetriever{
    private Http http; //將外部依賴以構造函數的方式引入,對單元測試更加友好
 
    public AddressRetriever(Http http){
        this.http = http;
    }
 
    public Address retrieve(double latitude, double longitude) throws IOException, ParseException{
        String params = String.format("lat=%.6flon=%.6f", latitude, longitude);
        String response = http.get("http://open.mapquestapi.com/nominatim/v1/reverse?format=json&"+params);
 
        JSONObject obj = (JSONObject)new JSONParse().parse(reponse);
        //...
    }
}
 
但不僅限於構造函數,還可以通過set方法或其他依賴注入框架實現。

為Stub增加一點智能

如這個樁:
Http http = new Http(){
    @Override
    public String get(String uri) throws IOException{
        return "{\"address\":{" + "\"house_number\":\"324\"," + "\"road\":\"North Tejon Street\","
        }
}
這個樁接受任何uri即可返回對應的結果,沒有對輸入進行判斷,我們期望的是:在收到預期參數時提供預期的輸入,可以通過在get()方法中加入判斷實現,這樣的通用功能引入Mock工具。

使用Mock工具簡化測試

public class AddressRetrieverTest {
    @Test
    public void answersAppropriateAddressForValidCoordinates() throws IOException, ParseException {
        Http http = mock(Http.class);
        when(http.get(contains("lat=38.000000&lon=-104.000000"))).thenReturn(
            "{\"address\":{" + "\"house_number\":\"324\","
            // ...
            + "}");
 
        AddressRetriever retriever = new AddressRetriever(http);
        Address address = retriever.retrieve(38.0,-104.0);
 
        assertThat(address.houseNumber, equalTo("324"));
    }
when().thenReturn()模式就是Mockito設置的常用方式。

介紹一種DI工具

DI工具有很多,如Spring DI和Google Guice,但是moctito內建的DI工具也能滿足絕大部分的需要,步驟如下:
  • 使用@Mock注解創建一個模擬對象
  • 使用@InjectMocks注解聲明一個目標對象
  • 在目標對象初始化完畢后,調用MockitoAnnotations.initMocks(this)方法完成注入
下面是示例代碼:
public class AddressRetrieverTest{
    @Mock
    private Http http;
 
    @InjectMocks
    private AddressRetriever retriever;
 
    @Before
    public void createRetriever(){
        retriever = new AddressRetriever();
        MockitoAnnotations.initMocks(this);
    }
 
    @Test
    public void answersAppropriateAddressForValidCoordinates() throws IOException, ParseException{
        when(http.get(contains("lat=38.000000&lon=-104.000000")))
            .thenReturn("{\"address\":{" 
                + "\"house_number\":\"324\","
                      //...
           }
 }
最后需要注意的是,如果使用了Mock,那不是直接測試生產代碼,而是在於生產代碼中加了鴻溝,單元測試的正確性依賴於被Mock對象的正確性,因此單元測試需要配合端到端的集成測試。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM