【问题标题】:Testing custom Views with Robolectric使用 Robolectric 测试自定义视图
【发布时间】:2013-08-15 08:32:43
【问题描述】:

我正在尝试使用 Robolectric 2.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”会导致

测试错误: testSanity(TestRoboActivityTest): XML file .\res\layout\test.xml line #-1(抱歉,尚未实现):错误膨胀类 com.viewpagerindicator.CirclePageIndicator

很酷,所以似乎还不支持自定义视图。在他们的website 上检查示例 Robolectric 项目, 一种解决方案可能是从 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);
    }
}

导致:

测试错误: testSanity(TestRoboActivityTest): XML file .\res\layout\test.xml line #-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})。这又导致了

测试错误: testSanity(TestRoboActivityTest): XML file .\res\layout\test.xml line #-1(抱歉,尚未实现):错误膨胀类 com.viewpagerindicator.CirclePageIndicator

编辑(2014 年 12 月)

请注意,以下 stracktrace 是后来由 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

请你们指出我正确的方向吗?我没主意了。 谢谢。

【问题讨论】:

  • 你能发布完整的堆栈跟踪吗?
  • 您使用的是自定义字体,还是使用自定义字体的自定义视图?我认为@joecks 的答案是正确的。 Android 无法在预览中呈现自定义字体(即,当您在 Eclipse 中查看 xml 时),这里可能会出现同样的问题。如果您控制文本视图,请尝试使用if (!isInEditMode())包装检索样式的代码
  • 布局xml图形预览中能看到自定义视图吗?
  • OP 能否发布他/她的堆栈跟踪以便我们提供帮助?拥有别人的堆栈跟踪不是很有帮助。谢谢。

标签: android robolectric


【解决方案1】:

我在与使用它们的 Activity 相同的测试类中测试视图。在这种情况下,我告诉 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,所以我不确定这是否适用于你。

【讨论】:

    【解决方案2】:

    问题:

    出现此问题是因为 gradle 以不同的方式合并项目依赖项(例如:compile project(':lib-custom'))和外部依赖项(例如:compile 'lib.package:name:1.1.0')。合并依赖项后,应用程序具有 R.java 文件,其中包含所有资源字段(颜色、ID、可绘制对象,...)。但是合并子模块和外部依赖项后生成的R.java 文件看起来不同。

    此问题仅存在于子模块中具有自定义视图的项目。在外部依赖的情况下,还有另一个问题,可以很容易地修复。阅读依赖类型here

    对于项目依赖结果 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.viewpagerindicator:library。 但这仍然不够——您需要将相关项目添加到 project.properties 文件到您的主 sourceSet。更多信息here

    例子:

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

    你可以检查这个解决方案的差异here

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

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

    我还将向 Robolectric 团队报告此问题。

    附:我有与此问题相关的project on github

    【讨论】:

      【解决方案3】:

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

      我不确定上面的代码是否可以正常工作,但用作当前活动实例的参考。 参考一下可能对你有帮助。

      【讨论】:

        【解决方案4】:

        您不能在 Roboelectric 中扩充视图,因为它不使用完整的 android 框架,而是模拟了所有的 Android API。

        您不应使用 roboelectric 来测试实际的视图显示行为。 它用于单元测试,仅用于测试您的业务逻辑,而不是查看绘图/显示等。为此,您可以以编程方式创建视图对象并模拟需要 android 系统的某些部分(使用类似 Mockito或 Powermock)。 例如 roboelectic 中的简单视图测试:

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

        此外,如果您想测试视图的外观或渲染等,您应该使用在实际设备上运行的功能测试框架,例如 EspressoRobotium .

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-22
          • 1970-01-01
          • 1970-01-01
          • 2013-08-19
          • 2012-10-15
          相关资源
          最近更新 更多