web-dev-qa-db-ja.com

Robolectricを使用したカスタムビューのテスト

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ファイル。\ res\layout\test.xml行#-1(申し訳ありませんが、まだ実装されていません):クラスcom.viewpagerindicator.CirclePageIndicatorの膨張エラー

かっこいいので、カスタムビューはまだサポートされていないようです。 website でサンプルのRobolectricプロジェクトをチェックすると、1つの解決策は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ファイル。\ res\layout\test.xml行#-1(申し訳ありませんが、まだ実装されていません):クラスin.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ファイル。\ res\layout\test.xml行#-1(申し訳ありませんが、まだ実装されていません):クラスin.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
_

正しい方向を教えていただけますか?私はアイデアがありません。ありがとう。

80
Tadej

ビューを使用するアクティビティを使用して、同じテストクラスでビューをテストします。この場合、Robolectricにそのアクティビティのインスタンスを提供するように指示し、それから膨らんだビューのインスタンスを取得します。

@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を使用しているので、これがあなたに当てはまるかどうかわかりません。

3
georger

問題:

この問題は、gradleがプロジェクトの依存関係(例:compile project(':lib-custom'))と外部の依存関係(例:compile 'lib.package:name:1.1.0')を異なる方法でマージするために発生します。依存関係がマージされた後、アプリにはすべてのリソースフィールド(色、ID、ドロウアブルなど)を含むR.Javaファイルがあります。ただし、生成された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

解決:

私は2つの可能な解決策を見つけました:

  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

このソリューションの差分を確認できます here

  1. すべてのカスタムビューをメインアプリの下に移動します。単体テストのためだけにカスタムビューをアプリに移動するのは良い方法ではありませんが、これはError inflating classの問題も修正します。

私は最初の解決策を好みますが、プロジェクトの依存関係を外部に変更することはできません。

また、この問題についてRobolectricチームに報告します。

追伸 githubのプロジェクト この問題に関連しています。

3
Oleksandr

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

このコード行で使用した「new 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);
}}

上記のコードが正常に機能するかどうかはわかりませんが、現在のアクティビティのインスタンスを参照するために使用します。それがあなたを助けるかもしれないことを参照してください。

Roboelectricでは、完全なAndroidフレームワークを使用せず、代わりにすべてのAndroid APIをモックアウトするため、Roboelectricでビューを膨らませることはできません。

実際のビュー表示動作をテストするためにロボエレクトリックを使用しないでください。単体テストに使用し、ビジネスロジックをテストするだけで、図面や表示などを表示することはできません。それを実現するには、プログラムでビューオブジェクトを作成し、Androidシステム(MockitoまたはPowermockのようなものを使用します。例えば、roboelecticでの簡単なビューテストの場合:

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

また、ビューの外観やレンダリングなどのレンダリングをテストする場合は、実際のデバイスで実行されるEspressoまたはRobotiumなどの機能テストフレームワークを使用する必要があります。

0
AmeyaB