web-dev-qa-db-fra.com

Android Test d'instrumentation - Problèmes de thread d'interface utilisateur

J'essaie d'écrire un test d'instrumentation pour mon Android.

Je rencontre des problèmes de filetage étranges et je n'arrive pas à trouver de solution.

Mon test d'origine:

@RunWith(AndroidJUnit4.class)
public class WorkOrderDetailsTest {

    @Rule
    public ActivityTestRule<WorkOrderDetails> activityRule = new ActivityTestRule<>(WorkOrderDetails.class);

    @Test
    public void loadWorkOrder_displaysCorrectly() throws Exception {
        final WorkOrderDetails activity = activityRule.getActivity();

        WorkOrder workOrder = new WorkOrder();
        activity.updateDetails(workOrder);

        //Verify customer info is displayed
        onView(withId(R.id.customer_name))
                .check(matches(withText("John Smith")));
    }
}

Il en est résulté une

Android.view.ViewRootImpl $ CalledFromWrongThreadException: seul le thread d'origine qui a créé une hiérarchie de vues peut toucher ses vues.

...

com.kwtree.kwtree.workorder.WorkOrderDetails.updateDetails (WorkOrderDetails.Java:155)

La seule chose que fait la méthode updateDetails() est quelques appels setText().

Après quelques recherches, il semblait que l'ajout d'une annotation UiThreadTestRule et Android.support.test.annotation.UiThreadTest À mon test résoudrait le problème.

@ UiThreadTest:

@RunWith(AndroidJUnit4.class)
public class WorkOrderDetailsTest {

    //Note: This is new
    @Rule
    public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();

    @Rule
    public ActivityTestRule<WorkOrderDetails> activityRule = new ActivityTestRule<>(WorkOrderDetails.class);

    @Test
    @UiThreadTest //Note: This is new
    public void loadWorkOrder_displaysCorrectly() throws Exception {
        final WorkOrderDetails activity = activityRule.getActivity();

        WorkOrder workOrder = new WorkOrder();
        activity.updateDetails(workOrder);

        //Verify customer info is displayed
        onView(withId(R.id.customer_name))
                .check(matches(withText("John Smith")));
    }
}

Java.lang.IllegalStateException: la méthode ne peut pas être appelée sur le thread d'application principal (on: main)

(Remarque: toutes les méthodes de cette trace de pile ne sont pas mon code)

Cela semble me donner des résultats mitigés ... S'il doit être exécuté sur le thread d'origine qui a créé les vues mais ne peut pas s'exécuter sur le thread principal, sur quel thread doit-il être exécuté?

J'apprécierais vraiment toute aide ou suggestion!

24
Khalos

Ces tests d'instrumentation s'exécutent dans leur propre application. Cela signifie également qu'ils s'exécutent dans leur propre thread .

Vous devez considérer votre instrumentation comme quelque chose que vous installez à côté de votre application réelle, de sorte que vos interactions possibles sont "limitées".

Vous devez appeler toutes les méthodes de vue depuis le thread UIThread/main de l'application, donc appeler activity.updateDetails(workOrder); depuis votre thread d'instrumentation n'est pas pas le thread principal de l'application. C'est pourquoi l'exception est levée.

Vous pouvez simplement exécuter le code dont vous avez besoin pour tester sur votre thread principal comme vous le feriez si vous l'appeliez dans votre application à partir d'un thread différent en utilisant

activity.runOnUiThread(new Runnable() {
    public void run() {
        activity.updateDetails(workOrder);
    }
}

Avec cela en cours, votre test devrait fonctionner.

L'exception d'état illégal que vous recevez semble être due à votre interaction avec la règle. Les états documentation

Notez que les méthodes d'instrumentation ne peuvent pas être utilisées lorsque cette annotation est présente.

Si vous démarrez/commencez votre activité en @Before ça devrait aussi marcher.

23
David Medenjak

Vous pouvez exécuter une partie de votre test sur le thread d'interface utilisateur principal à l'aide de UiThreadTestRule.runOnUiThread(Runnable) :

@Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();

@Test
public void loadWorkOrder_displaysCorrectly() throws Exception {
    final WorkOrderDetails activity = activityRule.getActivity();

    uiThreadTestRule.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            WorkOrder workOrder = new WorkOrder();
            activity.updateDetails(workOrder);
        }
    });

    //Verify customer info is displayed
    onView(withId(R.id.customer_name))
            .check(matches(withText("John Smith")));
}

Dans la plupart des cas, il est plus simple d'annoter la méthode de test avec UiThreadTest, cependant, cela peut entraîner d'autres erreurs telles que Java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main).

Pour l'ex-République yougoslave de Macédoine, voici une citation du Javadoc de UiThreadTest :

Notez qu'en raison de la limitation actuelle de JUnit, les méthodes annotées avec Before et After seront également exécutées sur le thread d'interface utilisateur. Envisagez d'utiliser runOnUiThread (Runnable) si c'est un problème.

Veuillez noter que UiThreadTest (package Android.support.test.annotation) Mentionné ci-dessus est différent de ( UiThreadTest (package Android.test)).

16
Jeremy Kao

La réponse acceptée est maintenant obsolète

La façon la plus simple d'y parvenir est d'utiliser simplement UiThreadTest

import Android.support.test.annotation.UiThreadTest;

        @Test
        @UiThreadTest
        public void myTest() {
            // Set up conditions for test

            // Call the tested method
            activity.doSomethingWithAView()

            // Verify that the results are correct
        }
14
Greg Ennis

La réponse acceptée décrit parfaitement ce qui se passe.

En outre, au cas où quelqu'un serait curieux de savoir pourquoi les méthodes d'Espresso qui touchent l'interface utilisateur, par exemple perform(ViewActions ...) n'a pas besoin de faire de même, c'est simplement parce qu'ils finissent par le faire plus tard pour nous.

Si vous suivez perform(ViewActions ...) vous constaterez qu'il finit par faire ce qui suit (dans Android.support.test.espresso.ViewInteraction):

private void runSynchronouslyOnUiThread(Runnable action) {
    ...
    mainThreadExecutor.execute(uiTask);
    ...
}

Ce mainThreadExecutor est lui-même annoté avec @MainThread.

En d'autres termes, Espresso doit également jouer selon les mêmes règles décrites par David sur la réponse acceptée.

1
joakim