5 votes

Création d'une maquette de WifiManager pour les tests unitaires Android

J'essaie d'implémenter des tests unitaires pour quelques classes qui dépendent de WifiManager et des ScanResults retournés. Ce que j'aimerais faire, c'est être capable de contrôler les ScanResults que je reçois afin de tester une variété de conditions différentes.

Malheureusement, il m'a été assez difficile de réussir à simuler WifiManager (bien que je suppose que je peux passer ses références nulles de constructeur dans mon MockWifiManager). Ce ne sera que mon premier problème, car une fois que j'aurai un MockWifiManager avec lequel jouer (si cela fonctionne même !), je devrai réussir à créer mon test ScanResults qui n'a pas de constructeur public (Imaginez qu'il est créé par une usine quelque part).

Questions : Comme il n'a pas de constructeur public, puis-je même l'étendre ?

Est-ce que je m'y prends mal ? On me pose souvent des questions sur la façon d'effectuer une tâche spécifique, mais en réalité, on essaie de résoudre un autre problème de la mauvaise façon, peut-être est-ce ce que je fais ici ?

Je suis très novice en matière d'Android et le fait de devoir créer toutes ces fonctionnalités a été pour le moins éprouvant.

Merci pour vos contributions !

Éditer : J'ai également beaucoup de mal à instancier un MockWifiManager. Le constructeur du gestionnaire wifi attend un type IWifiManager qui ne semble pas exister dans le SDK Android.

9voto

Finglas Points 8645

Créer une abstraction autour de WifiManager. Utilisez-la pour vos simulations. Le mocking de choses que vous ne possédez pas est difficile et fragile. Si c'est fait correctement, vous devriez être capable de changer les internes, plus vous finirez avec une meilleure API mockable.

Pour vos tests, vous pouvez utiliser le gestionnaire comme bon vous semble. Pour la production, vous passerez dans une instance concrète.

En ce qui concerne votre point sur la modification de votre code juste pour le rendre testable, c'est incorrect. Tout d'abord, vous devez simuler les rôles et non les types, comme indiqué dans l'article ci-dessous. Google pour plus d'informations.

Deuxièmement, la création d'une abstraction autour d'un code tiers est une bonne pratique, comme l'indique le principe d'inversion des dépendances de SOLID. Vous devez toujours dépendre des abstractions plutôt que des implémentations concrètes, que vous fassiez des tests unitaires ou non.

http://www.objectmentor.com/resources/articles/dip.pdf http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

2voto

Stephan Points 4003

Vous pouvez essayer de créer les instances ScanResult en utilisant la réflexion pour accéder aux constructeurs privés. Le code pourrait ressembler à quelque chose comme ceci :

        try {
            Constructor<ScanResult> ctor = ScanResult.class.getDeclaredConstructor(null);
            ctor.setAccessible(true);
            ScanResult sr = ctor.newInstance(null);
            sr.BSSID = "foo";
            sr.SSID = "bar";
            // etc... 
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

Pour les autres méthodes de test, je convertis la plupart du temps les informations provenant d'instances comme ScanResult et je n'encapsule que les informations dont j'ai besoin dans mes propres objets. Je les envoie ensuite à la méthode qui fait le travail. Cela facilite les tests car vous pouvez facilement construire ces objets intermédiaires sans vous appuyer sur les véritables objets ScanResult.

0voto

Gregory Stein Points 305

Je me suis battu pendant un moment pour construire ScanResult objet. J'ai utilisé avec succès cette approche de réflexion ci-dessus.

Si quelqu'un cherche un moyen de cloner ScanResult (ou tout autre objet implémentant l Parcelable ) vous pouvez utiliser cette approche (je l'ai vérifié dans un test unitaire) :

@RunWith(RobolectricTestRunner.class)
@Config(manifest=Config.NONE)
public class MovingAverageQueueTests {
    @Test
    public void parcelTest() {
        Parcel parcel = Parcel.obtain();

        ScanResult sr = buildScanResult("01:02:03:04:05:06", 70);

        parcel.writeValue(sr);
        parcel.setDataPosition(0); // required after unmarshalling
        ScanResult clone = (ScanResult)parcel.readValue(ScanResult.class.getClassLoader());
        parcel.recycle();

        assertThat(clone.BSSID, is(equalTo(sr.BSSID)));
        assertThat(clone.level, is(equalTo(sr.level)));
        assertThat(clone, is(not(sameInstance(sr))));
    }

    private ScanResult buildScanResult(String mac, int level) {
        Constructor<ScanResult> ctor = null;
        ScanResult sr = null;

        try {
            ctor = ScanResult.class.getDeclaredConstructor(null);
            ctor.setAccessible(true);
            sr = ctor.newInstance(null);

            sr.BSSID = mac;
            sr.level = level;

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return sr;
    }
}

Et pour ce qui est des performances, ce contrôle naïf :

@Test
public void buildVsClonePerformanceTest() {
    ScanResult sr = null;

    long start = System.nanoTime();
    for (int i = 0; i < 1000000; i++) {
        sr = buildScanResult("01:02:03:04:05:06", 70);
    }
    long elapsedNanos = System.nanoTime() - start;

    LOGGER.info("buildScanResult: " + elapsedNanos);

    start = System.nanoTime();
    for (int i = 0; i < 1000000; i++) {
        sr = cloneScanResult(sr);
    }
    elapsedNanos = System.nanoTime() - start;

    LOGGER.info("cloneScanResult: " + elapsedNanos);
}

Il a montré ces résultats :

26 oct. 2016 3:25:19 PM com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest INFO : buildScanResult : 202072179 26 oct. 2016 3:25:21 PM com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest INFO : cloneScanResult : 2004391903

Le clonage de cette manière est donc 10 fois moins efficace que la création d'une instance, même avec réflexion. Je sais que ce test n'est pas robuste car des optimisations sont faites lors de la compilation... Cependant, un facteur de dix est difficile à atténuer. J'ai également testé 10K itérations et le facteur était même de 100 ! Juste pour votre information.

P.S. Sortir Parcel.obtain() et parcel.recycle de la boucle n'aide pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X