Avant d'implémenter la base de données SQLite dans notre activité TodoListActivity, nous allons tester les différents appels CRUD et vérifier qu'ils fonctionnent correctement. Cela nous permettra également de valider nos interfaces DAO grâce à différents tests. :)
Créez une classe de test
Afin de commencer à tester les tables de notre base de données, nous allons nous concentrer sur la classe ItemDAO et valider la plupart de ses méthodes CRUD. Pour cela, nous créerons une classe appelée ItemDaoTest dans le dossier dédié à nos tests instrumentalisés, androidTest/, afin qu'ils soient exécutés à partir d'un périphérique Android (plutôt que sur la JVM).
Avant cela, nous devons installer une petite librairie issue d'Android, qui nous facilitera la mise en place de nos tests :
Extrait de build.gradle :
dependencies {
// TESTING
androidTestImplementation 'androidx.arch.core:core-testing:2.1.0'
}
Parfait ! Passons maintenant à la création d'une classe responsable des tests effectués sur ItemDAO. Pour cela, créez un dossier androidTest dans app/src. Puis ajoutez un dossier java. Enfin, créez le package com.openclassrooms.savemytrip.
Super ! Vous pouvez ajouter la classe ItemDaoTest ci-après.
Classe ItemDaoTest.java :
@RunWith(AndroidJUnit4.class)
public class ItemDaoTest {
// FOR DATA
private SaveMyTripDatabase database;
@Rule
public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
@Before
public void initDb() throws Exception {
this.database = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
SaveMyTripDatabase.class)
.allowMainThreadQueries()
.build();
}
@After
public void closeDb() throws Exception {
database.close();
}
}
Explications : Cette classe de test sera donc instrumentalisée et exécutée grâce à AndroidJUnitRunner via l'annotation @RunWith(AndroidJUnit4.class)
. Ce lanceur de test s'occupera de charger le package contenant l'ensemble de nos tests, le tout sur un périphérique Android pour les exécuter.
Nous avons ensuite défini une règle grâce à l'annotation @Rule
. Pour rappel, une règle nous permet de définir la manière dont les tests seront exécutés. Dans notre cas, nous avons utilisé ici la règle@InstantTaskExecutorRule
permettant de forcer l'exécution de chaque test de manière synchrone (donc sans les déporter dans un thread en background).
Puis, nous avons créé une méthode initDb()
qui va se charger de créer une instance de notre base de données, pour ensuite la placer dans la variable database
déclarée en haut de notre classe. Cette méthode sera appelée avant l'exécution de chaque test grâce à l'annotation @Before
.
Tiens, mais c'est bizarre, le builder pour générer notre classe Room est étrange... o_O
Ah ! Vous l'avez vu... :) En effet, pour faciliter les tests unitaires, Room nous fournit un builder appelé inMemoryDatabaseBuilder. Ce dernier permet de créer une instance de notre base de données directement en mémoire (et non dans un fichier sur un périphérique !). Pratique, non ?
Testez nos appels CRUD
Passons maintenant aux tests. Dans un premier temps, je vous laisse créer (dans votre package de test) une classe utilitaire nous permettant de lancer plus facilement des méthodes retournant des valeurs de type LiveData.
Classe LiveDataTestUtil.java :
public class LiveDataTestUtil {
public static <T> T getValue(final LiveData<T> liveData) throws InterruptedException {
final Object[] data = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
@Override
public void onChanged(@Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
latch.await(2, TimeUnit.SECONDS);
//noinspection unchecked
return (T) data[0];
}
}
Explications : Cette classe est fournie par Google pour vous aider à plus facilement créer des tests impliquant le type LiveData, et surtout à bloquer l'exécution du test tant que le résultat n'est pas retourné.
Testons dans un premier temps l'ajout et la récupération d'un nouvel utilisateur dans notre base SQLite.
Extrait de ItemDaoTest :
@RunWith(AndroidJUnit4.class)
public class ItemDaoTest {
// DATA SET FOR TEST
private static long USER_ID = 1;
private static User USER_DEMO = new User(USER_ID, "Philippe", "https://www.google.fr, ");
@Test
public void insertAndGetUser() throws InterruptedException {
// BEFORE : Adding a new user
this.database.userDao().createUser(USER_DEMO);
// TEST
User user = LiveDataTestUtil.getValue(this.database.userDao().getUser(USER_ID));
assertTrue(user.getUsername().equals(USER_DEMO.getUsername()) && user.getId() == USER_ID);
}
}
Explications : Avant de créer un test, nous déclarons et instancions un jeu de données statique que nous serons susceptibles de réutiliser dans nos différents tests. Ici nous créons simplement un utilisateur de démo. :)
Puis, nous créons un premier test grâce à l'annotation@Test
. Celui-ci va, dans un premier temps, insérer un nouvel utilisateur dans notre base de données (grâce à la méthode createUser
de notre DAO), pour ensuite le récupérer depuis cette même base de données grâce à la méthode getUser
.
Une fois que notre objet User
a bien été récupéré, nous pouvons tester grâce à la méthode assertTrue
s'il correspond bien à celui que nous avons précédemment enregistré. C'est aussi simple que cela !
Exécutez maintenant le test ! Félicitations, vous venez de réaliser votre premier test sur une base de données !
Cependant, pour le moment, nous avons testé uniquement l'ajout et la récupération de données sur la table User, mais pas encore sur la table Item... Allez, je suis sympa, je vous donne les tests finaux pour tester l'ensemble des méthodes CRUD de la table Item.
Extrait de ItemDaoTest :
public class ItemDaoTest {
private static Item NEW_ITEM_PLACE_TO_VISIT = new Item("Visite cet endroit de rêve !", 0, USER_ID
);
private static Item NEW_ITEM_IDEA = new Item("On pourrait faire du chien de traîneau ?", 1, USER_ID
);
private static Item NEW_ITEM_RESTAURANTS = new Item("Ce restaurant à l'air sympa", 2, USER_ID
);
@Test
public void getItemsWhenNoItemInserted() throws InterruptedException {
// TEST
List<Item> items = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID));
assertTrue(items.isEmpty());
}
@Test
public void insertAndGetItems() throws InterruptedException {
// BEFORE : Adding demo user & demo items
this.database.userDao().createUser(USER_DEMO);
this.database.itemDao().insertItem(NEW_ITEM_PLACE_TO_VISIT);
this.database.itemDao().insertItem(NEW_ITEM_IDEA);
this.database.itemDao().insertItem(NEW_ITEM_RESTAURANTS);
// TEST
List<Item> items = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID));
assertTrue(items.size() == 3);
}
@Test
public void insertAndUpdateItem() throws InterruptedException {
// BEFORE : Adding demo user & demo items. Next, update item added & re-save it
this.database.userDao().createUser(USER_DEMO);
this.database.itemDao().insertItem(NEW_ITEM_PLACE_TO_VISIT);
Item itemAdded = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID)).get(0);
itemAdded.setSelected(true);
this.database.itemDao().updateItem(itemAdded);
//TEST
List<Item> items = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID));
assertTrue(items.size() == 1 && items.get(0).getSelected());
}
@Test
public void insertAndDeleteItem() throws InterruptedException {
// BEFORE : Adding demo user & demo item. Next, get the item added & delete it.
this.database.userDao().createUser(USER_DEMO);
this.database.itemDao().insertItem(NEW_ITEM_PLACE_TO_VISIT);
Item itemAdded = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID)).get(0);
this.database.itemDao().deleteItem(itemAdded.getId());
//TEST
List<Item> items = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID));
assertTrue(items.isEmpty());
}
}
Explications : Ces tests sont censés être assez facilement compréhensibles et lisibles... :) Je ne vais donc pas les expliquer en détail. Sachez simplement que nous reprenons l'ensemble des méthodes de notre interface ItemDao
et nous vérifions qu’elles retournent bien les informations qu'elles devraient retourner.
En résumé
Il est assez simple de tester notre base de données avec les différents outils mis à notre disposition.
Nous devons ouvrir notre base de données avant notre test avec l’annotation
@Before
.Nous devons fermer notre base de données après le test avec l’annotation
@After
.
Notre base de données est prête et fonctionne correctement grâce à nos tests !
Dès la prochaine partie de ce cours, nous allons voir comment intégrer les appels à notre base de données directement depuis notre contrôleur TodoListActivity, de la manière la plus propre possible, grâce à l'Architecture Components...