Java 如何替换Android活动用于自动测试的对象类的实现?

Java 如何替换Android活动用于自动测试的对象类的实现?,java,android,eclipse,unit-testing,android-activity,Java,Android,Eclipse,Unit Testing,Android Activity,在测试Android活动时,我需要为接口使用不同于在生产环境中运行时的实现类。我熟悉使用模拟对象,但问题是Android上的某个活动是由操作系统实例化的,而不是由我实例化的,因此当我获得对该活动的引用时,它已经被实例化,并且已经使用了实现类。在我的例子中,我所说的类是一个登录类。对于我的自动测试,我希望能够控制登录结果。以下是我的例子: class MainActivity extends Activity { public void onCreate(Bundle savedStat

在测试Android活动时,我需要为接口使用不同于在生产环境中运行时的实现类。我熟悉使用模拟对象,但问题是Android上的某个活动是由操作系统实例化的,而不是由我实例化的,因此当我获得对该活动的引用时,它已经被实例化,并且已经使用了实现类。在我的例子中,我所说的类是一个登录类。对于我的自动测试,我希望能够控制登录结果。以下是我的例子:

class MainActivity extends Activity {

    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        setContentView(R.layout.main);
        LoginMgr aLoginMgr = new LoginMgrImpl();
        if (aLoginMgr.authenticated()) {
            //do something
        } else {
            //do something else
        }
    }
}


public interface LoginMgr {
    public boolean authenticated();
}
在库的其他地方,我有一个实现类:

public class LoginMgrImpl implements LoginMgr {
    //authenticate
}
在自动化测试过程中,我希望能够用一个测试版本替换生产
LoginMgrImpl
,该测试版本将返回测试所需的布尔值。这就是我需要帮助的地方,因为我不知道如何让
main活动
使用不同的
LoginMgrImpl
。如果有必要的话,我正在使用Eclipse。调用getActivity()时,
ActivityInstrumentationTestCase2
test类为我创建
MainActivity
。它调用no-arg构造函数,因此我没有机会在那里更改
LoginMgrImpl
。Eclipse控制构建,因此我看不到用不同的实现库构建
MainActivity
的方法

你们中有谁更有经验的人能为我指出一个成功自动化测试的方向吗?我不是唯一一个试图模拟活动对象使用的一些测试类的人,但我花了几个小时在论坛上试图找到一个解决方案,但没有成功。救命啊


根据每个人的反馈,我尝试了各种解决方案,找到了两种我可以接受的解决方案

解决方案1:其中一个在标记的答案中进行了解释,包括在Eclipse中设置一个单独的“项目”,该项目具有指向原始src、res、资产文件夹以及
AndroidManifest.xml
的符号链接。然后,我可以修改项目链接版本的项目属性,以引用支持我的测试的其他库实现。这很管用,但感觉很糟糕


解决方案2:为我的项目定义一个应用程序子类,它保存了
LoginMgr
实现的单个实例。然后,
main活动
将从应用程序实例中检索
LoginMgr
。在测试期间,我可以使用
ActivityUnitTestCase
注入一个引用模拟LoginMgr的模拟应用程序。以下是代码片段:

主要应用程序:(确保
AndroidManifest.xml
包括

MainActivity必须修改为:

class MainActivity extends Activity {

    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        setContentView(R.layout.main);

        // 2 lines added
        MainApplication aApplication = (MainApplication)getApplication();
        LoginMgr aLoginMgr = aApplication.getLoginMgr();

        if (aLoginMgr.authenticated()) {
            //do something
        } else {
            //do something else
        }
    }
}
单元测试必须是这样的:

public class MainActivityTest extends ActivityUnitTestCase<MainActivity> {

  public MainActivityTest() {
    super(MainActivity.class);
  }

  public void testUseAlternateLoginMgr() {
    MainApplication aMockApplication = new MainApplication()
    {
      @Override
      public LoginMgr getLoginMgr() {
        return new LoginMgr() {
          public boolean authenticated() {              {
            return true;
          }
        };
      }
    };

    setApplication(aMockApplication);

    Intent aDependencyIntent = new Intent("android.intent.action.MAIN");
    Activity aMainActivity = startActivity(aDependencyIntent, null, null);
    // verify results
  }
}
public class LoginHelper {
    private static LoginMgr _loginMgr ;

    public static LoginMgr get_LoginMgrImpl(){
        if(_loginMgr == null) _loginMgr = new LoginMgrImpl();
        return _loginMgr ;
    }

    public static void set_LoginMgrImpl(LoginMgr loginMgr){
        _loginMgr = loginMgr ;
    }
}
public类MainActivityTest扩展了ActivityUnitTestCase{
公共维护活动测试(){
超级(MainActivity.class);
}
public void testUseAlternateLoginMgr(){
MainApplication aMockApplication=新的MainApplication()
{
@凌驾
公共LoginMgr getLoginMgr(){
返回新的LoginMgr(){
公共布尔认证(){{
返回true;
}
};
}
};
setApplication(aMockApplication);
Intent-aDependencyIntent=newintent(“android.Intent.action.MAIN”);
Activity aMainActivity=startActivity(aDependencyIntent,null,null);
//验证结果
}
}
我可能会使用模拟框架而不是手工编码的模拟,但这只是一个示例


我认为解决方案2是更好的方法。谢谢@yorkw!

当我需要在Android应用程序中模拟或删除某些对象时,我已经将需要删除的对象的创建隔离到应用程序类中的一个简单的单一位置;然后根据它是否是测试对象,手动注释或取消注释我想要的版本r不是。这肯定不是自动化的,也不是很理想,但它起作用了

例如,让您的应用程序类拥有LoginMgr的(静态)实例,并通过静态方法使其可访问。您可以使用某种配置数据告诉应用程序类要使用哪个实现。
将配置数据传递到应用程序的一种方法是通过清单中的
节点,如前所述。您甚至可以通过声明包含
节点的特殊“测试”活动(而不是节点是
的子节点)并使用
getPackageManager().getActivityInfo来实现自动化(getComponentName(),PackageManager.GET_META_DATA)
从活动中获取它。然后,Eclipse启动配置将指定要启动的“测试”活动,而不是常规的“默认”活动活动。

您可能想研究使用依赖注入来解决此问题。我所知道的两个Android DI框架是和。它们看起来非常相似,甚至可能有某种联系;但我还没有进行足够的研究来了解它们的区别或关系。

测试我的应用程序的一种方法是在li中使用符号链接nux创建“可测试的”使用与生产中使用的库不同的库构建我的应用程序。为此,我在Eclipse中创建了两个项目,application和ApplicationTestable。application是真实的项目。ApplicationTestable是一个虚拟项目,它与application具有指向同一src、res和assets目录的符号链接。我还添加了一个符号链接链接到AndroidManifest.xml文件。然后,我通过进入项目属性并选择库的测试版本而不是生产版本,将ApplicationTestable链接到库的测试版本。然后,我进入jUnit测试所在的ApplicationTest项目的项目属性,并让它引用应用程序可测试项目。在这种情况下,我的librar测试版本
public MainActivity() {
  this(new LoginMgrImpl());
}

public MainActivity(LoginMgr loginMgr) {
  this.loginMgr = loginMgr;
}
public class LoginHelper {
    private static LoginMgr _loginMgr ;

    public static LoginMgr get_LoginMgrImpl(){
        if(_loginMgr == null) _loginMgr = new LoginMgrImpl();
        return _loginMgr ;
    }

    public static void set_LoginMgrImpl(LoginMgr loginMgr){
        _loginMgr = loginMgr ;
    }
}
public class ApplicationTest extends ActivityInstrumentationTestCase2<TestActivity> {
    TestActivity _activity;


    public ApplicationTest() {
        super(TestActivity.class);
        LoginHelper.set_LoginMgrImpl(new MockLoginMgr());
    }


    public void test1(){
        assertTrue(getActivity() != null);
    }
}