Android, A Complete Course, From Basics to Enterprise Edition

Les Tests.

Android, A Complete Course, From Basics to Enterprise Edition. 1

Les Tests. 1

Cycle de vie d’un projet Android. 1

1       Tester votre application. 1

1.1         Tester les appareils cibles de votre application. 2

1.2         Mise en place du projet de test. 3

1.3         Tester une activité. 3

1.4         Tester une ContentProvider. 8

1.5         Tester un service. 12

1.6         Monkey et MonkeyRunner. 13

1.7         Maven et Hudson pour automatiser les tests. 14

 Cet article est extraie du livre « Android, A Complete Course », disponible sur Android2ee.com.

Les exemples ou les programmes présents dans cet ouvrage sont fournis pour illustrer les descriptions théoriques. Ce code est libre de toute utilisation mais n'est pas distribuable.

La distribution du code est reservée au site :

http://android2ee.com.

La propriété intellectuelle du code appartient à :

Mathias Séguy

L’utilisation de ces codes est sous votre unique responsabilité et personne d’autre que vous ne pourra être tenu responsable des préjudices ou dommages de quelques natures que ce soit pouvant résulter de son utilisation.

Tous les noms de produits ou marques cités dans cet ouvrage sont des marques déposés par leurs propriétaires respectifs.

Publié par http://android2ee.com

Titre Original : Android, A Complete Course, From Basics to Enterprise Edition. Édition Française.

ISBN : 979-10-90388-00-0

Copyright © 2011 by Mathias Séguy

 

Aucune représentation ou reproduction, même partielle, autre que celles prévues à l’article L. 122-5 2° et 3° a) du code de la propriété intellectuelle ne peut être faite sans l’autorisation expresse de Mathias Seguy ou, le cas échéant, sans le respect des modalités prévues à l’article L. 122-10 dudit code

Cycle de vie d’un projet Android

1         Tester votre application

Le premier élément choquant dans la mise en place de test pour un projet Android est l’obligation de mettre en place un projet Eclipse-Android de Test différent du projet source, là où nous sommes habitués à avoir un package test dans notre projet source. Le corolaire est que nous avons un projet de test qui n’est autre qu’un projet Android avec ses layout, son manifeste… et qui est, au niveau du BuildPath, lié à notre projet initial.

L’intérêt d’une telle approche est de pouvoir mettre en place au niveau du projet de tests des ressources spécifiques à celui-ci qui génèreront la classe R de ressource Java pour le projet de test.

Vous pouvez bien sûr, ne pas vouloir respecter ce pattern et mettre vos classes de test dans un package test de votre projet initial. Il faut juste faire attention à deux trois détails : exclusion du package de test pour la compilation du projet et ajout de quelques variables dans le fichier manifest.xml. Nous présenterons aussi cette solution.

Dans le cadre des tests, il est intéressant d’aller lire les tutoriaux de Google ainsi que leur vision  sur la méthodologie de test à appliquer : http://developer.android.com/guide/topics/testing/index.html.

L’objectif des tests est de :

·         vous permettre de vous assurer que votre code marche,

·         de prévenir de la non régression de votre application.

Dans ce cadre, il vous faut tester les éléments suivants :

·         les méthodes de vos classes,

·         le cycle de vie de votre application,

·         sa capacité à répondre aux Intents que votre application est censée supporter,

·         les interfaces graphiques (dans le cadre d’une activité).

Et avoir une attention particulière pour les notions suivantes :

·         le changement de l’orientation, de la configuration de l’appareil,

·         l’arbre des choix d’appareils que vous supportez en fonction de vos ressources,

·         l’internationalisation,

·         la dépendance vers d’autres ressources (wifi, GPS,…)

·         l’utilisation des ressources (CPU, Batterie et mémoire)

De plus chaque composant, Activity, Service et ContentProvider possède une méthodologie de test spécifique.

Le framework sous-jacent à ces tests est JUnit 3, il vous faut donc toujours définir le constructeur vide et appeler le super constructeur avec ses paramètres attendus (sera présenté dans chaque paragraphe).

1.1       Tester les appareils cibles de votre application

Il n’y a pas encore d’automatisation du lancement de vos tests pour tel ou tel AVD ou pour tous les AVD que vous avez définis. Pour effectuer ces tests, il faut définir les différents AVD (Android Virtual Devices) qui sont la cible de votre application et la lancer pour chacun de ces AVD et tester à la main.

