Résoudre le Défi des Tests Unitaires Gradle avec `gradle.properties`
Publié le 13 July 2025
- 1. Le Problème : Isoler les Tests Unitaires d’un Plugin Gradle
- 2. La Solution : Simuler la Propriété avec
ExtraPropertiesExtension
- 2.1. Étape 1 : Comprendre ExtraPropertiesExtension
- 2.2. Étape 2 : Mise en Place du Test - Préparation
- 2.3. Étape 3 : Injection de la Propriété
- 2.4. Étape 4 : Diagramme de Flux de la Solution
- 2.5. Étape 5 : Comparaison Avant/Après
- 2.6. Étape 6 : Gestion des Cas de Test Multiples
- 2.7. Architecture Finale de la Solution
- 3. Les Avantages de cette Approche
- 4. Bonnes Pratiques et Conseils
- 5. Conclusion
1. Le Problème : Isoler les Tests Unitaires d’un Plugin Gradle
Lors de l’écriture de tests unitaires pour un plugin Gradle, un défi courant est de gérer les dépendances à la configuration du projet, comme les propriétés définies dans le fichier gradle.properties
. Dans notre cas, le plugin jbake.ghpages
devait lire une propriété site_config_path
pour fonctionner correctement. Le test unitaire pour la tâche initialize
devait vérifier le comportement du plugin en présence de cette propriété.
Le problème fondamental est que les tests unitaires, par conception, doivent être isolés. L’utilisation de ProjectBuilder
de Gradle crée une instance de Project
en mémoire, complètement déconnectée d’un véritable projet sur le système de fichiers. Par conséquent, cette instance de test ne lit pas automatiquement le fichier gradle.properties
et n’a donc pas connaissance de la propriété site_config_path
.
1.1. Architecture du Problème
Le diagramme suivant illustre la déconnexion entre l’environnement de test et le système de fichiers :
1.2. La Tentation d’une Fausse Bonne Idée
Une première approche pourrait être de créer un fichier gradle.properties
factice dans les ressources du test.
// src/test/resources/gradle.properties
site_config_path=src/jbake/settings/site.yml
Cependant, cette méthode est vouée à l’échec car ProjectBuilder
n’est pas conçu pour scanner le système de fichiers à la recherche de fichiers de configuration. Le test resterait isolé et ignorerait ce fichier.
2. La Solution : Simuler la Propriété avec ExtraPropertiesExtension
La solution élégante à ce problème n’est pas de lire le fichier, mais de simuler la présence de la propriété directement dans l’objet Project
de test. Gradle fournit un mécanisme puissant pour cela : les propriétés supplémentaires (Extra Properties).
2.1. Étape 1 : Comprendre ExtraPropertiesExtension
ExtraPropertiesExtension
est un conteneur clé-valeur attaché à chaque objet du modèle Gradle. Il permet d’ajouter des propriétés dynamiquement à un projet, une tâche, ou toute autre extension Gradle.
2.2. Étape 2 : Mise en Place du Test - Préparation
Commençons par créer la structure de base de notre test unitaire :
class JbakeGhPagesPluginTest {
@TempDir
lateinit var testProjectDir: File
@Test
fun `check initialize and config yaml file if not existing`() {
// Étape 1 : Créer un projet de test isolé
val project = ProjectBuilder.builder()
.withProjectDir(testProjectDir)
.build()
// Suite des étapes...
}
}
2.3. Étape 3 : Injection de la Propriété
Voici le processus détaillé pour injecter la propriété dans le projet de test :
@Test
fun `check initialize and config yaml file if not existing`() {
// Étape 1 : Créer un projet de test isolé en mémoire
val project = ProjectBuilder.builder()
.withProjectDir(testProjectDir)
.build()
// Étape 2 : Récupérer le gestionnaire de propriétés supplémentaires
val extra = project.extensions.getByType(ExtraPropertiesExtension::class.java)
// Étape 3 : Définir la propriété requise pour ce test
extra.set("site_config_path", "src/jbake/settings/site.yml")
// Étape 4 : Vérifier que la propriété est bien injectée
assertTrue(project.hasProperty("site_config_path"))
// Étape 5 : Appliquer le plugin qui pourra maintenant accéder à la propriété
project.plugins.apply("jbake.ghpages")
// Étape 6 : Exécuter la tâche et vérifier son comportement
val task: Task = project.tasks.findByName("initialize")
.apply(::assertNotNull)!!
// Étape 7 : Exécuter les actions de la tâche
task.actions.forEach { it.execute(task) }
// Étape 8 : Assertions finales
assertEquals(
"src/jbake/settings/site.yml",
project.properties["site_config_path"],
"La propriété devrait être accessible dans le projet"
)
}
2.4. Étape 4 : Diagramme de Flux de la Solution
2.5. Étape 5 : Comparaison Avant/Après
2.6. Étape 6 : Gestion des Cas de Test Multiples
Pour tester différents scénarios, créons plusieurs tests avec des configurations variées :
class JbakeGhPagesPluginTest {
@TempDir
lateinit var testProjectDir: File
private fun createProjectWithProperty(propertyValue: String?): Project {
val project = ProjectBuilder.builder()
.withProjectDir(testProjectDir)
.build()
// Injection conditionnelle de la propriété
propertyValue?.let { value ->
val extra = project.extensions.getByType(ExtraPropertiesExtension::class.java)
extra.set("site_config_path", value)
}
return project
}
@Test
fun `should work with valid property`() {
val project = createProjectWithProperty("src/jbake/settings/site.yml")
project.plugins.apply("jbake.ghpages")
val task = project.tasks.findByName("initialize")!!
task.actions.forEach { it.execute(task) }
// Assertions pour le cas normal
assertEquals("src/jbake/settings/site.yml", project.properties["site_config_path"])
}
@Test
fun `should handle missing property gracefully`() {
val project = createProjectWithProperty(null) // Pas de propriété
project.plugins.apply("jbake.ghpages")
val task = project.tasks.findByName("initialize")!!
// Le plugin devrait gérer l'absence de propriété
assertDoesNotThrow {
task.actions.forEach { it.execute(task) }
}
}
@Test
fun `should handle invalid property path`() {
val project = createProjectWithProperty("invalid/path/to/config.yml")
project.plugins.apply("jbake.ghpages")
val task = project.tasks.findByName("initialize")!!
// Test du comportement avec un chemin invalide
assertThrows<FileNotFoundException> {
task.actions.forEach { it.execute(task) }
}
}
}
2.7. Architecture Finale de la Solution
3. Les Avantages de cette Approche
-
Isolation Complète : Le test ne dépend d’aucun fichier externe. Il est autonome et peut s’exécuter de manière fiable dans n’importe quel environnement (local, CI/CD, etc.).
-
Clarté et Intention : Le test déclare explicitement les conditions préalables à son exécution. Quiconque lit le test voit immédiatement que le plugin nécessite la propriété
site_config_path
pour fonctionner. -
Maintenabilité : Si le nom de la propriété change, il suffit de le mettre à jour à un seul endroit dans le test, sans avoir à manipuler des fichiers de configuration de test.
-
Flexibilité : Il devient trivial de tester différents scénarios en changeant simplement la valeur de la propriété injectée dans chaque test (valeur valide, invalide, absente, etc.).
-
Performance : Pas de lecture de fichiers, tout se passe en mémoire, ce qui rend les tests plus rapides.
-
Reproductibilité : Les tests sont déterministes car ils ne dépendent pas de l’état du système de fichiers.
4. Bonnes Pratiques et Conseils
4.1. Créer une Méthode Utilitaire
class GradleTestUtils {
companion object {
fun createProjectWithProperties(
projectDir: File,
properties: Map<String, String>
): Project {
val project = ProjectBuilder.builder()
.withProjectDir(projectDir)
.build()
val extra = project.extensions.getByType(ExtraPropertiesExtension::class.java)
properties.forEach { (key, value) ->
extra.set(key, value)
}
return project
}
}
}
4.2. Validation des Propriétés
@Test
fun `should validate property injection`() {
val project = createProjectWithProperty("test-value")
// Vérifications multiples
assertTrue(project.hasProperty("site_config_path"))
assertEquals("test-value", project.property("site_config_path"))
assertNotNull(project.properties["site_config_path"])
// Vérification que la propriété est accessible par le plugin
project.plugins.apply("jbake.ghpages")
// ... reste du test
}
5. Conclusion
Plutôt que de lutter pour faire lire des fichiers de configuration à un environnement de test unitaire, la meilleure pratique consiste à simuler l’état requis. L’utilisation de ExtraPropertiesExtension
pour définir par programme les propriétés Gradle est la méthode la plus propre et la plus robuste pour réaliser des tests unitaires de plugins efficaces et fiables.
Cette technique transforme un problème de dépendance externe en une simple injection de dépendance, au cœur même de la philosophie du Test-Driven Development (TDD). Elle offre un contrôle total sur l’environnement de test tout en maintenant l’isolation nécessaire à des tests unitaires de qualité.
Les diagrammes et exemples présentés dans cet article montrent comment cette approche peut être mise en œuvre de manière progressive et méthodique, permettant de créer une suite de tests robuste et maintenable pour vos plugins Gradle.