2011-04-16 148 views
14

我有一個存儲應用程序上下文信息的應用程序。應用程序上下文信息在擴展Application類的MyApp類中的活動之間共享。如何模擬getApplicationContext

我正在爲我的活動編寫一個單元測試,我想檢查一下,當用戶在活動中單擊某個按鈕時,應用程序狀態將會改變。事情是這樣的:

@Override 
public void onClick(View pView) { 
    ((MyApp)getApplicationContext()).setNewState(); 
} 

的問題是,我不知道如何嘲笑那個應用程序上下文。我正在使用ActivityUnitTestCase作爲測試案例庫。當我撥打setApplication時,它將更改值應用程序成員活動類,但不是應用程序上下文。我試過setActivityContext也是,但它似乎是錯誤的(它不是應用程序上下文,但活動上下文),它觸發startActivity內部斷言)。

所以問題是 - 如何模擬getApplicationContext()

+0

我與_getApplication()代替_getApplicationContext()_的理念上來。現在,我可以嘲笑_Application_對象,並使用_setApplication()_。這是有點解決方法。但是,我沒有得到這些方法之間的區別。而[問題](http://stackoverflow.com/questions/5018545)與未回答。 – lstipakov 2011-04-16 12:22:06

回答

30

由於方法getApplicationContext位於您正在擴展的類中,因此會變得有些問題。有幾個問題需要考慮:

  • 你真的不能模擬正在測試的類,這是對象繼承(即子類化)的許多缺點之一。
  • 另一個問題是,ApplicationContext是一個singleton,這使得它測試更加邪惡,因爲你不能輕易地嘲笑一個被編程爲不可替代的全局狀態。

你可以在這種情況下做的,是喜歡object composition over inheritance。所以爲了讓你的Activity可測試,你需要將邏輯分解一點。假設你的Activity被稱爲MyActivity。它需要由組成的一個邏輯組件(或類),讓它命名爲MyActivityLogic。下面是一個簡單的類圖圖:

MyActivity and MyActivityLogic UML diagram from yUml

爲了解決上述問題單,我們讓邏輯是與應用程序上下文中,「注入」,因此它可以用一個模擬測試。然後,我們只需測試MyActivity對象是否已將正確的應用程序上下文放入MyActivityLogic。我們如何基本解決這兩個問題是通過another layer of abstraction(從Butler Lampson轉述)。在這種情況下,我們添加的新層是在活動對象之外移動的活動邏輯。

對於示例的緣故類需要尋找排序的是這樣的:

public final class MyActivityLogic { 

    private MyApp mMyApp; 

    public MyActivityLogic(MyApp pMyApp) { 
     mMyApp = pMyApp; 
    } 

    public MyApp getMyApp() { 
     return mMyApp; 
    } 

    public void onClick(View pView) { 
     getMyApp().setNewState(); 
    } 
} 

public final class MyActivity extends Activity { 

    // The activity logic is in mLogic 
    private final MyActivityLogic mLogic; 

    // Logic is created in constructor 
    public MyActivity() { 
     super(); 
     mLogic = new MyActivityLogic(
      (MyApp) getApplicationContext()); 
    } 

    // Getter, you could make a setter as well, but I leave 
    // that as an exercise for you 
    public MyActivityLogic getMyActivityLogic() { 
     return mLogic; 
    } 

    // The method to be tested 
    public void onClick(View pView) { 
     mLogic.onClick(pView); 
    } 

    // Surely you have other code here... 

} 

它都應該是這個樣子: classes with methods made in yUml

要測試MyActivityLogic你只需要一個簡單的JUnit TestCase代替ActivityUnitTestCase(因爲它不是一個Activity),您可以使用您選擇的模擬框架嘲笑你的應用環境(因爲手卷自己的嘲弄有點拖累)。示例使用Mockito

MyActivityLogic mLogic; // The CUT, Component Under Test 
MyApplication mMyApplication; // Will be mocked 

protected void setUp() { 
    // Create the mock using mockito. 
     mMyApplication = mock(MyApplication.class); 
    // "Inject" the mock into the CUT 
     mLogic = new MyActivityLogic(mMyApplication); 
} 

public void testOnClickShouldSetNewStateOnAppContext() { 
    // Test composed of the three A's   
    // ARRANGE: Most stuff is already done in setUp 

    // ACT: Do the test by calling the logic 
    mLogic.onClick(null); 

    // ASSERT: Make sure the application.setNewState is called 
    verify(mMyApplication).setNewState(); 
} 

要測試MyActivity您使用ActivityUnitTestCase像往常一樣,我們只需要確保它創建一個具有正確ApplicationContext一個MyActivityLogic。粗略的測試代碼示例,可以完成所有這些工作:

// ARRANGE: 
MyActivity vMyActivity = getActivity(); 
MyApp expectedAppContext = vMyActivity.getApplicationContext(); 

// ACT: 
// No need to "act" much since MyActivityLogic object is created in the 
// constructor of the activity 
MyActivityLogic vLogic = vMyActivity.getMyActivityLogic(); 

// ASSERT: Make sure the same ApplicationContext singleton is inside 
// the MyActivityLogic object 
MyApp actualAppContext = vLogic.getMyApp(); 
assertSame(expectedAppContext, actualAppContext); 

希望這一切對您有意義並幫助您。

+0

感謝您的詳細解答! – lstipakov 2011-04-18 08:26:19

+0

很棒的回答。事實上,Android的:) – Snicolas 2012-06-13 05:24:32

+0

您介紹MVP模式這是一個很好的答案。你保持OO理論是令人印象深刻的。 – Thom 2014-01-27 18:52:54