La première chose à faire et de définir l’arbre des appareils cibles de votre application. Pour cela il vous faut au moins prendre en compte les paramètres suivants :

·         La version Android et le target (Android ou Google)

·         La taille de l’écran (small, normal, large, xlarge)

·         La densité de l’écran (ldpi, mdpi, hdpi, xhdpi)

Ainsi si vous avez votre level minimum défini à 4 et votre level cible à 9, vous devriez définir 6*4*4 soit 96 AVD et tester pour chacun le comportement de votre application. Cette approche étant complètement délirante en terme de temps de test, l’approche la plus saine est donc d’avoir tester pour votre level cible les différentes tailles et densités de votre écran (ce qui fait tout de même 16 AVD différents) puis de fixer la taille de l’écran à normal, sa densité à mdpi et de faire varier les level (soit 5 AVD différents). Cette approche implique quand même la définition de 21 AVD et de tester votre application sur chacun d’entre eux.

Face à un tel constat, il n’y a plus qu’à mettre en place l’intégration continue avec Hudson qui permet le lancement des tests en rafales sur l’ensemble des AVD définis dans notre environnement de développement.

La seconde chose à vérifier est l’internationalisation de votre application. Si vous avez défini plusieurs langues, n’oubliez pas de les tester.

Enfin, si votre application possède deux modes, développement et production, ces deux modes sont aussi à tester.

1.2       Mise en place du projet de test

Il faut créer un projet Eclipse-Android de test en utilisant le wizard : New…->other->Android Test Project.

Il est possible que vous ayez quelques problèmes avec ce wizard, si vous n’y arrivez pas, créez un nouveau projet Android (New…->other->Android Project) et rajoutez dans votre fichier manifest.xml les lignes suivantes :

<application android:icon="@drawable/icon" android:label="@string/app_name">

    <uses-library android:name="android.test.runner" />

 </application>

<uses-sdk android:minSdkVersion="8" />

<instrumentation android:targetPackage="net.stinfoservices.android.tuto.test"

     android:name="android.test.InstrumentationTestRunner" />

Où vous spécifiez le package que vous souhaitez tester dans la balise android:targetPackage. Ensuite, il vous suffit de lier votre projet de test au projet que vous testez en passant par le build Path : Build Path-> Configure BuildPath…  et dans l’onglet Projet, rajouter le projet que vous souhaitez tester. En d’autres termes, un projet Android muni de ces quelques balises devient un projet de test.

Ainsi si vous souhaitez ne pas mettre en place de projet de tests et avoir vos tests au sein de votre projet principal, il suffit de créer un package test et de rajouter les balises ci-dessus à votre manifest.xml. Il vous faudra dans votre class path exclure ce package lors de la compilation.

1.3       Tester une activité

Maintenant que votre projet est créé, pour construire une classe de test il suffit de créer une classe qui étend ActivityInstrumentationTestCase2<?> où le paramètre générique n’est autre que l’activité qui sera testée par votre classe de test. Mettez votre classe dans un package qui correspond au package de l’activité testée suffixée par « .test ».

Ensuite avant de vous lancer dans l’écriture de vos tests à proprement parler, vous devez créer un constructeur vide qui appelle son super(nom du package tester, nom de l’activité testée) :

public SimpleActivityTest() {

                super("net.stinfoservices.android.tuto.test", TestASimpleActivityTuto.class);

}

Votre classe de test est opérationnelle. Un dernier détail, préfixer le nom des méthodes de tests qui seront lancées par « test », sinon, elles ne seront pas prises en compte par JUnit.

Enfin, vous pouvez annoter certains de vos tests par @UIThreadTest pour assurer que ceux-ci sont exécutés dans la thread de l’IHM.

1.3.1       Mise en place du setUp et du tearDown

Il est préconisé de surcharger les méthodes setUp et tearDown, la première met en place l’initialisation de l’environnement de test avant chaque test, la seconde restitue un environnement propre.

Typiquement, le setup initialise votre activité à tester et permet de lui envoyer des key-events :

/* * Sets up the test environment before each test. *

 * @see android.test.ActivityInstrumentationTestCase2#setUp()

 */

@Override

