2014-04-25 37 views
2

我正在測試我在Android中創建的Fragment。我完全控制了代碼,所以我可以根據自己的情況對其進行更改。問題是,我不確定我錯過了哪些設計模式以使其合理。Android中的模擬對象未作爲參數傳遞

我正在尋找一種方法來模擬Android中不作爲參數傳遞的對象。 This question表明,您可能想要模擬的任何內容都應該寫入以作爲參數傳遞。

這對某些情況是有意義的,但我無法弄清楚如何讓它在Android上工作,其中有些是不可能的。例如,使用Fragment,您不得不在回調方法中完成大部分繁重工作。我怎樣才能讓我的嘲弄物體進入碎片?

例如,在這ListFragment我需要檢索一系列的東西顯示給用戶。我顯示的東西需要動態檢索並添加到自定義適配器。它目前如下所示:

public class MyFragment extends ListFragment { 

    private List<ListItem> mList; 

    void setListValues(List<ListItem> values) { 
    this.mList = values; 
    } 

    List<ListItem> getListValues() { 
    return this.mList; 
    } 

    @Override 
    public void onCreateView(LayoutInflater i, ViewGroup vg, Bundle b) { 
    // blah blah blah 
    } 

    @Override 
    public void onViewCreated(View view, Bundle savedInstanceState) { 
    this.setListValues(ListFactory.getListOfDynamicValues()); 
    CustomAdapter adapter = new CustomAdapter(
     getActivity(), 
     R.layout.row_layout, 
     this.getListValues()); 
    this.setListAdapter(adapter); 
    } 

} 

我正在嘗試使用Mockito和Robolectric來做到這一點。

這是我robolectric測試用例的開頭:

public class MyFragmentTest { 

    private MyFragment fragment; 

    @Before 
    public void setup() { 
    ListItem item1 = mock(ListItem.class); 
    ListItem item2 = mock(ListItem.class); 
    when(item1.getValue()).thenReturn("known value 1"); 
    when(item2.getValue()).thenReturn("known value 2"); 
    List<ListItem> mockList = new ArrayList<ListItem>(); 
    mockList.add(item1); 
    mockList.add(item2); 
    MyFragment real = new MyFragment(); 
    this.fragment = spy(real); 
    when(this.fragment.getValueList()).thenReturn(mockList); 
    startFragment(); 
    } 

} 

這感覺非常錯誤的。來自mockito api的This section指出,除非你處理遺留代碼,否則你不應該非常頻繁地進行部分模擬。

此外,我實際上不能用這種方法嘲笑CustomAdapter類。

做這種事情的正確方法是什麼?我是否在Fragment類中錯誤地構造了一些東西?我想我可能會添加一些私有套件,但這仍然不太合適。

有人可以對此有所瞭解嗎?我很高興重寫,我只想知道一些好的模式,用於處理Fragment中的狀態,以及我如何使它們可測試。

回答

1

我最終創建了自己的解決方案。我的方法是爲每個創建或設置對象的調用添加另一個間接級別。

首先,讓我指出我實際上無法讓Mockito與FragmentActivity對象一起可靠地工作。它有點受到打擊或遺漏,但特別是在嘗試創建Mockito Spy對象時,某些生命週期方法似乎不被調用。我認爲這與gotcha number 2 shown here有關。也許這是由於Android使用反射重新創建和實例化活動和片段的方式?請注意,我沒有錯誤地保留參考,因爲它提醒,但只與Spy互動,如圖所示。

因此,我無法模擬需要框架調用生命週期方法的Android對象。

我的解決方案是在我的Activity和Fragment方法中創建更多類型的方法。這些方法是:

  • getters(getX())返回字段名爲X
  • 檢索器(retrieveX()),做某種工作來獲得對象。
  • 創建者(createMyFragment())通過調用new創建對象。類似於檢索器。

吸氣劑有你需要的任何可見性。礦通常是publicprivate

檢索者和創建者是私有包或protected,允許您在測試包中覆蓋它們,但不會使它們一般可用。這些方法背後的想法是,您可以使用存根對象繼承常規對象,並在測試期間注入已知值。如果Mockito嘲笑/間諜正在爲你工作,你也可以嘲笑這些方法。

採取全部措施後,測試將如下所示。

這裏是我原來的問題的片段,修改爲使用上述方法。這是正常的項目:

package org.myexample.fragments 

