Android,
A Complete Course, From
Basics to Enterprise Edition
Android, A Complete Course, From Basics to Enterprise Edition
Cycle de vie d’un projet Android
1.1 Tester les appareils cibles de votre application
1.2 Mise en place du projet de test
1.4 Tester une ContentProvider
1.7 Maven et Hudson pour automatiser les tests
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 :
La
propriété intellectuelle du code appartient à :
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
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).
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.
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.
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.
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.
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)
}
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));
}
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();
}
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)
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);
}
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.
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();
}
}
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);
}
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);
}
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);
}
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();
}
}
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
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();
}
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.
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
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.
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.