2011-04-16 76 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