protected void setUp() throws Exception {

                super.setUp();

                // Must be done before the first call to getActivity() to send keys events to the Activity

                // under test

                setActivityInitialTouchMode(false);

                // Start the app under test by starting its main activity. The test runner already knows

                // which activity this is from the call to the super constructor, as mentioned previously.

                // The tests can now use instrumentation to directly access the main activity through

                // activity.

                activity = getActivity();

}

Si vous souhaitez by passer le lock du téléphone, il faut demander la permission dans le fichier manifest.xml de l’activité testée :

<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>

Et rajouter ces lignes de codes au sein de sa méthode onCreate :

//by pass the keyGuardManager

KeyguardManager  mKeyGuardManager = (KeyguardManager)

activity.getSystemService(Service.KEYGUARD_SERVICE);

KeyguardLock mLock = mKeyGuardManager.newKeyguardLock("TestASimpleActivityTuto");

mLock.disableKeyguard();

Par contre, vous devez quand vous livrez votre application enlever ces lignes de codes. Ce problème de lock se pose dans les tests car si votre émulateur n’est pas déjà lancé et déverrouillé vos tests seront bloqués au niveau de cet écran.

Si vous voulez, entre deux tests, réinitialiser vos données de manière systématique, il vous faut surcharger la méthode tearDown qui est appelée après chaque méthode de tests.

1.3.2       Tester les pré-conditions

Il paraît judicieux d’avoir une méthode de test dont l’objectif est de vérifier que l’initialisation de votre activité s’est bien déroulée, dans ce cadre vous pouvez implémenter la méthode de test de ces pré-conditions :

/***************************************************************************************/

/** Testing PreConditions ****************************************************************/

/*************************************************************************************/

public void testPreconditionsTesting() {

                // check your variables are not null

                assertTrue(((TestASimpleActivityTuto) activity).editText != null);

                assertTrue(((TestASimpleActivityTuto) activity).getTextViewEditStr() != null);

                assertTrue(((TestASimpleActivityTuto) activity).getTextViewRadioStr() != null);

                // and do others preconditions checks if needed (here there is no need)

}

1.3.3       Tester la consistance des données après une pause de l’activité

Lorsque le téléphone change son orientation, détecte un changement dans sa configuration, ou que votre activité passe au second plan, l’activité passe par les méthodes onPause et onResume qui détruisent puis reconstruisent l’application. La consistance de vos données doit être vérifiée ; vous ne devez pas perdre les données de l’utilisateur qu’il a déjà entré dans l’IHM.

Ce test est extrêmement important.

Dans cet exemple les lignes importantes sont celles (en gras) qui pause et relance l’application. Sinon, on sauvegarde l’état des éléments avant la pause et on regarde après la relance si ces états sont identiques. Voici cet exemple :

/**

 * Check when the application goes onPause and then onResume that the TextViewData, the

 * radioButton and the EditText state are restored

 */

public void testOnResumeDataConsistency() {

                // first set the data

 

                // Store the state of the different elements:

                // begin with the selected radioButton

                RadioGroup radioGroup = (RadioGroup) activity.findViewById(R.id.radioGroup);

                int selectedButtonId = radioGroup.getCheckedRadioButtonId();

                // Then with the TextViewRadio

                TextView textViewRadio = (TextView) activity.findViewById(R.id.textViewRadio);

                String textViewRadioStr = textViewRadio.getText().toString();

                // then with the TextViewEditText:

                TextView textViewEditText = (TextView) activity.findViewById(R.id.textViewEditText);

                String textViewEditTextStr = textViewEditText.getText().toString();

                // and finally with the EditText

                final EditText editText = (EditText) activity.findViewById(R.id.EditText);

                String editTextStr = editText.getText().toString();

 

                // Pause the activity and then resume it

                Instrumentation instr = this.getInstrumentation();

                instr.callActivityOnPause(activity);

                instr.callActivityOnResume(activity);

                // then retrieve the displayed elements and compare:

                assertTrue(radioGroup.getCheckedRadioButtonId() == selectedButtonId);

                assertTrue(textViewRadio.getText().toString().equals(textViewRadioStr));

                assertTrue(textViewEditText.getText().equals(textViewEditTextStr));

                assertTrue(editText.getText().toString().equals(editTextStr));

 

}

1.3.4       Tester la conscience des données après un arrêt de l’activité

Dans certains cas, une activité doit sauvegarder son état (ou l’état de certains de ses éléments) lorsqu’elle quitte et le restaurer à son redémarrage. Dans ce cas, vous devez vous assurer du bon fonctionnement de ce process. Les méthodes suivantes vous le permettent :

