Testowanie niestandardowych widoków za pomocą Robolectric

82

Próbuję uruchomić testy jednostkowe w Robolectric 2.1.1 i nie mogę go zmusić do zawyżania niestandardowych układów (np. Klas ViewPagerIndicator). Załóżmy, że to jest mój układ:

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

Rozważ tę moją klasę testową:

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

Wykonanie `` mvn clean test '' skutkuje w

Testy błędne:
testSanity (TestRoboActivityTest): plik XML. \ res \ layout \ test.xml wiersz # -1 (przepraszam, jeszcze nie zaimplementowano): Błąd napełniania klasy com.viewpagerindicator.CirclePageIndicator

Fajnie, więc wygląda na to, że niestandardowe widoki nie są jeszcze obsługiwane. Sprawdzając przykładowy projekt Robolectric na swojej stronie internetowej , jednym z rozwiązań może być zawyżenie układu z 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);
    }
}

Co skutkuje w:

Testy błędne: 
testSanity (TestRoboActivityTest): plik XML. \ res \ layout \ test.xml wiersz # -1 (przepraszam, jeszcze nie zaimplementowano): Błąd napełniania klasy com.viewpagerindicator.CirclePageIndicator

Moją ostatnią deską ratunku było użycie klas cienia:

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

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

    // etc.
}

i używając @Config(shadows = {CirclePageIndicatorShadow.class}). To znowu spowodowało

Testy błędne: 
testSanity (TestRoboActivityTest): plik XML. \ res \ layout \ test.xml wiersz # -1 (przepraszam, jeszcze nie zaimplementowano): Błąd napełniania klasy com.viewpagerindicator.CirclePageIndicator

Edycja (grudzień 2014)

Zwróć uwagę, że następujący ślad stracktrace został dodany później przez Davida Rabinowitza. Chociaż jest to związane, nie jest to problem, z którym miałem wtedy do czynienia.


Oto ślad stosu:

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

Czy moglibyście wskazać mi właściwy kierunek? Nie mam pomysłów. Dzięki.

Tadej
źródło
1
Czy możesz opublikować pełny ślad stosu?
Corey D
Czy używasz niestandardowej czcionki, czy niestandardowego widoku, który używa niestandardowej czcionki? Myślę, że @joecks jest na dobrej drodze, jeśli chodzi o odpowiedź. Android nie może renderować niestandardowych czcionek w podglądzie (np. Gdy patrzysz na xml w Eclipse) i ten sam problem może występować tutaj. Jeśli kontrolujesz widok tekstu, spróbuj zawinąć kod, który pobiera styl za pomocąif (!isInEditMode())
karl
Czy możesz zobaczyć niestandardowy widok w podglądzie graficznym układu XML?
JstnPwll
1
Czy OP może opublikować swój ślad stosu, abyśmy mogli pomóc? Posiadanie śladu stosu innej osoby nie jest zbyt pomocne. Dzięki.
Caleb

Odpowiedzi:

4

Testuję widoki w tej samej klasie testowej z działaniem, które ich używa. W tym przypadku mówię Robolectric, aby podał przykład tego działania i na tej podstawie otrzymuję wystąpienie zawyżonego widoku:

@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: Używam Robolectric 3.0, więc nie jestem pewien, czy to dotyczy Ciebie.

George
źródło
3

Problem:

Ten problem występuje, ponieważ gradle w różny sposób scala zależności projektu (np .:) compile project(':lib-custom')i zależności zewnętrzne (np. :) compile 'lib.package:name:1.1.0'. Po scaleniu zależności aplikacja ma R.javaplik ze wszystkimi polami zasobów (kolory, identyfikatory, rysunki, ...). Ale wygenerowany R.javaplik wygląda inaczej po scaleniu modułów podrzędnych i zależności zewnętrznych.

Ten problem występuje tylko w przypadku projektów, które mają niestandardowe widoki w modułach podrzędnych . W przypadku zależności zewnętrznych pojawiają się inne problemy, które można łatwo naprawić. Przeczytaj o typach zależności tutaj .

W przypadku zależności projektu R.javaplik wynikowy zawiera wszystkie identyfikatory zasobów, ale identyfikatory z modułu podrzędnego nie są równe ich oryginalnym identyfikatorom całkowitym:

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

Dla zależności zewnętrznych scalony R.javaplik jest tylko wynikiem scalenia plików R.java ze wszystkich zależności zewnętrznych

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

Rozwiązanie:

Znalazłem dwa możliwe rozwiązania:

  1. Jeśli to możliwe, konwertuj zależności z modułu podrzędnego na moduł zewnętrzny. Na przykład dla viepager wskaźnik ma pozycję w repozytorium maven.org - fr.avianey.com.viewpagerindicator: library. Ale to wciąż za mało - musisz dodać powiązany element do pliku project.properties do głównego zestawu sourceSet. Więcej informacji tutaj

Przykład:

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

Możesz sprawdzić różnice dla tego rozwiązania tutaj

  1. Przenieś wszystkie swoje widoki niestandardowe do głównej aplikacji. Przenoszenie widoków niestandardowych do aplikacji tylko z powodu testów jednostkowych nie jest dobrym podejściem, ale rozwiąże to również problem z Error inflating class.

Wolę pierwsze rozwiązanie, ale czasami nie da się zmienić zależności projektu na zewnętrzną.

Zamierzam również zgłosić ten problem zespołowi Robolectric.

PS Mam projekt na githubie związany z tą kwestią.

Oleksandr
źródło
0

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

W tym wierszu kodu użyto „nowe działanie ()” oznacza wystąpienie nowego działania, które nie dotyczy bieżącego działania. Możesz rozwiązać ten problem, przekazując wystąpienie w bieżącym działaniu. Użyj w ten sposób

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

Nie jestem pewien, czy powyższy kod działa dobrze, ale użyj jako odniesienia, wystąpienia bieżącego działania. Skorzystaj z pomocy.

Vijay Pal Vishwakarma
źródło
0

Nie można zawyżać liczby wyświetleń w Roboelectric, ponieważ nie używa on pełnej struktury Androida, ale zamiast tego wyśmiewa wszystkie interfejsy API systemu Android.

Nie należy używać roboelectric do testowania rzeczywistego zachowania widoku. Ma być używany do testów jednostkowych i tylko do testowania logiki biznesowej, a nie do przeglądania rysunków / wyświetlania itp. Aby to osiągnąć Możesz programowo tworzyć obiekty widoku i makietować niektóre części, które wymagają systemu Android (użyj czegoś takiego jak Mockito lub Powermock ) . np. prostego testowania widoku w roboelectic:

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

Ponadto, jeśli chcesz przetestować renderowanie wyglądu lub renderowania widoku itp., Powinieneś użyć funkcjonalnych struktur testowania, takich jak Espresso lub Robotium, które działają na rzeczywistym urządzeniu.

AmeyaB
źródło