• 12 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 26/09/2024

Testez vos DAO

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@Testpour 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éthoderunTestpour gérer facilement les coroutines. 

@RunWith(AndroidJUnit4.class)
public final class AnimalDaoTest
{

  //...

  @Test
  public void testShouldInsertAnimalIntoDatabaseSuccessfully() {

  }

}
@RunWith(AndroidJUnit4::class)
class AnimalDaoTest
{

  //…

  @Test
  fun testShouldInsertAnimalIntoDatabaseSuccessfully() = runTest {
    
  }

}

3. Insérez un animal dans la base de données à l’aide de la méthodeaddAnimalde 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  getAnimalByIddu 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 :

@RunWith(AndroidJUnit4.class)
public final class AnimalDaoTest
{

  //...

  @Test
  public void testShouldInsertAnimalIntoDatabaseSuccessfully() {AnimalType
    // Given
    final Animal animal = new Animal(
      1,
      AnimalType.CAT,
      "Mirabelle",
      12,
      120,
      1,
      null
    );

    // When
    database.animalDao().addAnimal(animal);

    // When
    final Animal insertedAnimal = database.animalDao().getAnimalById(1);
    assertEquals("Inserted and retrieved animals must have the same id", animal.id, insertedAnimal.id);
  }

}
@RunWith(AndroidJUnit4::class)
class AnimalDaoTest
{

  //…

  @Test
  fun testShouldInsertAnimalIntoDatabaseSuccessfully() = runTest {
    // Given
    val animal = Animal(
      id = 1,
      type = AnimalType.CAT,
      name = "Mirabelle",
      weight = 12,
      height = 120,
      age = 1
    )
    
    // When
    database.animalDao().addAnimal(animal)
    
    // Then
    val insertedAnimal = database.animalDao().getAnimalById(1)
    assertEquals("Inserted and retrieved animals must be equals", animal, insertedAnimal)
  }

}

Testez la mise à jour

Continuons les tests de l’interfaceAnimalDaoavec 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@Testpour 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éthoderunTestpour gérer facilement les coroutines.

Si vous utilisez Java : 

Si vous utilisez Kotlin : 

@RunWith(AndroidJUnit4.class)
public final class AnimalDaoTest
{

  //...

  @Test
  public void testShouldUpdateAnimalIntoDatabaseSuccessfully() {

  }

}
@RunWith(AndroidJUnit4::class)
class AnimalDaoTest
{

  //…

  @Test
  fun testShouldUpdateAnimalIntoDatabaseSuccessfully() = runTest {
    
  }

}

3. Insérez un animal dans la base de données à l’aide de la méthodeaddAnimalde 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éthodegetAnimalByIddu 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 :

@RunWith(AndroidJUnit4.class)
public final class AnimalDaoTest
{

  //...

  @Test
  public void testUpdateAnimalIntoDatabaseSuccessfully() {
    // Given
    final Animal animal = new Animal(
      1,
      AnimalType.CAT,
      "Mirabelle",
      12,
      120,
      1,
      null
    );

    database.animalDao().addAnimal(animal);

    // When
    final Animal newAnimal = new Animal(
      animal.id,
      animal.type,
      "Minou",
      animal.height,
      animal.weight,
      animal.age,
      animal.address
    );

    database.animalDao().addAnimal(newAnimal);

    // Then
    final Animal updatedAnimal = database.animalDao().getAnimalById(1);
    assertEquals("Inserted and updated animals must have the same id", animal.id, updatedAnimal.id);
  }

}
@RunWith(AndroidJUnit4::class)
class AnimalDaoTest
{

  //…

  @Test
  fun testShouldUpdateAnimalIntoDatabaseSuccessfully() = runTest {
   // Given 
   var animal = Animal(
      id = 1,
      type = AnimalType.CAT,
      name = "Mirabelle",
      weight = 12,
      height = 120,
      age = 1
    )
    
    database.animalDao().addAnimal(animal)
    
    // When
    animal = animal.copy(name = "Minou")
    
    database.animalDao().addAnimal(animal)
    
    // Then
    val updatedAnimal = database.animalDao().getAnimalById(1)
    assertEquals(“Inserted and updated animals must be equals”, animal, updatedAnimal)
  }

}

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 deleteAnimalde l’interfaceAnimalDao.