public void testOnStopAndCreateDataConsistency() {

                // if you want to close and relaunch your application just call:

                activity.finish();

                activity = this.getActivity();

}

1.3.5       Tester la dynamique fonctionnelle de votre IHM

Il est souvent nécessaire de tester la dynamique fonctionnelle de votre IHM. Pour cela, il peut être utile d’avoir des tests de comportement d’IHM qui envoie des input souris et clavier et qui valide que le comportement attendu est bien celui obtenu. Ces méthodes de manipulation d’IHM sont intuitives et facilement manipulables.

Dans l’exemple ci-dessous, lorsque l’utilisateur choisi un radio bouton, le TextView à ses cotés affiche son choix. Les tests doivent ainsi vérifier que le choix associé au bouton radio est conservé et que le texte affiché dans le TextView l’est aussi.

/**

 * Test if when a radio button is selected the TextViewRadio is updated according to that

 * selection

 */

public void testTextViewRadioUpdate() {

                // we had import net.stinfoservices.android.tuto.test.R

                //retrieve the graphic elements

                TextView textViewRadio = (TextView) activity.findViewById(R.id.textViewRadio);

                final RadioButton radio1 = (RadioButton) activity.findViewById(R.id.radio1);

                // change the radioButton selection within the RadioGroup

 

                // Request focus for the radioButton in the application under test

                // This code interacts with the app's View so it has to run on the app's thread

                // not the test's thread.

                // To do this, pass the necessary code to the application with runOnUiThread(). The

                // parameter is an anonymous Runnable object that contains the Java statements put in it by

                // its run() method.

                activity.runOnUiThread(new Runnable() {

                               public void run() {

                                               radio1.requestFocus();

                               }

                });

                // select the item (the third radio button

                this.sendKeys(KeyEvent.KEYCODE_ENTER);

                // Check the TextView is updated according to the selection:

                String textExpectedInTextViewRadio = "The selected radio button is Un";

                String textDisplayedInTextViewRadio = textViewRadio.getText().toString();

                assertEquals(textExpectedInTextViewRadio, textDisplayedInTextViewRadio);

                // now select another element

                // send 2 down arrow keys

                for (int i = 1; i <= 2; i++) {

                               this.sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);

                }

                // select the item (the third radio button

                this.sendKeys(KeyEvent.KEYCODE_ENTER);

                // Check the TextView is updated according to the selection:

                textExpectedInTextViewRadio = "The selected radio button is Trois";

                textDisplayedInTextViewRadio = textViewRadio.getText().toString();

                assertEquals(textExpectedInTextViewRadio, textDisplayedInTextViewRadio);

}

 

Les méthodes utiles de ce code sont les méthodes :

·         Qui permettent de récupérer les éléments graphiques (findViewById)

·         Qui permettent d’envoyer des évènements clavier (sendKeys)

1.3.6       Tester des interactions utilisateurs avec votre IHM

Il est aussi recommandé d’écrire des tests qui permettent de vérifier que la navigation au sein de votre IHM s’effectue bien.

Les méthodes présentées ici sont celles de la classe TouchUtils. Un exemple :

/**

 * Just do some random interactions with the screen

 */

