2011-02-11 62 views
17

我必须在这里失去一些东西。 ActivityUnitTestCase的JavaDoc建议此测试用例测试与系统隔离的活动:如何防止ActivityUnitTestCase调用Application.onCreate?

此类提供单个活动的单独测试。被测试的活动将在与系统基础结构最少连接的情况下创建,并且您可以注入许多Activity的依赖关系的模拟或包装版本。

我假设包括没有实际启动应用程序。此外,它暴露了一个setApplication助手,可以推测用于注入一个模拟应用程序。

但是,任何ActivityUnitTestCase我开始启动(实际)应用程序并调用其onCreate方法。更确切地说,InstrumentationTestRunner似乎正在这样做,甚至在我的测试的setUp方法中有机会获得setApplication之前这样做!我甚至没有注意到这一点,因为它似乎发生在测试套件启动期间的一个点上,甚至没有达到Eclipse断点,但是在onCreate中写入日志显示它实际上被调用。

这完全超出了我。为什么我想在Android的测试运行器实例化并执行实际应用程序时使用模拟应用程序对象?考虑到instrumentation runner运行在它自己的线程中,这会产生更多的问题,并在此时产生主应用程序线程。这意味着正在执行的测试和被调用的Application.onCreate之间存在竞争状态。如果您在那里做了可能会影响您测试的任何事情,例如写入一个共享的首选项文件,那么你完全搞砸了,因为你的测试会随机失败。

我错过了什么,或者这只是在测试框架中的一个粗略的监督?

UPDATE 这似乎也会影响ApplicationTestCase。在我的测试案例开始之前,我可以在我的应用程序类'onCreate中达到一个断点。我们在那里开始一个随意而废的AsyncTask,它会随机失败,因为我没有机会嘲笑它(记住,这是在我的测试用例上调用setUp之前)。这里是堆栈跟踪的onCreate的这个不起眼的调用过程中我看到:

Thread [<1> main] (Suspended (breakpoint at line 86 in QypeRadar)) 
QypeRadar.onCreate() line: 86 
InstrumentationTestRunner(Instrumentation).callApplicationOnCreate(Application) line: 969 
ActivityThread.handleBindApplication(ActivityThread$AppBindData) line: 4244 
ActivityThread.access$3000(ActivityThread, ActivityThread$AppBindData) line: 125  
ActivityThread$H.handleMessage(Message) line: 2071 
ActivityThread$H(Handler).dispatchMessage(Message) line: 99 
Looper.loop() line: 123 
ActivityThread.main(String[]) line: 4627  
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method] 
Method.invoke(Object, Object...) line: 521 
ZygoteInit$MethodAndArgsCaller.run() line: 868 
ZygoteInit.main(String[]) line: 626 
NativeStart.main(String[]) line: not available [native method] 

为什么测试运行callApplicationOnCreate即使the docs明确规定:

测试用例不会叫的onCreate(),直到你测试调用createApplication()。这使您有机会在onCreate()之前设置或调整任何其他框架或测试逻辑。

这是一个平坦的谎言 - 它不给我机会!

+0

据我所知,只有两种方法可以解决这个问题:1)重写InstrumentationTestRunner,不要对`callApplicationOnCreate`进行调用,并将其留给测试用例以决定是否需要。不知道对其他测试会有什么影响。 2)将我们的onCreate方法重写为无副作用和幂等(记住它将被调用两次,因为对于测试用例来说,你也必须调用`createApplication`)。思考? – Matthias 2011-02-16 10:30:36

+0

所以,我创建了一个绕过`callApplicationOnCreate`的自定义测试运行器,现在一切正常* unit *测试都能正常工作。对于功能测试,我仍然需要默认的测试运行器来执行全面启动,所以我现在有两个测试运行器,这有点痛苦。 – Matthias 2011-02-16 16:24:48

+0

有趣的写作。它是否足够简单,可以绕过适用于相应测试用例类型的`callApplicationOnCreate`,从而允许为测试运行器创建一个可以向上游发送的补丁? – 2011-02-16 18:02:13

回答

2

我正在用匕首进行测试,所以可能这也是你的情况,因为你可能只想注入并且不要调用应用程序中的任何东西。的onCreate,所以这个人是我(api17 +)正常工作:

private Context mContext; 
private Application mApplication; 

@Override 
protected void setUp() throws Exception { 
    super.setUp(); 

    mContext = new ContextWrapper(getInstrumentation().getTargetContext()) { 
     @Override 
     public Context getApplicationContext() { 
      return mApplication; 
     } 
    }; 
    mApplication = new MyAppMock(); 
    mApplication.attachBaseContext(mContext); 

    setApplication(app); 
} 

public void testActivityCreated() { 
    Intent intent = AboutActivity.createIntent(mContext); 
    setActivityContext(mContext); 
    startActivity(intent, null, null); 
    assertNotNull(getActivity()); 
} 

对于< api16你需要使用反射,并呼吁Application.attach(context)而是Application.attachBaseContext()设置Application.mLoadedApk,否则会崩溃。

我已经把一切融合在一起并取得演示程序,说明如何使用匕首进行测试: https://github.com/vovkab/dagger-unit-test

它还演示了如何嘲笑你的应用程序,适用于任何Android版本。