Testen benutzerdefinierter Ansichten mit Robolectric

82

Ich versuche, Unit-Tests mit Robolectric 2.1.1 auszuführen, und kann keine benutzerdefinierten Layouts (z. B. ViewPagerIndicator-Klassen) aufblasen. Angenommen, dies ist mein Layout:

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

Betrachten Sie dies als meine Testklasse:

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

Das Ausführen von 'mvn clean test' führt zu

Fehlerhafte Tests:
testSanity (TestRoboActivityTest): XML-Datei. \ res \ layout \ test.xml Zeile # -1 (leider noch nicht implementiert): Fehler beim Aufblasen der Klasse com.viewpagerindicator.CirclePageIndicator

Cool, es scheint also, dass benutzerdefinierte Ansichten noch nicht unterstützt werden. Wenn Sie das Robolectric-Beispielprojekt auf der Website überprüfen , besteht eine Lösung darin, das Layout von LayoutInflater aufzublasen:

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

was in ... endet:

Fehlerhafte Tests: 
testSanity (TestRoboActivityTest): XML-Datei. \ res \ layout \ test.xml Zeile # -1 (leider noch nicht implementiert): Fehler beim Aufblasen der Klasse com.viewpagerindicator.CirclePageIndicator

Mein letzter Ausweg war der Versuch, Schattenklassen zu verwenden:

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

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

    // etc.
}

und mit @Config(shadows = {CirclePageIndicatorShadow.class}). Dies führte erneut zu

Fehlerhafte Tests: 
testSanity (TestRoboActivityTest): XML-Datei. \ res \ layout \ test.xml Zeile # -1 (leider noch nicht implementiert): Fehler beim Aufblasen der Klasse com.viewpagerindicator.CirclePageIndicator

Bearbeiten (Dezember 2014)

Bitte beachten Sie, dass die folgende Spur später von David Rabinowitz hinzugefügt wurde. Obwohl verwandt, ist es nicht das Problem, mit dem ich zu der Zeit konfrontiert war.


Hier ist die Stapelverfolgung:

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

Könntet ihr mich bitte in die richtige Richtung weisen? Ich habe keine Ideen mehr. Vielen Dank.

Tadej
quelle
1
Können Sie eine vollständige Stapelverfolgung veröffentlichen?
Corey D
Verwenden Sie eine benutzerdefinierte Schriftart oder eine benutzerdefinierte Ansicht, die eine benutzerdefinierte Schriftart verwendet? Ich denke, @joecks ist mit ihrer Antwort auf dem richtigen Weg. Android kann keine benutzerdefinierten Schriftarten in einer Vorschau rendern (dh wenn Sie sich die XML in Eclipse ansehen), und das gleiche Problem kann hier auftreten. Wenn Sie die Textansicht steuern, versuchen Sie, den Code, der den Stil abruft, mitif (!isInEditMode())
karl
Können Sie die benutzerdefinierte Ansicht in der grafischen Vorschau des Layout-XML sehen?
JstnPwll
1
Könnte das OP seine Stapelverfolgung veröffentlichen, damit wir helfen können? Die Stapelverfolgung einer anderen Person zu haben, ist nicht sehr hilfreich. Vielen Dank.
Caleb

Antworten:

4

Ich teste Ansichten in derselben Testklasse mit der Aktivität, die sie verwendet. In diesem Fall fordere ich Robolectric auf, eine Instanz dieser Aktivität anzugeben, und daraus erhalte ich eine Instanz der aufgeblasenen Ansicht:

@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: Ich verwende Robolectric 3.0, daher bin ich mir nicht sicher, ob dies auf Sie zutrifft.

Georger
quelle
3

Problem:

Dieses Problem tritt auf, weil gradle Projektabhängigkeiten (z. B. :) compile project(':lib-custom')und externe Abhängigkeiten (z. B. :) compile 'lib.package:name:1.1.0'auf unterschiedliche Weise zusammenführt. Nach dem Zusammenführen der Abhängigkeiten verfügt die App über eine R.javaDatei mit allen Ressourcenfeldern (Farben, IDs, Zeichen, ...). Die generierte R.javaDatei sieht jedoch nach dem Zusammenführen von Submodulen und externen Abhängigkeiten anders aus.

Dieses Problem tritt nur bei Projekten auf, die benutzerdefinierte Ansichten in Submodulen haben . Bei externen Abhängigkeiten gibt es weitere Probleme, die leicht behoben werden können. Lesen Sie mehr über Abhängigkeiten Typen hier .

Für Projektabhängigkeiten R.javaenthält die Ergebnisdatei alle Ressourcenkennungen, aber die Kennungen aus dem Submodul entsprechen nicht ihren ursprünglichen Ganzzahlkennungen:

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

Für externe Abhängigkeiten zusammengeführte R.javaDatei nur ein Zusammenführungsergebnis von R.java-Dateien aus allen externen Abhängigkeiten

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

Lösung:

Ich habe zwei mögliche Lösungen gefunden:

  1. Konvertieren Sie Ihre Abhängigkeiten nach Möglichkeit vom Submodul zum externen. Zum Beispiel hat der Viepager-Indikator ein Element im Repository von maven.org - fr.avianey.com.viewpagerindicator: library. Dies reicht jedoch immer noch nicht aus. Sie müssen der Datei project.properties ein verwandtes Element zu Ihrem Hauptquellensatz hinzufügen. Mehr Infos hier

Beispiel:

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

Sie können diff für diese Lösung hier überprüfen

  1. Verschieben Sie alle Ihre benutzerdefinierten Ansichten unter Ihre Haupt-App. Es ist kein guter Ansatz, benutzerdefinierte Ansichten nur aufgrund von Komponententests in die App zu verschieben, aber dies behebt auch das Problem mit Error inflating class.

Ich bevorzuge die erste Lösung, aber es ist manchmal nicht möglich, die Projektabhängigkeit in eine externe zu ändern.

Ich werde dem Robolectric-Team auch über dieses Problem berichten.

PS Ich habe ein Projekt auf Github im Zusammenhang mit diesem Problem.

Oleksandr
quelle
0

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

In dieser Codezeile haben Sie 'new Activity ()' als Instanz einer neuen Aktivität verwendet, die nicht für Ihre aktuelle Aktivität gilt. Sie können dieses Problem beheben, indem Sie die Instanz für die aktuelle Aktivität übergeben. Verwenden Sie wie folgt:

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

Ich bin nicht sicher, ob der obige Code einwandfrei funktioniert, verwende ihn jedoch als Referenz für die aktuelle Aktivität. Verweisen Sie es kann Ihnen helfen.

Vijay Pal Vishwakarma
quelle
0

Sie können Ansichten in Roboelectric nicht aufblasen, da es nicht das vollständige Android-Framework verwendet, sondern alle Android-APIs verspottet.

Sie sollten roboelectric nicht verwenden, um das tatsächliche Verhalten der Ansichtsanzeige zu testen. Es soll für Komponententests und nur zum Testen Ihrer Geschäftslogik und nicht zum Anzeigen von Zeichnungen / Anzeigen usw. verwendet werden. Um dies zu erreichen, können Sie programmgesteuert Ansichtsobjekte erstellen und bestimmte Teile verspotten, die das Android-System benötigen (verwenden Sie etwas wie Mockito oder Powermock ). . zB von einfachen Ansichtstests in Roboelektrik:

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

Wenn Sie das Rendern testen möchten, wie Ihre Ansicht aussieht oder gerendert wird, sollten Sie Funktionstest-Frameworks wie Espresso oder Robotium verwenden, die auf einem tatsächlichen Gerät ausgeführt werden.

AmeyaB
quelle