public void testTouchUtils() {

                // needs to ask the permission INJECT_EVENTS

                final EditText editText = (EditText) activity.findViewById(R.id.EditText);

                // do a tap on the screen

                TouchUtils.tapView(this, editText);

                // do a click on the component(the test, the view to touch)

                TouchUtils.clickView(this, editText);

                // do a drag(the test, fromX, toX, fromY, toY,step number)

                // be sure to be in your activity, else you'll have a SecurityException

                // TouchUtils.drag(this,10.0f, 80.0f, 10.0f, 55.0f, 6);

                // another way to make drag (the test, the activity)

                TouchUtils.dragQuarterScreenDown(this, activity);

                // do a long click on a component (the test,the view to click)

                TouchUtils.longClickView(this, editText);

                // a menu appears, so select the first item to go back to the activity

                TouchUtils.clickView(this, editText);

                // then wite a long text

                this.sendKeys("H E L L O SPACE W O R L D SPACE I SPACE

A M SPACE U N D E R SPACE T E S T ");

                this.sendKeys(KeyEvent.KEYCODE_ENTER);

                this.sendKeys("H E L L O ENTER W O R L D ENTER I ENTER A M ENTER U N

D E R ENTER T E S T ");

                // do a scroll (the test, the activity, the view group)

                final ScrollView scroll = (ScrollView) activity.findViewById(R.id.ScrollView01);

                // send key event to move out from the edit text

                for (int i = 1; i <= 15; i++) {

                               this.sendKeys(KeyEvent.KEYCODE_DPAD_UP);

                }

                // Now try to move the scroll and obviously it doesn't work

                TouchUtils.scrollToBottom(this, activity, scroll);

                TouchUtils.scrollToTop(this, activity, scroll);

}

 

1.4       Tester une ContentProvider

Maintenant que votre projet est créé, pour construire une classe de test il suffit de créer une classe qui étend ProviderTestCase2<?> où le paramètre générique n’est autre que l’activité qui sera testée par votre classe de test. Mettez votre classe dans un package qui correspond au package de l’activité testée suffixée par « .test ».

Ensuite avant de vous lancer dans l’écriture de vos tests à proprement parler, vous devez créer un constructeur vide qui appelle son super(Classe à tester, nom de l’autorité du provider) :

/** The empty constructors */

public MyContentProviderTest() {

                super(MyContentProvider.class, MyContentProvider.AUTHORITY);

}

Votre classe de test est opérationnelle. Un dernier détail, préfixer le nom des méthodes de tests qui seront lancées par « test », sinon, elles ne seront pas prises en compte par JUnit.

1.4.1       Stratégie

La stratégie de test associée à un ContentProvider à mettre en place consiste à tester :

·         L’exposition de vos variables publiques,

·         La réponse de votre provider aux URI qui lui sont associées ainsi qu’à celles qui ne le sont pas (et dans ce dernier cas vérifier que votre provider renvoie bien IllegalArgumentException),

·         Les six méthodes publiques de votre provider,

·         La logique métier de votre provider (s’il en possède une).

Pour effectuer ces tests, vous devez toujours vous positionner en tant qu’utilisateur lambda de votre provider et donc n’accéder à celui-ci qu’au travers d’un ContentResolver (dans le cas des tests un mockContentResolver) et des attributs publics de votre provider.

Enfin, votre classe de tests possèdera des méthodes utiles qui seront appelées par vos méthodes de tests qui insèrent un ensemble de données, vident la base de données, ou bien vérifient qu’elle est vide. Par exemple :

/** * @return the number of inserted elements */

private int insertFamily() {

                ContentValues[] newLines = new ContentValues[4];

                newLines[0] = fillAConstantValue("Seguy", "Mathias", "brown", "brown", "36");

                newLines[1] = fillAConstantValue("Gargam", "Celine", "green", "blond", "36");

                newLines[2] = fillAConstantValue("Seguy", "Basile", "brown", "brown", "2");

                newLines[3] = fillAConstantValue("Doudou", "Lapin", "blue", "grey", "1");

                int insertedLine = contentResolver.bulkInsert(MyContentProvider.Constants.CONTENT_URI,

newLines);

                return insertedLine;

}

 

/** Assert the database is empty, usefull to ensure database state before testing anything */

private void assertDataBaseEmpty() {

                // Pour ramener un sous-ensemble :

                String[] projection = { MyContentProvider.Constants.KEY_COL_ID};

                Cursor cursor = contentResolver.query(MyContentProvider.Constants.CONTENT_URI, projection,

null, null, null);

                // then browse the result: if there is an element then the database is not empty

                if (cursor.moveToFirst()) {

                               fail();

                }

}

 

1.4.2       Mise en place

1.4.2.1       Mise en place de l’environnement de test

Dans votre classe de test, vous devez mettre en place votre environnement. L’environnement minimaliste, dans le cas d’un content provider associé à une base de données, consiste à :

·         Déclarer un MockContentProvider qui n’est autre qu’un ContentProvider dédié aux tests et qui place vos tests dans un environnement isolé de tests.

·         Surcharger la méthode setUp() pour initialiser votre Resolver et, si vous le souhaitez, peupler votre ContentProvider de données.

·         Surcharger la méthode tearDown() de manière à réinitialiser vos données entre deux tests (vider toutes vos données pour repartir d’une base vierge).

Ce qui donne :

/** The content resolver to use to communicate with the Content Provider under test */

MockContentResolver contentResolver;

 

/** The empty constructors */

public MyContentProviderTest() {

                super(MyContentProvider.class, MyContentProvider.AUTHORITY);

}

/*****************************************************************************************/

/** Instanciation **************************************************************************/

/*****************************************************************************************/

/* * (non-Javadoc) *  * @see android.test.ProviderTestCase2#setUp() */

@Override

protected void setUp() throws Exception {

                super.setUp();

                // instanciate the ContentResolver using a mock

                contentResolver = getMockContentResolver();

}

/* * (non-Javadoc) *  * @see android.test.AndroidTestCase#tearDown() */

@Override

protected void tearDown() throws Exception {

                super.tearDown();

                // and delete every one:

                contentResolver.delete(MyContentProvider.Constants.CONTENT_URI, null, null);

}

1.4.2.2       Tester les constantes publiques du Provider

Dans ce cas, il suffit de lister toutes les constantes exposées par votre provider et de vérifier qu’elles sont non nulles, mais aussi qu’elles sont exhaustives :

/*****************************************************************************************/

/** Testing constant ************************************************************************/

/****************************************************************************************/

/** * Test the constants */

public void testConstant() {

                assertTrue(MyContentProvider.AUTHORITY != null);

                assertTrue(MyContentProvider.PATH_TO_DATA != null);

                assertTrue(MyContentProvider.Constants.CONTENT_URI != null);

                assertTrue(MyContentProvider.Constants.MIME_COLLECTION != null);

                assertTrue(MyContentProvider.Constants.MIME_ITEM != null);

                assertTrue(MyContentProvider.Constants.DATABASE_NAME != null);

                assertTrue(MyContentProvider.Constants.KEY_COL_AGE != null);

                assertTrue(MyContentProvider.Constants.KEY_COL_EYES_COLOR != null);

                assertTrue(MyContentProvider.Constants.KEY_COL_FIRSTNAME != null);

                assertTrue(MyContentProvider.Constants.KEY_COL_HAIR_COLOR != null);

                assertTrue(MyContentProvider.Constants.KEY_COL_ID != null);

                assertTrue(MyContentProvider.Constants.KEY_COL_NAME != null);

                assertTrue(MyContentProvider.Constants.MY_TABLE != null);

                assertTrue(MyContentProvider.Constants.AGE_COLUMN != 0);

                assertTrue(MyContentProvider.Constants.DATABASE_VERSION != 0);

                assertTrue(MyContentProvider.Constants.EYES_COLOR_COLUMN != 0);

                assertTrue(MyContentProvider.Constants.FIRSTNAME_COLUMN != 0);

                assertTrue(MyContentProvider.Constants.HAIR_COLOR_COLUMN != 0);

                assertTrue(MyContentProvider.Constants.ID_COLUMN != 0);

                assertTrue(MyContentProvider.Constants.NAME_COLUMN != 0);

}

1.4.2.3       Tester les six méthodes publiques de votre provider

Pour tester ces méthodes, le plus simple est de mettre en place un jeu de données puis de le manipuler (insert, update, delete, query) et de vérifier que le résultat obtenu est le résultat attendu. Pour vérifier cela, il faut vérifier le retour de la méthode mais aussi l’état de la base de données.

Dans l’exemple ci-dessous, nous présentons l’idée de la structuration des tests de manière succincte et nous masquons les méthodes privées de cette classe. Vous pouvez retrouver l’exemple complet dans les sources de ce livre.

 

/*****************************************************************************************/

/** Testing kernel methods of the content Provider ***************************************/

/*****************************************************************************************/

/** * Test the OnCreate method */

public void testOnCreate() {

                ContentProviderClient contentProviderClient = contentResolver

                                               .acquireContentProviderClient(MyContentProvider.Constants.CONTENT_URI);

                assertTrue(contentProviderClient != null);

}

/** * Test the Query method */

public void testQuery() {

                // first be sure the database is empty

                assertDataBaseEmpty();

                // first insert an element

                insertAnElement("Doe", "John", "blue", "brown", "32");

                // Query elements with blue eyes and brown hair : Should have only one element

                queryBlueEyesBrownHair(1);

                // first insert another element

                insertAnElement("Eod", "Nhoj", "blue", "brown", "32");

                // Query elements with blue eyes and brown hair : Should have only one element

                queryBlueEyesBrownHair(2);

}

/** * Test the insert method */

public void testinsert() {

                // first be sure the database is empty

                assertDataBaseEmpty();

                // first insert an element

                insertAnElement("Doh", "John", "blue", "brown", "32");

                // second insert a bunch of elements

                int insertedLine = insertFamily();

                assertTrue(insertedLine == 4);

}

/** * Test the delete method */

public void testDelete() {

                // first be sure the database is empty

                assertDataBaseEmpty();

                // then insert elements

                int insertedLine = insertFamily();

                // then delete elements:

                // begin with only one:

                String where = MyContentProvider.Constants.KEY_COL_EYES_COLOR + "=? AND "

                                               + MyContentProvider.Constants.KEY_COL_HAIR_COLOR + "=?";

                String[] whereParam = { "blue", "grey" };

                int deletedLines = contentResolver.delete(MyContentProvider.Constants.CONTENT_URI, where,

whereParam);

                assertTrue(deletedLines == 1);

                // then delete 2 elements

                whereParam[0] = "brown";

                whereParam[1] = "brown";

                deletedLines = contentResolver.delete(MyContentProvider.Constants.CONTENT_URI, where,

whereParam);

                assertTrue(deletedLines == 2);

                // then delete every body:

                contentResolver.delete(MyContentProvider.Constants.CONTENT_URI, null, null);

                // then delete nobody

                whereParam[0] = "blond";

                whereParam[1] = "green";

                deletedLines = contentResolver.delete(MyContentProvider.Constants.CONTENT_URI, where,

whereParam);

                assertTrue(deletedLines == 0);

}

/** * test the update method */

public void testUpdate() {

                // first be sure the database is empty

                assertDataBaseEmpty();

                // then insert elements

                int insertedLine = insertFamily();

                // then update someone

                ContentValues constantValue = new ContentValues();

                constantValue.put(MyContentProvider.Constants.KEY_COL_NAME, "Seguy");

                String where = MyContentProvider.Constants.KEY_COL_EYES_COLOR + "=? AND "

                                               + MyContentProvider.Constants.KEY_COL_HAIR_COLOR + "=?";

                String[] whereParam = { "green", "blond" };

                int updateLines=contentResolver.update(MyContentProvider.Constants.CONTENT_URI, constantValue, where, whereParam);

                assertTrue(updateLines == 1);       

                //Ensure the database is in the expected state

                //----------------------------------------------------     

}

/** * Test the getType method */

public void testgetType() {

                String returnedType=contentResolver.getType(MyContentProvider.Constants.CONTENT_URI);

                assertEquals("vnd.android.cursor.dir/vnd.net.stinfoservices.human",returnedType);

                Uri itemUri=Uri.parse(MyContentProvider.Constants.CONTENT_URI+"/2");

                returnedType=contentResolver.getType(itemUri);

                assertEquals("vnd.android.cursor.item/vnd.net.stinfoservices.human",returnedType);                  

}

1.4.2.4       Tester les URI invalides

Pour cela, il vous suffit de mettre en place une méthode spécifique qui teste le retour de votre provider lorsqu’il reçoit une URI invalide :

/**

 * Test if an invalid Uri send an IllegalArgumentException

 */

public void testInvalidUri(){

                Uri invalidUri=Uri.parse(MyContentProvider.Constants.CONTENT_URI+"invalid");

                try{

                               contentResolver.delete(MyContentProvider.Constants.CONTENT_URI, null, null);

                }catch(IllegalArgumentException illExc){

                               //the waited result is here

                }

                catch(Exception ex){

                               fail();

                }

}

1.5       Tester un service

Le test d’un service s’effectue de manière analogue aux tests d’un provider ou d’une activité :

Maintenant que votre projet est créé, pour construire une classe de test il suffit de créer une classe qui étend ServiceTestCase<?> où le paramètre générique n’est autre que l’activité qui sera testée par votre classe de test. Mettez votre classe dans un package qui correspond au package de l’activité testée suffixée par « test ».

Ensuite avant de vous lancer dans l’écriture de vos tests à proprement parler, vous devez créer un constructeur vide qui appelle son super(Classe à tester) :

/** The empty constructors */

public MyServiceTest() {

                               super(MyService.class);

                }

Votre classe de test est opérationnelle. Un dernier détail, préfixer le nom des méthodes de tests qui seront lancées par « test », sinon, elles ne seront pas prises en compte par JUnit.

Il est de votre responsabilité de tester deux choses concernant un service :

·         Son lancement,

·         Ses méthodes métiers

 

1.5.1       Test du lancement de votre service

Les tests de lancement de votre service comportent deux méthodes, l’une teste le démarrage au travers de la méthode StartService, l’autre au travers d’un Binder.

 

/*****************************************************************************************/

/** Test  **************************************************************************/

/*****************************************************************************************/

/**

   * Test basic startup/shutdown of Service

   */

    @SmallTest

    public void testStartable() {

                //start using the startService Method

        Intent startIntent = new Intent();

        startIntent.setClass(getContext(), MyService.class);

        startService(startIntent);

        //then stop it

        shutdownService();

    }

 

    /**

     * Test binding to service

     */

    @MediumTest

    public void testBindable() {

                //start using the stopService Method

        Intent startIntent = new Intent();

        startIntent.setClass(getContext(), MyService.class);

        IBinder service = bindService(startIntent);

      //then stop it

        shutdownService();

    }

 

1.6       Monkey et MonkeyRunner

Enfin, un dernier outil de test est l’outil « Monkey » fournit par l’environnement de développement d’Android. Ce dernier lance un « singe » sur votre AVD qui clique un peu partout.

1.6.1       Utiliser le Monkey

Avant de lancer le Monkey, il vous faut lancer votre AVD et le délocker.

Pour lancer le Monkey, il vous suffit de le lancer en ligne de commande :

$ adb shell monkey [options] <event-count>

 

Sans options, le Monkey sera lancé en mode silencieux et enverra des évènements à tous les packages installés sur l’AVD ouvert.

Il est plus courant de spécifier le mode verbose, l’activité à tester ainsi que le nombre d’évènements :

$ adb shell monkey -p your.package.name -v 500

 

Vous pouvez trouver l’ensemble des options possibles sur le site Android :

http://developer.android.com/guide/developing/tools/monkey.html

1.6.2       MonkeyRunner : utiliser un programme Python pour lancer les tests

Il est possible, au moyen d’un programme python, de spécifier le comportement du Monkey. Plus précisément, il est possible d’installer une application Android (Activité, test, Service, ContentProvider), de la lancer, de lui envoyer des évènements et de prendre des copies d’écran. Il n’y a pas de relation entre le Monkey et le MonkeyRunner autre que celle de les considérer tous deux comme des éléments externes à l’AVD qui lance des traitements dessus.

L’intérêt fondamental du MonkeyRunner est l’automatisation des tests sur n émulateurs que vous pouvez définir et qui seront lancés et clos programmatiquement. Cela permet la mise en place de tests fonctionnels, de tests de non-régressions sur l’ensemble de vos appareils cibles.

L’application MonkeyRunner utilise Jython, une implémentation Java de Python. Pour l’installer dans votre environnement Eclipse, il suffit d’utiliser l’update site d’Eclipse et de télécharger le plugin JyDT (Jython Development Tool) avec les valeurs suivantes :

Nom : JyDT Update Site

URL :http://www.redrobinsoftware.net/jydt/updatesite

Vous pouvez vous référer au site http://www.redrobinsoftware.net/jydt/installation/installation.html pour un tutorial sur cette installation.

L’exemple donné par Google est le suivant :

# Imports the monkeyrunner modules used by this program
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice

# Connects to the current device, returning a MonkeyDevice object
device = MonkeyRunner.waitForConnection()

# Installs the Android package. Notice that this method returns a boolean, so you can test
# to see if the installation worked.
device.installPackage('myproject/bin/MyApplication.apk')

# Runs an activity in the application
device.startActivity(component='com.example.android.myapplication.MainActivity')

# Presses the Menu button
device.press('KEYCODE_MENU','DOWN_AND_UP')

# Takes a screenshot
result = device.takeSnapShot

# Writes the screenshot to a file
result.writeToFile('myproject/shot1.png','png')

 

Qui se lance comme suit :

monkeyrunner -plugin <program_filename> <program_options>

Où program_filename est le programme python que vous souhaitez lancer et program_options sont les arguments passés à ce programme.

1.7       Maven et Hudson pour automatiser les tests

Enfin, vous pouvez mettre en place Maven sur votre projet pour le construire automatiquement et spécifier les types d’émulateurs à utiliser et le lier à Hudson pour faire de l’intégration continue qui de fait lancera les tests sur les différents émulateurs définis.