Vous y voilà enfin ! Après cette phase de préparation, vous pouvez écrire votre premier test. Puisqu’il faut bien débuter quelque part, c’est la méthodeaddAnimal
, dans un scénario d’insertion de données qui sera testée la première.
Testez l’insertion
1. Dans la classeAnimalDaoTest
, ajoutez une méthodetestShouldInsertAnimalIntoDatabaseSuccessfully
.
2. Faites porter à la méthode l’annotation@Test
pour que la méthode soit bien identifiée comme une méthode de test.
Si vous utilisez Java : | Si vous utilisez Kotlin, n’oubliez pas d’encapsuler l’exécution du futur test dans la méthode |
|
|
3. Insérez un animal dans la base de données à l’aide de la méthodeaddAnimal
de l’interface AnimalDao
.
4. Vérifiez que l’animal a bien été inséré dans la base de données en le récupérant via la méthode getAnimalById
du même DAO. Le test est valide si l’animal inséré dans la base de données et l’animal récupéré de la base de données sont identiques.
Si vous utilisez Java : | Si vous utilisez Kotlin : |
|
|
Testez la mise à jour
Continuons les tests de l’interfaceAnimalDao
avec le test de la méthodeaddAnimal
, cette fois-ci, dans un scénario de mise à jour de données.
1. Dans la classeAnimalDaoTest
, ajoutez une méthodetestShouldUpdateAnimalIntoDatabaseSuccessfully
.
2. Faites porter à la méthode l’annotation@Test
pour que la méthode soit bien identifiée comme une méthode de test.
Si vous utilisez le langage de programmation Kotlin, n’oubliez pas d’encapsuler l’exécution du futur test dans la méthoderunTest
pour gérer facilement les coroutines.
Si vous utilisez Java : | Si vous utilisez Kotlin : |
|
|
3. Insérez un animal dans la base de données à l’aide de la méthodeaddAnimal
de l’interface AnimalDao
.
4. Changez la valeur d’un attribut, par exemple sa race, puis mettez-le à jour dans la base de données. Vérifiez ensuite que la mise à jour a bien été répercutée dans celle-ci en récupérant l’animal via la méthodegetAnimalById
du même DAO. Encore une fois, le test est valide uniquement si l’animal inséré puis mis à jour dans la base de données et l’animal récupéré de la base de données sont identiques.
Si vous utilisez Java : | Si vous utilisez Kotlin : |
|
|
Testez la suppression
Après les opérations d’insertion et de suppression, c’est à l’opération de suppression d’être testée ! Focalisons-nous sur la méthode deleteAnimal
de l’interfaceAnimalDao
.
1. Dans la classeAnimalDaoTest
, ajoutez une méthodetestShouldDeleteAnimalFromDatabaseSuccessfully
.
Faites porter à la méthode l’annotation @Test
pour que la méthode soit bien identifiée comme une méthode de test.
Si vous utilisez le langage de programmation Kotlin, n’oubliez pas d’encapsuler l’exécution du futur test dans la méthoderunTest
pour gérer facilement les coroutines.
Si vous utilisez Java : | Si vous utilisez Kotlin : |
|
|
3. Insérez un animal dans la base de données à l’aide de la méthodeaddAnimal
de l’interface AnimalDao
.
4. Supprimez-le via la méthodedeleteAnimal
de la même classe. Finalement il convient d’essayer de le récupérer via la méthodegetAnimalById
de l’interfaceAnimalDao
. Le test est valide si le résultat renvoyé par la méthodegetAnimalById
estnull
.
Si vous utilisez Java : | Si vous utilisez Kotlin : |
|
|
Testez la collecte de données
Pour terminer la couverture de tests de l’interfaceAnimalDao
, une méthode doit encore être testée : la méthodegetAllAnimals
.
Ne doit-on pas encore écrire deux tests ? Un pour la méthodegetAllAnimals
et un pour la méthodegetAnimalById
?
Effectivement, jusqu’alors, vous n’avez pas écrit un test dédié à la couverture de la méthode getAnimalById
de l’interfaceAnimalDao
. Cependant, cette méthode est appelée et donc testée dans les 3 autres tests écrits jusqu’à maintenant. La méthode est déjà couverte par des tests.
Le test de la classegetAllAnimals
n’est pas aussi trivial à écrire que les tests des autres méthodes de l’interfaceAnimalDao
. En effet, elle fait intervenir des types pas toujours simples à manipuler dans le cadre de l’écriture de tests : le typeLiveData
en Java et le type Flow
en Kotlin. Mais ne vous inquiétez pas, vous aurez toutes les armes en main pour arriver au bout de ce nouveau défi !
Dans le cadre de ce test, nous n'allons pas créer une, mais bien deux méthodes de tests distinctes. La première méthode permettra de vérifier que lorsque la base de données est vide, la méthode getAllAnimals
de l’interfaceAnimalDao
renvoie bien une liste vide. La seconde méthode permettra de vérifier que lorsque la base de données n’est pas vide, elle renvoie le nombre d'éléments attendus.
Implémentez le test en Java
Ce qui fait de l'écriture de ces tests un défi est l’utilisation desLiveData
. Heureusement pour vous, Jose Alcérreca, un employé de Google a créé une classe permettant de faciliter la manipulation des LiveData
dans les tests.
Copiez-collez la classe suivante dans le projet PETiSoin.
/* Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
public class LiveDataTestUtil {
public static <T> T getOrAwaitValue(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);
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(2, TimeUnit.SECONDS)) {
throw new RuntimeException("LiveData value was never set.");
}
//noinspection unchecked
return (T) data[0];
}
}
Cette classe met à disposition une méthode statique getOrAwaitValue
qui observe leLiveData
passé en paramètre. Elle l’observe jusqu’à recevoir la nouvelle valeur. Si une valeur est déjà disponible, elle est collectée immédiatement. Dans le cas contraire, un garde-fou déclenche une exception si aucune valeur n’est collectée au bout de deux secondes.
L'utilisation des LiveData dans les tests ne s’arrête pas là. Aussi, vous devez mettre en place une règle permettant d’indiquer au framework Android que les opérations de collecte doivent se faire dans le thread principal.
Avant d’écrire cette règle, il convient d’ajouter une nouvelle dépendance au projet.
1. Ajoutez la dépendanceandroidx.arch.core:core-testing
dans le catalogue Gradle.
[versions]
coreTesting = "2.2.0"
[libraries]
androidx-core-testing = { group = "androidx.arch.core", name = "core-testing", version.ref = "coreTesting" }
2. Appliquez la dépendance au fichier "build.gradle.kts" du module app du projet Android Studio.
dependencies {
//…
androidTestImplementation(libs.androidx.core.testing)
}
3. Écrivez la règle permettant d’indiquer au framework Android que les opérations de collecte doivent se faire dans le thread principal. Pour ce faire, utilisez la classeInstantTaskExecutorRule
. Déclarez-lui une instance en tant qu’attribut de la classe de test et ajoutez-lui l’annotation@Rule
.
@RunWith(AndroidJUnit4.class)
public final class AnimalDaoTest
{
//...
@Rule
public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
}
4. L’écriture des tests peut enfin commencer ! À l’image des méthodes créées précédemment, vous pouvez créer deux méthodestestGetAnimalsShouldReturnEmptyList
et testGetAnimalsShouldReturnNonEmptyList
.
5. N’oubliez pas de faire porter à ces méthodes l’annotation@Test
pour qu’elles soient bien identifiées comme méthodes de test.
@RunWith(AndroidJUnit4.class)
public final class AnimalDaoTest
{
//...
@Test
public void testGetAllAnimalsShouldReturnEmptyList()
throws InterruptedException {
}
@Test
public void testGetAllAnimalsShouldReturnNonEmptyList()
throws InterruptedException {
}
}
Le contenu des méthodes est relativement simple. Dans la méthode testGetAllAnimalsShouldReturnEmptyList
l’objectif est d’appeler directement la méthode getAllAnimals
de la classeAnimalDao
et de vérifier que la liste reçue est vide.
@RunWith(AndroidJUnit4.class)
public final class AnimalDaoTest
{
//...
@Test
public void testGetAllAnimalsShouldReturnEmptyList()
throws InterruptedException {
final List<Animal> animals = LiveDataTestUtil.getOrAwaitValue(database.animalJavaDao().getAllAnimals());
assertTrue("Retrieved list of animals must be empty", animals.isEmpty());
}
}
Dans la méthodetestGetAnimalsShouldReturnNonEmptyList
l’objectif est d’insérer plusieurs animaux dans la base de données puis d’appeler la méthodegetAllAnimals
de la classe AnimalDao
. Il convient ensuite de vérifier que la liste reçue contient le nombre d'éléments attendus.
@RunWith(AndroidJUnit4.class)
public final class AnimalDaoTest
{
//...
@Test
public void testGetAllAnimalsShouldReturnNonEmptyList()
throws InterruptedException
{
// Given
final List<Animal> animals = new ArrayList<>();
animals.add(new Animal(
1,
AnimalType.CAT,
"Mirabelle",
12,
120,
1,
null
));
animals.add(new Animal(
2,
AnimalType.DOG,
"Heyden",
10,
100,
10,
null
));
for (final Animal animal : animals)
{
database.animalDao().addAnimal(animal);
}
// When
final List<Animal> results = LiveDataTestUtil.getOrAwaitValue(database.animalDao().getAllAnimals());
// Then
assertEquals("Inserted and retrieved list must have the same size", animals.size(), results.size());
}
}
Implémentez le test en Kotlin
Ajoutez une bibliothèque tierce au projet. Il s’agit de la bibliothèque Turbine, recommandée par Google et dont l’usage est très populaire chez les développeurs pour tester facilement les Flow.
1. Ajoutez la nouvelle dépendance au projet dans le catalogue Gradle.
[versions]
turbine = "1.1.0"
[libraries]
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
2. Appliquez la dépendance au fichier "build.gradle.kts" du module app du projet Android Studio.
dependencies {
//…
androidTestImplementation(libs.turbine)
}
3. Vous pouvez maintenant créer deux méthodestestGetAnimalsShouldReturnEmptyList
et testGetAnimalsShouldReturnNonEmptyList
.
4. N’oubliez pas de faire porter à ces méthodes l’annotation@Test
pour qu’elles soient bien identifiées comme méthodes de test.
5. N’oubliez pas également d’encapsuler l’exécution des futurs tests dans la méthoderunTest
pour gérer facilement les coroutines.
@RunWith(AndroidJUnit4::class)
class AnimalDaoTest
{
//…
@Test
fun testGetAllAnimalsShouldReturnEmptyList() = runTest {
}
@Test
fun testGetAllAnimalsShouldReturnNonEmptyList() = runTest {
}
}
Le contenu des méthodes est relativement simple. Dans la méthode testGetAllAnimalsShouldReturnEmptyList
l’objectif est d’appeler directement la méthode getAllAnimals
de la classe AnimalDao
et de vérifier que la liste reçue est vide.
@RunWith(AndroidJUnit4::class)
class AnimalDaoTest
{
//…
@Test
fun testGetAllAnimalsShouldReturnEmptyList() = runTest {
database.animalDao().getAllAnimals().test {
val animals = awaitItem()
assertTrue(“Retrieved list must be empty”, animals.isEmpty())
cancel()
}
}
}
Dans la méthodetestGetAnimalsShouldReturnNonEmptyList
l’objectif est d’insérer plusieurs animaux dans la base de données puis d’appeler la méthodegetAllAnimals
de la classeAnimalDao
. Il convient ensuite de vérifier que la liste reçue contient le nombre d'éléments attendus.
@RunWith(AndroidJUnit4::class)
class AnimalDaoTest
{
//…
@Test
fun testGetAllAnimalsShouldReturnNonEmptyList() = runTest {
// Given
val animals = listOf(
Animal(
id = 1,
type = AnimalType.CAT,
name = "Mirabelle",
weight = 12,
height = 120,
age = 1
),
Animal(
id = 2,
type = AnimalType.DOG,
name = "Heyden",
weight = 10,
height = 100,
age = 10
)
)
animals.forEach {
database.animalDao().addAnimal(it)
}
// When
database.animalDao().getAllAnimals().test {
// Then
val results = awaitItem()
assertEquals(“Inserted and retrieved list must have the same size”, animals.size, results.size)
cancel()
}
}
}
Voici une vidéo qui récapitule les principales étapes pour tester vos DAO. Dans le cadre de cette vidéo, nous allons nous limiter à la couverture de tests des méthodesaddAnimal
dans le cadre d’une insertion et de la méthodegetAllAnimals
de la classeAnimalDao
.
À vous de jouer
Contexte
Il est temps de tester le travail que vous avez effectué jusqu'à maintenant sur l’application PETiSoin et plus particulièrement sa rubrique “Santé”.
Consignes
Dans le projet, disponible sur GitHub (Java ou Kotlin), créez les tests permettant de couvrir le code de la classeVaccineDao
.
Livrables
Vous pouvez dupliquer le projet GitHub pour modifier le code source du projet et fournir un projet dont l’ensemble des tests passent.
En résumé
Le test d’insertion est valide si les données insérées dans la base de données et les données récupérées de la base de données sont identiques.
Le test de mise à jour est valide si les données mises à jour dans la base de données correspondent aux données attendues après la modification.
Le test de suppression est valide si les données supprimées de la base de données ne peuvent plus être récupérées.
Le test de collecte est valide si les données récupérées de la base de données correspondent exactement aux données attendues.
En Kotlin, pour tester les
Flow
, il est possible d’utiliser la bibliothèque tierce Turbine.En Java, pour tester les
LiveData
, l’utilisation d’une classe helperLiveDataTestUtil
et d’une règle via la classeInstantTaskExecutorRule
est nécessaire.
Vous avez testé vos DAO ce qui est une première bonne pratique à mettre en place quand on développe une application mobile. Une autre bonne pratique est l’utilisation de la base de données dans une architecture maintenable et évolutive. C’est ce que nous allons voir dans le chapitre suivant !