用机器电学测试自定义视图

我正在尝试用 Robolectric2.1.1运行单元测试,但是我不能让它自定义布局(比如 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>

把这当作我的测试课吧:

@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”的结果是

Tests in error:
testSanity(TestRoboActivityTest): XML file .\res\layout\test.xml line #-1 (sorry, not yet implemented): Error inflating class com.viewpagerindicator.CirclePageIndicator

酷,看起来好像自定义视图还不支持, 一个解决方案可能是使用 Layout膨胀器来充气布局:

@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);
}
}

结果是:

Tests in error:
testSanity(TestRoboActivityTest): XML file .\res\layout\test.xml line #-1 (sorry, not yet implemented): Error inflating class com.viewpagerindicator.CirclePageIndicator

我的最后一招是尝试使用影子类:

@Implements(CirclePageIndicator.class)
public class CirclePageIndicatorShadow implements PageIndicator {


@Override
@Implementation
public void setViewPager(ViewPager view) {
// Stub
}


// etc.
}

和使用 @Config(shadows = {CirclePageIndicatorShadow.class})。这再次导致

Tests in error:
testSanity(TestRoboActivityTest): XML file .\res\layout\test.xml line #-1 (sorry, not yet implemented): Error inflating class com.viewpagerindicator.CirclePageIndicator

编辑(2014年12月)

请注意,下面的 stracktrace 是后来由 DavidRabinowitz 添加的。虽然相关,但这不是我当时面临的问题。


下面是堆栈跟踪:

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

你们能告诉我正确的方向吗? 我没主意了。 谢谢。

17752 次浏览

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

在这行代码中,您使用的“ new Activity ()”指的是新 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);
}}

我不确定上面的代码是否工作良好,但可以作为参考,当前活动的实例。 也许对你有帮助。

问题:

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

这个问题只存在于在子模块 中具有自定义视图的项目中。在外部依赖的情况下,还有另外一个问题,这个问题可以很容易地解决。阅读依赖类型 给你

对于项目依赖项,结果 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

解决方案:

我发现了两种可能的解决方案:

  1. 尽可能将您的依赖项从子模块转换为外部依赖项。例如 viepager 指示器在 maven.org 存储库中有一个条目-fr.avianey.com.viewpager: library。 但是这还不够-您需要将相关项添加到 project.properties 文件到您的主 source Set 中。更多信息 < a href = “ https://github.com/robolecric/robolecric/issues/1334”rel = “ nofollow”> 这里

例如:

// 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

您可以检查此解决方案的 diff 给你

  1. 将所有自定义视图移动到主应用程序下。仅仅因为单元测试而将自定义视图移动到应用程序并不是一个好的方法,但是这也会修复 Error inflating class的问题。

我更喜欢第一个解决方案,但它是不可能的,有时改变项目依赖外部。

我也将向机器电子团队报告这个问题。

另外,我有 Github 上的项目相关的问题。

你不能在 Roboelectric 夸大视图,因为它没有使用完整的 Android 框架,而是模仿了所有的 Android API。

您不应该使用机器人电气测试实际的视图显示行为。 它用于单元测试,只是测试您的业务逻辑,而不是查看绘图/显示等。为了实现这一点,您可以通过编程创建视图对象并模拟出需要 android 系统的某些部分(使用类似于 Mockito 或 Powermock的东西)。 例如,在机器人电子设备中进行简单视图测试:

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

此外,如果你想测试渲染你的视图看起来或渲染等,你应该使用功能测试框架,如 浓咖啡机器人运行在一个实际的设备上。

我用使用它们的 Activity 测试同一个测试类中的视图。在这个例子中,我告诉 Robolectic 给出一个 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: 我使用 Roboleectic 3.0,所以我不确定这是否适用于你。