Testing custom Views with Robolectric
我正在尝试使用Robolectric 2.1.1运行单元测试,但无法获取它来膨胀自定义布局(例如ViewPagerIndicator类)。
假设这是我的布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <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> |
考虑一下我的测试课程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @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干净测试"结果
1 2 3 | Tests in error: testSanity(TestRoboActivityTest): XML file .\ es\\layout\\test.xml line #-1 (sorry, not yet implemented): Error inflating class com.viewpagerindicator.CirclePageIndicator |
很酷,因此似乎尚不支持自定义视图。 在他们的网站上查看示例Robolectric项目,
一种解决方案是从LayoutInflater扩展布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @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); } } |
结果是:
1 2 3 | Tests in error: testSanity(TestRoboActivityTest): XML file .\ es\\layout\\test.xml line #-1 (sorry, not yet implemented): Error inflating class com.viewpagerindicator.CirclePageIndicator |
我的最后一招是尝试使用影子类:
1 2 3 4 5 6 7 8 9 10 11 | @Implements(CirclePageIndicator.class) public class CirclePageIndicatorShadow implements PageIndicator { @Override @Implementation public void setViewPager(ViewPager view) { // Stub } // etc. } |
并使用
1 2 3 | Tests in error: testSanity(TestRoboActivityTest): XML file .\ es\\layout\\test.xml line #-1 (sorry, not yet implemented): Error inflating class com.viewpagerindicator.CirclePageIndicator |
编辑(2014年12月)
请注意,以下stracktrace是David Rabinowitz后来添加的。 虽然相关,但这不是我当时面临的问题。
这是堆栈跟踪:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | android.view.InflateException: XML file .\ es\\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 |
你们能给我指出正确的方向吗? 我没主意了。
谢谢。
我在与使用视图的活动相同的测试类中测试视图。在这种情况下,我告诉Robolectric给出该Activity的一个实例,并从中得到一个膨胀视图的实例:
1 2 3 4 5 6 7 8 9 10 11 | @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,所以我不确定这是否适用于您。
问题:
发生此问题是因为gradle以不同的方式合并了项目依赖项(例如:
仅对于在子模块中具有自定义视图的项目,才存在此问题。如果存在外部依赖性,则存在另一个问题,可以轻松解决。在此处了解依赖项类型。
对于项目依赖项,结果
1 | com.lib.custom.R.color.primary != com.main.project.R.color.primary |
对于外部依赖项,合并的
1 | com.lib.custom.R.color.primary == com.main.project.R.color.primary |
解:
我发现了两种可能的解决方案:
但这还不够-您需要将project.properties文件中的相关项目添加到主sourceSet中。更多信息在这里
例:
1 2 3 4 5 6 | // 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 |
您可以在此处检查差异以获取此解决方案
我更喜欢第一个解决方案,但有时不可能将项目依赖关系更改为外部。
我还将向Robolectric团队报告此问题。
附言我在github上有与此问题相关的项目。
您不能在Roboelectric中膨胀视图,因为它没有使用完整的android框架,而是模拟了所有Android API。
您不应使用roboelectric测试实际的视图显示行为。
它用于单元测试,仅用于测试您的业务逻辑,而不用于查看绘图/显示等。要实现这一点,您可以以编程方式创建视图对象并模拟出需要android系统的某些部分(使用Mockito或Powermock之类的东西) 。
例如在机器人学中的简单视图测试:
1 2 3 | MyCustomView view = new MyCustomView(); assertNotNull(view.setSomeNo(2); assertTrue(2, view.getSomeNo()); |
另外,如果要测试视图外观或渲染方式等的渲染,则应使用在实际设备上运行的功能测试框架,例如Espresso或Robotium。
在这行代码中,您使用的" new Activity()"表示新Activity的实例,而不是当前Activity的实例。
您可以通过在当前活动上传递实例来解决此问题。
像这样使用-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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实例的参考。
推荐它可能对您有帮助。