Тестування власних подань за допомогою Robolectric


82

Я намагаюся запустити модульні тести з 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" в

Помилки тестів:
testSanity (TestRoboActivityTest): файл XML. \ res \ layout \ test.xml рядок # -1 (вибачте, ще не реалізовано): Помилка роздування класу com.viewpagerindicator.CirclePageIndicator

Класно, тому, схоже, власні подання ще не підтримуються. Перевіряючи зразок проекту 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. \ res \ layout \ test.xml рядок # -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. \ res \ layout \ test.xml рядок # -1 (вибачте, ще не реалізовано): Помилка роздування класу com.viewpagerindicator.CirclePageIndicator

Редагувати (грудень 2014 р.)

Зверніть увагу, що наступний штрих було додано пізніше Девідом Рабіновіцем. Хоча це стосується, це не проблема, з якою я стикався тоді.


Ось трасування стека:

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

Не могли б ви, хлопці, вказати мені в правильному напрямку? У мене не вистачає ідей. Дякую.


1
Чи можете ви опублікувати повний стек?
Кори Д

Ви використовуєте спеціальний шрифт або спеціальний вигляд, який використовує спеціальний шрифт? Я думаю, що @joecks зі своїми відповідями на правильному шляху. Android не може відображати власні шрифти в попередньому перегляді (тобто, коли ви переглядаєте xml в Eclipse), і та сама проблема може виникати і тут. Якщо ви керуєте текстовим поданням, спробуйте обернути код, який отримує стиль, за допомогоюif (!isInEditMode())
karl

Чи можете ви побачити власний вигляд у графічному попередньому перегляді макета xml?
JstnPwll


1
Чи може ОП опублікувати свій трас стека, щоб ми могли допомогти? Мати чужий стек стека не дуже корисно. Дякую.
Калеб,

Відповіді:


4

Я тестую подання в тому самому тестовому класі, що і Activity, що їх використовує. У цьому випадку я кажу 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

Проблема:

Ця проблема трапляється, оскільки gradle об'єднує залежності проекту (наприклад:) compile project(':lib-custom')та зовнішні залежності (наприклад :) compile 'lib.package:name:1.1.0'по-різному. Після об’єднання залежностей додаток має R.javaфайл із усіма полями ресурсів (кольори, ідентифікатори, малюнки, ...). Але згенерований R.javaфайл виглядає інакше після об’єднання підмодулів та зовнішніх залежностей.

Ця проблема існує лише з проектами, які мають власні подання в підмодулях . У випадку зовнішніх залежностей виникають інші проблеми, які можна легко виправити. Про типи залежностей читайте тут .

Результат для залежностей від проекту 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 до вашого основного набору джерел. Більше інформації тут

Приклад:

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

Ви можете перевірити різницю для цього рішення тут

  1. Перемістіть усі власні подання під основним додатком. Неправильний підхід переносити власні подання до програми лише через модульне тестування, але це також вирішить проблему з Error inflating class.

Я віддаю перевагу першому рішенню, але неможливо іноді змінити залежність проекту на зовнішню.

Я також збираюся доповісти про це питання команді Robolectric.

PS У мене є проект на github, пов'язаний з цією проблемою.


0

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

У цьому рядку коду, який ви використовували, 'new 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);
}}

Я не впевнений, що наведений вище код працює нормально, але використовується для довідки, наприклад, поточної активності. Зверніться, це може вам допомогти.


0

Ви не можете надувати подання в Roboelectric, оскільки він не використовує повну структуру Android, а замість цього висміює всі API Android.

Не слід використовувати roboelectric для перевірки фактичної поведінки відображення подання. Його слід використовувати для модульних тестів і просто для тестування вашої ділової логіки, а не для перегляду креслень / дисплеїв тощо. Щоб досягти цього, ви можете програмно створювати об’єкти перегляду та висміювати певні деталі, які потребують системи Android (використовуйте щось на зразок Mockito або Powermock ) . наприклад, просте тестування виду в робоелектриці:

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

Крім того, якщо ви хочете протестувати рендеринг того, як ваш вигляд виглядає чи відображає тощо, вам слід використовувати функціональні фреймворки тестування, такі як Espresso або Robotium, які працюють на реальному пристрої.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.