web-dev-qa-db-fra.com

Test de vues personnalisées avec Robolectric

J'essaie d'exécuter des tests unitaires avec Robolectric 2.1.1 et je ne parviens pas à gonfler les dispositions personnalisées (par exemple, les classes ViewPagerIndicator). Supposons que ceci soit ma mise en page:

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

Considérez ceci comme ma classe de test:

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

L'exécution des résultats du "test propre mvn" 

 Tests en erreur: 
 TestSanity (TestRoboActivityTest): fichier XML.\Res\layout\test.xml ligne # -1 (désolé, pas encore implémentée): Erreur lors de l’ajout de la classe com.viewpagerindicator.CirclePageIndicator 

Cool, il semble donc que les vues personnalisées ne soient pas encore prises en charge. Vérification de l'exemple de projet Robolectric sur leur site Web , Une solution pourrait être de gonfler la présentation à partir de 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);
    }
}

qui se traduit par: 

 Tests en erreur: 
 TestSanity (TestRoboActivityTest): fichier XML.\Res\layout\test.xml ligne # -1 (désolé, pas encore implémentée): Erreur lors de l’ajout de la classe com.viewpagerindicator.CirclePageIndicator.

Mon dernier recours était d'essayer d'utiliser des classes d'ombre: 

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

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

    // etc.
}

et en utilisant @Config(shadows = {CirclePageIndicatorShadow.class}). Cela a encore abouti à 

 Tests en erreur: 
 TestSanity (TestRoboActivityTest): fichier XML.\Res\layout\test.xml ligne # -1 (désolé, pas encore implémentée): Erreur lors de l’ajout de la classe com.viewpagerindicator.CirclePageIndicator.

Edit (Décembre 2014)

Veuillez noter que la chaîne de caractères suivante a été ajoutée plus tard par David Rabinowitz. Bien qu’ils soient liés, ce n’est pas le problème auquel j’étais confronté à l’époque.


Voici la trace de la pile:

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

Pourriez-vous me diriger dans la bonne direction? Je suis à court d'idées ... Merci.

79
Tadej

Problème:

Ce problème se produit car gradle fusionne les dépendances de projet (ex: compile project(':lib-custom')) et les dépendances externes (ex: compile 'lib.package:name:1.1.0') de manière différente. Après la fusion des dépendances, l'application contient le fichier R.Java avec tous les champs de ressources (couleurs, identifiants, éléments dessinables, ...). Mais le fichier R.Java généré est différent après la fusion des sous-modules et des dépendances externes. 

Ce problème existe uniquement avec les projets qui ont des vues personnalisées dans les sous-modules. En cas de dépendances externes, il existe un autre problème, qui peut être facilement résolu. Lisez à propos des types de dépendances ici .

Pour les dépendances du projet, le fichier résultat R.Java contient tous les identifiants de ressources, mais les identifiants du sous-module ne correspondent pas à leurs identifiants entiers d'origine:

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

Pour les dépendances externes, le fichier R.Java fusionné est simplement un résultat de la fusion des fichiers R.Java de toutes les dépendances externes

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

Solution:

J'ai trouvé deux solutions possibles:

  1. Convertissez vos dépendances de sous-module en externe si possible. Par exemple, pour indicateur viepager, il existe un élément dans le référentiel maven.org - fr.avianey.com.viewpagerindicator: library . Mais cela n’est toujours pas suffisant, vous devez ajouter un élément associé au fichier project.properties à votre sourceSet principal. Plus d'infos ici

Exemple:

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

Vous pouvez vérifier diff pour cette solution ici

  1. Déplacez toutes vos vues personnalisées sous votre application principale. Ce n'est pas une bonne approche de déplacer des vues personnalisées vers une application uniquement à cause des tests unitaires, mais cela résoudra également le problème avec Error inflating class.

Je préfère la première solution, mais il est parfois impossible de changer la dépendance du projet en externe.

Je vais également rendre compte de ce problème à l'équipe de Robolectric.

P.S. J'ai projet sur github lié à cette question.

3
Oleksandr

Je teste des vues dans la même classe de test avec l'activité qui les utilise. Dans ce cas, je dis à Robolectric de donner une instance de cette activité et à partir de là, je reçois une instance de la vue gonflée

@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: J'utilise Robolectric 3.0, donc je ne suis pas sûr que cela s'applique à vous.

2
georger

Vous ne pouvez pas gonfler les vues dans Roboelectric car celui-ci n'utilise pas l'intégralité de la structure Android, mais simule toutes les API Android.

Roboelectric ne doit pas être utilisé pour tester le comportement réel de l'affichage des vues . Il doit être utilisé pour les tests unitaires et uniquement pour tester votre logique métier et non pour visualiser des dessins/affichages, etc. certaines parties nécessitant le système Android (utilisez quelque chose comme Mockito ou Powermock ) . par exemple, des tests de vue simples en mode roboélectique:

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

De même, si vous souhaitez tester le rendu de l'apparence ou du rendu de votre vue, vous devez utiliser des frameworks de tests fonctionnels tels que Espresso ou Robotium qui s'exécutent sur un périphérique réel.

0
AmeyaB