1. Dans la classeAnimalDaoTest, ajoutez une méthodetestShouldDeleteAnimalFromDatabaseSuccessfully.

Faites porter à la méthode l’annotation @Testpour 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éthoderunTestpour gérer facilement les coroutines.

Si vous utilisez Java : 

Si vous utilisez Kotlin :

@RunWith(AndroidJUnit4.class)
public final class AnimalDaoTest
{

  //...

  @Test
  public void testShouldDeleteAnimalFromDatabaseSuccessfully() {

  }

}
@RunWith(AndroidJUnit4::class)
class AnimalDaoTest
{

  //…

  @Test
  fun testShouldDeleteAnimalFromDatabaseSuccessfully() = runTest {
    
  }

}

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éthodedeleteAnimalde 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éthodegetAnimalByIdestnull.

Si vous utilisez Java :

Si vous utilisez Kotlin : 

@RunWith(AndroidJUnit4.class)
public final class AnimalDaoTest
{

  //...

  @Test
  public void testDeleteAnimalFromDatabaseSuccessfully() {
    // Given
    final Animal animal = new Animal(
      1,
      AnimalType.CAT,
      "Mirabelle",
      12,
      120,
      1,
      null
    );

    database.animalDao().addAnimal(animal);

    // When
    database.animalDao().deleteAnimal(animal);

    // Then
    final Animal deletedAnimal = database.animalDao().getAnimalById(1);
    assertNull("Retrieved animal must be null", deletedAnimal);
  }

}
@RunWith(AndroidJUnit4::class)
class AnimalDaoTest
{

  //…

  @Test
  fun testShouldDeleteAnimalFromDatabaseSuccessfully() = runTest {
    // Given
    val animal = Animal(
      id = 1,
      type = AnimalType.CAT,
      name = "Mirabelle",
      weight = 12,
      height = 120,
      age = 1
    )
    
    database.animalDao().addAnimal(animal)
    
    // When
    database.animalDao().deleteAnimal(animal)
    
    // Then
    val deletedAnimal = database.animalDao().getAnimalById(1)
    assertNull(“Retrieved aimal must be null”, deletedAnimal)
  }

}

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éthodegetAllAnimalset un pour la méthodegetAnimalById?

Effectivement, jusqu’alors, vous n’avez pas écrit un test dédié à la couverture de la méthode  getAnimalByIdde 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 typeLiveDataen 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  getAllAnimalsde l’interfaceAnimalDaorenvoie 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  LiveDatadans 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 getOrAwaitValuequi observe leLiveDatapassé 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éthodestestGetAnimalsShouldReturnEmptyListet  testGetAnimalsShouldReturnNonEmptyList.

5. N’oubliez pas de faire porter à ces méthodes l’annotation@Testpour 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  getAllAnimalsde la classeAnimalDaoet 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éthodetestGetAnimalsShouldReturnNonEmptyListl’objectif est d’insérer plusieurs animaux dans la base de données puis d’appeler la méthodegetAllAnimalsde 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éthodestestGetAnimalsShouldReturnEmptyListet  testGetAnimalsShouldReturnNonEmptyList.

4. N’oubliez pas de faire porter à ces méthodes l’annotation@Testpour 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éthoderunTestpour 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  testGetAllAnimalsShouldReturnEmptyListl’objectif est d’appeler directement la méthode  getAllAnimals de la classe AnimalDaoet 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éthodegetAllAnimalsde 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éthodesaddAnimaldans le cadre d’une insertion et de la méthodegetAllAnimalsde 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 lesFlow, il est possible d’utiliser la bibliothèque tierce Turbine.

  • En Java, pour tester lesLiveData, l’utilisation d’une classe helperLiveDataTestUtilet d’une règle via la classeInstantTaskExecutorRuleest 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 !

Exemple de certificat de réussite
Exemple de certificat de réussite