Créer des tests unitaires locaux

Un test local s'exécute directement sur votre propre poste de travail, plutôt que sur un appareil ou un émulateur Android. Il utilise donc votre machine virtuelle Java (JVM) locale plutôt qu'un appareil Android pour exécuter les tests. Les tests locaux vous permettent d'évaluer plus rapidement la logique de votre application. Cependant, l'impossibilité d'interagir avec le framework Android limite les types de tests que vous pouvez exécuter.

Un test unitaire vérifie le comportement d'une petite section de code, l'unité testée. Pour ce faire, il exécute ce code et vérifie le résultat.

Les tests unitaires sont généralement simples, mais leur configuration peut poser problème lorsque l'unité testée n'est pas conçue pour être testée :

  • Le code que vous souhaitez vérifier doit être accessible à partir d'un test. Par exemple, vous ne pouvez pas tester directement une méthode privée. Vous testez plutôt la classe à l'aide de ses API publiques.
  • Pour exécuter des tests unitaires de manière isolée, les dépendances de l'unité testée doivent être remplacées par des composants que vous contrôlez, tels que des objets fictifs ou d'autres doubles de test. Cela est particulièrement problématique si votre code dépend du framework Android.

Pour en savoir plus sur les stratégies de tests unitaires courantes sur Android, consultez Éléments à tester.

Emplacement des tests locaux

Par défaut, les fichiers sources des tests unitaires locaux sont placés dans module-name/src/test/. Ce répertoire existe déjà lorsque vous créez un projet à l'aide d'Android Studio.

Ajouter des dépendances de test

Vous devez également configurer les dépendances de test de votre projet pour utiliser les API standards fournies par le framework de test JUnit.

Pour ce faire, ouvrez le fichier build.gradle du module de votre application et spécifiez les bibliothèques suivantes en tant que dépendances. Utilisez la fonction testImplementation pour indiquer qu'elles s'appliquent à l'ensemble de sources de test local, et non à l'application :

dependencies {
  // Required -- JUnit 4 framework
  testImplementation "junit:junit:$jUnitVersion"
  // Optional -- Robolectric environment
  testImplementation "androidx.test:core:$androidXTestVersion"
  // Optional -- Mockito framework
  testImplementation "org.mockito:mockito-core:$mockitoVersion"
  // Optional -- mockito-kotlin
  testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
  // Optional -- Mockk framework
  testImplementation "io.mockk:mockk:$mockkVersion"
}

Créer une classe de test unitaire local

Vous écrivez votre classe de test unitaire local en tant que classe de test JUnit 4.

Pour ce faire, créez une classe contenant une ou plusieurs méthodes de test, généralement dans module-name/src/test/. Une méthode de test commence par l'annotation @Test et contient le code permettant d'exercer et de vérifier un seul aspect du composant que vous souhaitez tester.

L'exemple suivant montre comment implémenter une classe de test unitaire local. La méthode de test emailValidator_correctEmailSimple_returnsTrue() tente de vérifier isValidEmail(),qui est une méthode de l'application. La fonction de test renvoie "true" si isValidEmail() renvoie également "true".

Kotlin

import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class EmailValidatorTest {
  @Test fun emailValidator_CorrectEmailSimple_ReturnsTrue() {
    assertTrue(EmailValidator.isValidEmail("name@email.com"))
  }

}

Java

import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

class EmailValidatorTest {
  @Test
  public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
    assertTrue(EmailValidator.isValidEmail("name@email.com"));
  }
}

Vous devez créer des tests lisibles qui évaluent si les composants de votre application renvoient les résultats attendus. Nous vous recommandons d'utiliser une bibliothèque d'assertions telle que junit.Assert, Hamcrest ou Truth. L'extrait de code ci-dessus est un exemple d'utilisation de junit.Assert.

Bibliothèque Android fictive

Lorsque vous exécutez des tests unitaires locaux, le plug-in Android Gradle inclut une bibliothèque contenant toutes les API du framework Android, correspondant à la version utilisée dans votre projet. La bibliothèque contient toutes les méthodes et classes publiques de ces API, mais le code qu'elles contiennent a été supprimé. Si l'une des méthodes est appelée, le test génère une exception.

Cela permet de créer des tests locaux lors du référencement de classes dans le framework Android, telles que Context. Plus important encore, il vous permet d'utiliser un framework de simulation avec des classes Android.

Simuler des dépendances Android

Un problème courant consiste à constater qu'une classe utilise une ressource de chaîne. Vous pouvez obtenir des ressources de chaîne en appelant la méthode getString() dans la classe Context. Toutefois, un test en local ne peut pas utiliser Context ni aucune de ses méthodes, car elles appartiennent au framework Android. Idéalement, l'appel à getString() serait déplacé hors de la classe, mais ce n'est pas toujours possible. La solution consiste à créer un simulacre ou un bouchon de Context qui renvoie toujours la même valeur lorsque sa méthode getString() est appelée.

Avec la bibliothèque Android simulable et les frameworks de simulation tels que Mockito ou MockK, vous pouvez programmer le comportement des simulations des classes Android dans vos tests unitaires.

Pour ajouter un objet fictif à votre test unitaire local à l'aide de Mockito, suivez ce modèle de programmation :

  1. Incluez la dépendance de la bibliothèque Mockito dans votre fichier build.gradle, comme décrit dans Configurer votre environnement de test.
  2. Au début de la définition de votre classe de test unitaire, ajoutez l'annotation @RunWith(MockitoJUnitRunner.class). Cette annotation indique au lanceur de test Mockito de vérifier que vous utilisez correctement le framework et simplifie l'initialisation de vos objets fictifs.
  3. Pour créer un objet fictif pour une dépendance Android, ajoutez l'annotation @Mock avant la déclaration du champ.
  4. Pour simuler le comportement de la dépendance, vous pouvez spécifier une condition et une valeur de retour lorsque la condition est remplie à l'aide des méthodes when() et thenReturn().

L'exemple suivant montre comment créer un test unitaire qui utilise un objet Context fictif en Kotlin créé avec Mockito-Kotlin.

import android.content.Context
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

private const val FAKE_STRING = "HELLO WORLD"

@RunWith(MockitoJUnitRunner::class)
class MockedContextTest {

  @Mock
  private lateinit var mockContext: Context

  @Test
  fun readStringFromContext_LocalizedString() {
    // Given a mocked Context injected into the object under test...
    val mockContext = mock<Context> {
        on { getString(R.string.name_label) } doReturn FAKE_STRING
    }

    val myObjectUnderTest = ClassUnderTest(mockContext)

    // ...when the string is returned from the object under test...
    val result: String = myObjectUnderTest.getName()

    // ...then the result should be the expected one.
    assertEquals(result, FAKE_STRING)
  }
}

Pour en savoir plus sur l'utilisation du framework Mockito, consultez la documentation de référence de l'API Mockito et la classe SharedPreferencesHelperTest dans l'exemple de code. Suivez également l'atelier de programmation sur les tests Android.

Erreur : "Méthode ... non simulée"

La bibliothèque Android fictive génère une exception si vous essayez d'accéder à l'une de ses méthodes avec le message Error: "Method ... not mocked.

Si les exceptions générées posent problème pour vos tests, vous pouvez modifier le comportement de sorte que les méthodes renvoient une valeur nulle ou zéro, selon le type renvoyé. Pour ce faire, ajoutez la configuration suivante dans le fichier build.gradle de premier niveau de votre projet dans Groovy :

android {
  ...
  testOptions {
    unitTests.returnDefaultValues = true
  }