// imports 

public class MyFragment extends ListFragment { 

    private List<ListItem> mList; 

    void setListValues(List<ListItem> values) { 
    this.mList = values; 
    } 

    List<ListItem> getListValues() { 
    return this.mList; 
    } 

    @Override 
    public void onCreateView(LayoutInflater i, ViewGroup vg, Bundle b) { 
    // blah blah blah 
    } 

    @Override 
    public void onViewCreated(View view, Bundle savedInstanceState) { 
    this.setListValues(this.retrieveListItems()); 
    CustomAdapter adapter = this.createCustomAdapter(); 
    this.setListAdapter(adapter); 
    } 

    List<ListItem> retrieveListItems() { 
    List<Item> result = ListFactory.getListOfDynamicValues(); 
    return result; 
    } 

    CustomAdapter createCustomAdapter() { 
    CustomAdapter result = new CustomAdapter(
     this.getActivity(); 
     R.layout.row_layout, 
     this.getListValues()); 
    return result; 
    } 

} 

當我測試這個目標,我希望能夠控制什麼獲取周圍通過。我的第一個想法是使用Spy,用我已知的值替換返回值retrieveListItems()createCustomAdapter()。然而,正如我上面所說的,我無法讓Mockito間諜在處理碎片時表現出色。 (尤其是ListFragment s - 我與其他類型混合成功,但不信任它。)所以,我們將繼承這個對象。在測試項目中,我有以下幾點。請注意,您的實際類中的方法可見性必須允許子類覆蓋,因此它需要封裝爲私有,並且在相同的包或protected中。請注意,我重寫了檢索器和創建器,而是返回了我的測試將設置的靜態變量。

package org.myexample.fragments 

// imports 

public class MyFragmentStub extends MyFragment { 

    public static List<ListItem> LIST = null; 
    public static CustomAdapter ADAPTER = null; 


    /** 
    * Resets the state for the stub object. This should be called 
    * in the teardown methods of your test classes using this object. 
    */ 
    public static void resetState() { 
    LIST = null; 
    ADAPTER = null; 
    } 

    @Override 
    List<ListItem> retrieveListItems() { 
    return LIST_ITEMS; 
    } 

    @Override 
    CustomAdapter createCustomAdapter() { 
    return CUSTOM_ADAPTER; 
    } 

} 

在我的測試項目中的相同包中,我有片段的實際測試。請注意,雖然我使用Robolectric,但它應該適用於您正在使用的任何測試框架。 @Before註釋的用處不大,因爲您需要更新個別測試的靜態狀態。

package org.myexample.fragments 

// imports 

@RunWith(RobolectricTestRunner.class) 
public class MyFragmentTest { 

    public MyFragment fragment; 
    public Activity activity; 

    @After 
    public void after() { 
    // Very important to reset the state of the object under test, 
    // as otherwise your tests will affect each other. 
    MyFragmentStub.resetState(); 
    } 

    private void setupState(List<ListItem> testList, CustomAdapter adapter) { 
    // Set the state you want the fragment to use. 
    MyFragmentStub.LIST = testList; 
    MyFragmentStub.ADAPTER = adapter; 
    MyFragmentStub stub = new MyFragmentStub(); 
    // Start and attach the fragment using Robolectric. 
    // This method doesn't call visible() on the activity, though so 
    // you'll have to do that yourself. 
    FragmentTestUtil.startFragment(stub); 
    Robolectric.ActivityController.of(stub.getActivity()).visible(); 
    this.fragment = stub; 
    this.activity = stub.getActivity(); 

    } 

    @Test 
    public void dummyTestWithKnownValues() { 
    // This is a test that does nothing other than show you how to use 
    // the stub. 
    // Create whatever known values you want to test with. 
    List<ListItem> list = new ArrayList<ListItem>(); 
    CustomAdapter adapter = mock(CustomAdapter.class); 
    this.setupState(list, adapter); 
    // android fest assertions 
    assertThat(this.fragment).isNotNull(); 
    } 

} 

這肯定比使用模擬框架更詳細。不過,它甚至可以在Android的生命週期中起作用。如果我正在測試Activity,我通常還會包含static boolean BUILD_FRAGMENTS變量。如果屬實,我將通過適當的方法調用超級方法,或者根據需要返回已知的片段。通過這種方式,我可以注入我的測試對象,並在Android生命週期中發揮出色。

相關問題