使用Robolectrictesting自定义视图

我试图用Robolectric 2.1.1运行unit testing,我无法让它膨胀自定义布局(如ViewPagerIndicator类)。 假设这是我的布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="test" android:id="@+id/test_test"/> <com.viewpagerindicator.CirclePageIndicator android:layout_width="fill_parent" android:layout_height="wrap_content"/> </LinearLayout> 

考虑一下我的testing课:

 @RunWith(RobolectricTestRunner.class) public class TestRoboActivityTest { private TestRoboActivity mActivity; @Before public void setUp() throws Exception { mActivity = Robolectric.buildActivity(TestRoboActivity.class).create().get(); } @After public void tearDown() throws Exception { mActivity = null; } @Test public void testSanity() throws Exception { Assert.assertNotNull(mActivity); } } 

执行'mvn clean test'结果

testing错误:
 testSanity(TestRoboActivityTest):XML文件。\ res \ layout \ test.xml行#-1(抱歉,尚未实现):错误膨胀类com.viewpagerindicator.CirclePageIndicator

很酷,所以看起来像自定义视图尚不支持。 在他们的网站上检查示例Robolectric项目,一种解决scheme可能是从LayoutInflater扩展布局:

 @RunWith(RobolectricTestRunner.class) public class TestRoboActivityTest { private View mTestRoboActivityView; @Before public void setUp() throws Exception { mTestRoboActivityView = LayoutInflater.from(new Activity()).inflate(R.layout.test, null); } @After public void tearDown() throws Exception { mTestRoboActivityView = null; } @Test public void testSanity() throws Exception { Assert.assertNotNull(mTestRoboActivityView); } } 

这导致:

testing错误: 
 testSanity(TestRoboActivityTest):XML文件。\ res \ layout \ test.xml行#-1(抱歉,尚未实现):错误膨胀类com.viewpagerindicator.CirclePageIndicator

