2014年4月13日 星期日

Robolectric介紹(二) 如何使用Robolectric 開發 project with SherlockActionBar

在 Robolectric介紹(一) shadow 介紹完簡單的功能之後,我開始思考是否有辦法將這個方法套用在我們的Project上面,在設定完成之後我嘗試run了一次test,結果如下


java.lang.IllegalStateException: there must have been some overlap for resourceIdToResName! expected 5815 but got 5814
at org.robolectric.res.MergedResourceIndex.merge(MergedResourceIndex.java:25)
at org.robolectric.res.MergedResourceIndex.(MergedResourceIndex.java:17)
at org.robolectric.res.RoutingResourceLoader.(RoutingResourceLoader.java:22)
at org.robolectric.RobolectricTestRunner.createAppResourceLoader(RobolectricTestRunner.java:598)
at org.robolectric.RobolectricTestRunner.getAppResourceLoader(RobolectricTestRunner.java:582)
at org.robolectric.internal.ParallelUniverse.setUpApplicationState(ParallelUniverse.java:98)
at org.robolectric.RobolectricTestRunner.setUpApplicationState(RobolectricTestRunner.java:401)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:219)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:174)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

在我花了很久時間debug與search之後,發現在xml內的小差異會造成test fail android:id="@android:id 以及 android:id="@+id 
只有android要求必須使用@android:id的才使用這種設定方式 例如 tabhost,ListFragment
否則一律使用 android:id="@+id ,錯用這個設定會造成Robolectric 無法 test

在projct成功可以test之後我遇到了另一個問題,由於我們的project implement了
ActionBarSherlock 所以在測試的時候常常會看到一些關於actionbar設定的nullpointer
為了解決這個問題,就必須在測試前預先做一些相關的設定

ActionBarSherlockRobolectric 提供了相關的解法,由於他提供的解法是給舊版本的,所以在這邊我們必須做一些小修改,只需要將

shadowOf(mActivity).setContentView(contentView);

修改成

mActivity.getWindow().setContentView(contentView);

就完成了 

接著我就可以開始做相關的測試 例如我希望測試從mainactivity 導向某個fragment之後的測試 
我可以這樣寫
 @Before
 public void init(){
  ActionBarSherlock.registerImplementation(ActionBarSherlockRobolectric.class);
  ActionBarSherlock.unregisterImplementation(ActionBarSherlockNative.class);
  ActionBarSherlock.unregisterImplementation(ActionBarSherlockCompat.class);
  
 }
 @Test @Config(reportSdk = 10, manifest = "AndroidManifest.xml")
    public void shouldNotBeNull() throws Exception {
  
     ShadowApplication shadowApplication = Robolectric.shadowOf(Robolectric.application);
     shadowApplication.declareActionUnbindable("com.google.android.gms.analytics.service.START");
     Fragment myFragment = new myTestFragment();
       
     
        startFragment( myFragment );

       assertNotNull( myFragment );
    }
    
    public static void startFragment( Fragment fragment )
    {

     SherlockFragmentActivity activity = Robolectric.buildActivity( MainActivity.class )
                                               .create()
                                               .start()
                                               .resume()
                                               .get();   

        FragmentManager fragmentManager = activity.getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add( fragment, null );
        fragmentTransaction.commit();
    }

需要注意的是,我在init做了相關的設定之後,接著在test的地方做了一些相關的Config,
AndroidManifest是要測試project的AndroidManifest.xml 否則會出現resourceNameNotFound
由於我們的project有使用google trace analytics所以必須加入下面這行
shadowApplication.declareActionUnbindable("com.google.android.gms.analytics.service.START");

否則會出現
java.lang.NullPointerException
 at com.google.analytics.tracking.android.AnalyticsGmsCoreClient$AnalyticsServiceConnection.onServiceConnected(AnalyticsGmsCoreClient.java:176)
 at org.robolectric.shadows.ShadowApplication$2.run(ShadowApplication.java:246)
 at org.robolectric.util.Scheduler$PostedRunnable.run(Scheduler.java:162)
 at org.robolectric.util.Scheduler.runOneTask(Scheduler.java:107)
 at org.robolectric.util.Scheduler.advanceTo(Scheduler.java:92)
 at org.robolectric.util.Scheduler.advanceToLastPostedRunnable(Scheduler.java:68)
 at org.robolectric.util.Scheduler.unPause(Scheduler.java:25)
 at org.robolectric.shadows.ShadowLooper.unPause(ShadowLooper.java:220)
 at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:259)
 at org.robolectric.util.ActivityController.create(ActivityController.java:111)
 at org.robolectric.util.ActivityController.create(ActivityController.java:123)
 at MyFragmentTest.startFragment(MyFragmentTest.java:61)
 at MyFragmentTest.shouldNotBeNull(MyFragmentTest.java:50)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
 at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
 at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:233)
 at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
 at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:174)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)


接著myTestFragment 其實是使用mock的原理,由於我測試的Fragment在init的時候會去
onCreateOptionsMenu ,並且執行 mItem.getActionView(), 並且做一些相關的設定
但是我們的actionbar是用mock出來的所以在getActionView的時候會回傳null,
由於Menu的相關功能我可以額外寫一個測試,所以在這裡我並不想測試這部分也不想關心這部分,所以我直接mock了我想測試的fragment並且override了onCreateOptionsMenu的method,範例如下

public class myTestFragment extends MyFragment{

 @Override
 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
  // TODO Auto-generated method stub
  //super.onCreateOptionsMenu(menu, inflater);
 }
}

在相關的設定都設定完成之後,我run了一次test結果卻依舊是紅燈


android.view.InflateException: <merge /> can be used only with a valid ViewGroup root and attachToRoot=true
 at android.view.LayoutInflater.inflate(LayoutInflater.java:455)
 at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
 at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
 at ActionBarSherlockRobolectric.setContentView(ActionBarSherlockRobolectric.java:40)
 at com.actionbarsherlock.app.SherlockFragmentActivity.setContentView(SherlockFragmentActivity.java:261)
 at com.nineyi.MainActivity.onCreate(MainActivity.java:268)
 at android.app.Activity.performCreate(Activity.java:5104)
 at org.fest.reflect.method.Invoker.invoke(Invoker.java:112)
 at org.robolectric.util.ActivityController$1.run(ActivityController.java:116)
 at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:257)
 at org.robolectric.util.ActivityController.create(ActivityController.java:111)
 at org.robolectric.util.ActivityController.create(ActivityController.java:123)
 at MyFragmentTest.startFragment(MyFragmentTest.java:61)
 at MyFragmentTest.shouldNotBeNull(MyFragmentTest.java:50)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
 at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
 at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:233)
 at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
 at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:174)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)


這個地方我目前還想不到解法,原因是在MainActivity使用了<merge> 當作我們的root,造成了他的錯誤,由於目前還找不到特別的解法,所以目前這部分我也只能先將source改成 releativeLayout,希望之後有其他的方法可以解決這個問題

在經過了一番折騰努力之後終於看到了期待已久的綠燈...
不過這只不過是個開始,測試的路才剛開通而已~


相關參考文件
https://gist.github.com/JakeWharton/3803294
https://github.com/playhaven/playhaven-robolectric
http://robolectric.blogspot.mx/2013/05/configuring-robolectric-20.html
http://robolectric.blogspot.tw/2013/04/the-test-lifecycle-in-20.html
http://stackoverflow.com/questions/17993239/getsupportactionbar-returns-null-with-robolectric/18034473#18034473

沒有留言:

張貼留言