我最后的手段是试图使用影子类:

 @Implements(CirclePageIndicator.class) public class CirclePageIndicatorShadow implements PageIndicator { @Override @Implementation public void setViewPager(ViewPager view) { // Stub } // etc. } 

并使用@Config(shadows = {CirclePageIndicatorShadow.class}) 。 这又导致了

testing错误: 
 testSanity(TestRoboActivityTest):XML文件。\ res \ layout \ test.xml行#-1(抱歉,尚未实现):错误膨胀类com.viewpagerindicator.CirclePageIndicator

编辑(2014年12月)

请注意,David Rabinowitz后来添加了以下的追踪。 虽然有关,但这不是我当时所面临的问题。


这是堆栈跟踪:

 android.view.InflateException: XML file .\res\layout\activity_home.xml line #-1 (sorry, not yet implemented): Error inflating class com.test.custom.RobotoTextView at android.view.LayoutInflater.createView(LayoutInflater.java:613) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:687) at android.view.LayoutInflater.rInflate(LayoutInflater.java:746) at android.view.LayoutInflater.inflate(LayoutInflater.java:489) at android.view.LayoutInflater.inflate(LayoutInflater.java:396) at android.view.LayoutInflater.inflate(LayoutInflater.java:352) at org.robolectric.tester.android.view.RoboWindow.setContentView(RoboWindow.java:82) at org.robolectric.shadows.ShadowActivity.setContentView(ShadowActivity.java:273) at android.app.Activity.setContentView(Activity.java) at com.example.testrobocustomfont.MainActivity.onCreate(MainActivity.java:12) at com.example.testrobocustomfont.MainActivityTest.setUp(MainActivityTest.java:28) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27) at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31) at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:241) at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:177) at org.junit.runners.ParentRunner.run(ParentRunner.java:236) 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) Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:525) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_createView(LayoutInflater.java:587) at android.view.LayoutInflater.createView(LayoutInflater.java) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_createViewFromTag(LayoutInflater.java:687) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_rInflate(LayoutInflater.java:746) at android.view.LayoutInflater.rInflate(LayoutInflater.java) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_inflate(LayoutInflater.java:489) at android.view.LayoutInflater.inflate(LayoutInflater.java) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_inflate(LayoutInflater.java:396) at android.view.LayoutInflater.inflate(LayoutInflater.java) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_inflate(LayoutInflater.java:352) at android.view.LayoutInflater.inflate(LayoutInflater.java) at org.robolectric.tester.android.view.RoboWindow.setContentView(RoboWindow.java:82) at org.robolectric.shadows.ShadowActivity.setContentView(ShadowActivity.java:273) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.robolectric.bytecode.ShadowWrangler$ShadowMethodPlan.run(ShadowWrangler.java:455) at android.app.Activity.setContentView(Activity.java) at com.example.testrobocustomfont.MainActivity.onCreate(MainActivity.java:12) at com.example.testrobocustomfont.MainActivityTest.setUp(MainActivityTest.java:28) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) ... 22 more Caused by: java.lang.RuntimeException: error converting RobotoMedium.ttf using EnumConverter at org.robolectric.shadows.Converter.convertAndFill(Converter.java:150) at org.robolectric.shadows.Converter.convertAndFill(Converter.java:50) at org.robolectric.shadows.ShadowResources.createTypedArray(ShadowResources.java:228) at org.robolectric.shadows.ShadowResources.attrsToTypedArray(ShadowResources.java:203) at org.robolectric.shadows.ShadowResources.access$000(ShadowResources.java:51) at org.robolectric.shadows.ShadowResources$ShadowTheme.obtainStyledAttributes(ShadowResources.java:460) at android.content.res.Resources$Theme.obtainStyledAttributes(Resources.java) at android.widget.TextView.__constructor__(TextView.java:561) at android.widget.TextView.<init>(TextView.java:447) at android.widget.TextView.<init>(TextView.java:442) at com.test.custom.RobotoTextView.<init>(RobotoTextView.java:16) at android.view.LayoutInflater.createView(LayoutInflater.java:587) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:687) at android.view.LayoutInflater.rInflate(LayoutInflater.java:746) at android.view.LayoutInflater.inflate(LayoutInflater.java:489) at android.view.LayoutInflater.inflate(LayoutInflater.java:396) at android.view.LayoutInflater.inflate(LayoutInflater.java:352) at org.robolectric.tester.android.view.RoboWindow.setContentView(RoboWindow.java:82) at org.robolectric.shadows.ShadowActivity.setContentView(ShadowActivity.java:273) at android.app.Activity.setContentView(Activity.java) at com.example.testrobocustomfont.MainActivity.onCreate(MainActivity.java:12) at com.example.testrobocustomfont.MainActivityTest.setUp(MainActivityTest.java:28) ... 22 more Caused by: java.lang.RuntimeException: no value found for RobotoMedium.ttf at org.robolectric.shadows.Converter$EnumOrFlagConverter.findValueFor(Converter.java:375) at org.robolectric.shadows.Converter$EnumConverter.fillTypedValue(Converter.java:343) at org.robolectric.shadows.Converter$EnumConverter.fillTypedValue(Converter.java:336) at org.robolectric.shadows.Converter.convertAndFill(Converter.java:148) at org.robolectric.shadows.Converter.convertAndFill(Converter.java:50) at org.robolectric.shadows.ShadowResources.createTypedArray(ShadowResources.java:228) at org.robolectric.shadows.ShadowResources.attrsToTypedArray(ShadowResources.java:203) at org.robolectric.shadows.ShadowResources.access$000(ShadowResources.java:51) at org.robolectric.shadows.ShadowResources$ShadowTheme.obtainStyledAttributes(ShadowResources.java:460) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.robolectric.bytecode.ShadowWrangler$ShadowMethodPlan.run(ShadowWrangler.java:455) at android.content.res.Resources$Theme.obtainStyledAttributes(Resources.java) at android.widget.TextView.$$robo$$TextView_347d___constructor__(TextView.java:561) at android.widget.TextView.<init>(TextView.java:447) at android.widget.TextView.<init>(TextView.java:442) at com.test.custom.RobotoTextView.<init>(RobotoTextView.java:16) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:525) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_createView(LayoutInflater.java:587) at android.view.LayoutInflater.createView(LayoutInflater.java) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_createViewFromTag(LayoutInflater.java:687) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_rInflate(LayoutInflater.java:746) at android.view.LayoutInflater.rInflate(LayoutInflater.java) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_inflate(LayoutInflater.java:489) at android.view.LayoutInflater.inflate(LayoutInflater.java) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_inflate(LayoutInflater.java:396) at android.view.LayoutInflater.inflate(LayoutInflater.java) at android.view.LayoutInflater.$$robo$$LayoutInflater_1d1f_inflate(LayoutInflater.java:352) at android.view.LayoutInflater.inflate(LayoutInflater.java) at org.robolectric.tester.android.view.RoboWindow.setContentView(RoboWindow.java:82) at org.robolectric.shadows.ShadowActivity.setContentView(ShadowActivity.java:273) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.robolectric.bytecode.ShadowWrangler$ShadowMethodPlan.run(ShadowWrangler.java:455) at android.app.Activity.setContentView(Activity.java) at com.example.testrobocustomfont.MainActivity.onCreate(MainActivity.java:12) at com.example.testrobocustomfont.MainActivityTest.setUp(MainActivityTest.java:28) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) ... 22 more 

请你们指点我正确的方向? 我没有想法。 谢谢。

问题:

发生这个问题是因为gradle以不同的方式合并项目依赖项(例如: compile project(':lib-custom') )和外部依赖项(例如: compile 'lib.package:name:1.1.0' )。 合并后,应用程序具有R.java文件与所有资源字段(颜色,ids,drawables,…)。 但合并子模块和外部依赖关系后生成的R.java文件看起来不同。

此问题仅适用于在子模块中具有自定义视图的项目 。 在外部依赖的情况下,还有另一个问题,可以很容易地解决。 在这里阅读依赖types。

对于项目依赖项,结果R.java文件包含所有资源标识符,但子模块中的标识符不等于其原始整数标识符:

 com.lib.custom.R.color.primary != com.main.project.R.color.primary 

对于外部依赖项,合并R.java文件只是来自所有外部依赖项的R.java文件的合并结果

 com.lib.custom.R.color.primary == com.main.project.R.color.primary 

解:

我find了两个可能的解决scheme:

  1. 尽可能将您的依赖关系从子模块转换为外部模块。 例如,viepager指标在maven.org存储库中有一个项目 – fr.avianey.com.viewpagerindicator:library。 但是这还不够 – 您需要将相关项目添加到project.properties文件到您的主sourceSet。 更多信息在这里

例:

 // add this dependency to your gradle file instead of project dependency compile 'fr.avianey.com.viewpagerindicator:library:2.4.1@aar' // add library dependencies for robolectric (now robolectric knows // about additional libraries to load resources) android.library.reference.1=../../../app/build/intermediates/exploded-aar/fr.avianey.com.viewpagerindicator/library/2.4.1 

你可以在这里查看这个解决scheme的差异

  1. 移动您的主应用程序下的所有自定义视图。 仅仅因为unit testing,将自定义视图移动到应用程序并不是一个好方法,但是这也将解决Error inflating class

我更喜欢第一个解决scheme,但是有时候不能将项目依赖性改为外部。

我也会向Robolectric团队报告这个问题。

PS我有关于这个问题的github项目 。

我在与使用它们的Activity相同的testing类中testing视图。 在这种情况下,我告诉Robolectric给这个Activity的一个实例,从那里我得到一个膨胀视图的实例:

 @Before public void setup(){ activity = Robolectric.buildActivity(MyActivity.class).create().get(); View view = LayoutInflater.from(activity).inflate(R.layout.myView, null); } @Test public void allElementsInViewProduct(){ assertNotNull(view.findViewById(R.id.view1)); assertNotNull(view.findViewById(R.id.view2)); assertNotNull(view.findViewById(R.id.view3)); } 

LE:我使用Robolectric 3.0,所以我不确定这是否适用于您。

mTestRoboActivityView = LayoutInflater.from(new Activity()).inflate(R.layout.test, null);

在这行代码中,您使用的“新的Activity()”意味着新的Activity的实例,不适用于您当前的Activity。 您可以通过在当前活动上传递实例来解决此问题。 像这样使用 –

 public class TestRoboActivityTest { private View mTestRoboActivityView; private Context mContext; public TestRoboActivityTest(Context mContext){ this.mContext=mContext; } @Before public void setUp() throws Exception { mTestRoboActivityView = (LayoutInflater.from(mContext)).inflate(R.layout.test, null); } @After public void tearDown() throws Exception { mTestRoboActivityView = null; } @Test public void testSanity() throws Exception { Assert.assertNotNull(mTestRoboActivityView); }} 

我不确定上面的代码工作正常,但是用作参考,当前Activity的实例。 请参阅它可能会帮助你。

你不能在Roboelectric中膨胀视图,因为它不使用完整的android框架,而是模拟出所有的Android API。

您不应该使用旋转电机来testing实际的视图显示行为。 这是用于unit testing,只是为了testing你的业务逻辑,而不是查看绘图/显示等。为了达到这个目的,你可以编程创build视图对象,并模拟出需要android系统的某些部分(使用类似Mockito或Powermock的东西) 。 例如在roboelectic简单的视图testing:

 MyCustomView view = new MyCustomView(); assertNotNull(view.setSomeNo(2); assertTrue(2, view.getSomeNo()); 

另外,如果你想testing你的视图的外观或渲染效果,你应该使用functiontesting框架,比如在实际设备上运行的Espresso或者Robotium

Viewpager在哪里? 。 这是错误的,因为CirclePageIndicator想膨胀视图,但没有什么可膨胀的。 indicator.setViewPager(寻呼机);