# Stratégie de test — iMedia TV+ ## Principes - **TDD partout où c'est possible** : écrire le test avant l'implémentation - **Couverture cible** : > 70% sur les couches `data/` et `util/` - **Tests UI** : parcours critiques uniquement (login, lecture, favoris) - **Exécution locale** : tous les tests doivent passer avant chaque commit ## Stack de test | Technologie | Usage | |-------------|-------| | JUnit 5 | Runner principal | | MockK | Mocking Kotlin (classes, objects, extensions) | | Turbine | Test des Flow (StateFlow, SharedFlow) | | Kotlinx Coroutines Test | `runTest`, `TestDispatcher` | | Compose UI Test | Test des écrans Compose TV | | Espresso | Complément pour interactions système | ## Tests unitaires ### data/repository/ | Fichier | Scénarios | |---------|-----------| | `AuthRepositoryTest.kt` | ✅ Réponse valide → `auth == "1"` → `Success` | | | ✅ Réponse valide → `auth == "0"` → `Error("Identifiants incorrects")` | | | ✅ Timeout → `Error("Le serveur ne répond pas")` | | | ✅ Socket error → `Error("Aucune connexion réseau")` | | `LiveRepositoryTest.kt` | ✅ Catégories chargées → liste triée FR/AR/autres | | | ✅ Chaînes filtrées par catégorie | | | ✅ Flux vide → liste vide (pas de crash) | | `EpgRepositoryTest.kt` | ✅ Cache valide (< 2min) → pas d'appel API | | | ✅ Cache expiré → appel API + mise à jour cache | | | ✅ Pas de cache → appel API + insertion | | | ✅ EPG vide → `Error("Aucun programme")` | | `FavoritesRepositoryTest.kt` | ✅ Ajout favori → liste contient l'ID | | | ✅ Suppression → liste ne contient plus l'ID | | | ✅ Persistance → données conservées après recreation du repo | | `SeriesRepositoryTest.kt` | ✅ Détail série retourne saisons + épisodes | | | ✅ Épisode sans titre → fallback "Épisode N" | ### data/storage/ | Fichier | Scénarios | |---------|-----------| | `SecureStorageTest.kt` | ✅ Valeur écrite = valeur lue | | | ✅ Données différentes après clair vs chiffré | | | ✅ Clear supprime toutes les données | | | ✅ Valeur inexistante → null safe | ### util/ | Fichier | Scénarios | |---------|-----------| | `CategorySortUtilsTest.kt` | ✅ Catégories FR en premier | | | ✅ Catégories AR ensuite | | | ✅ Catégories "Toutes" et "Favoris" toujours en haut | | | ✅ Autres en dernier | | | ✅ Ordre alphabétique à l'intérieur des groupes | | `XtreamUrlBuilderTest.kt` | ✅ URL Live correcte avec m3u8 | | | ✅ URL VOD avec extension dynamique | | | ✅ URL série avec épisode_id | | | ✅ URL Replay avec timestamp formaté | ## Tests d'intégration / UI | Fichier | Parcours | |---------|----------| | `LoginScreenTest.kt` | ✅ Remplit les champs → bouton Connexion activé | | | ✅ Champs vides → bouton désactivé | | | ✅ Erreur réseau → message d'erreur visible | | | ✅ Connexion OK → navigation vers HomeScreen | | `HomeScreenTest.kt` | ✅ Catégories affichées au chargement | | | ✅ Changement de catégorie → nouvelles chaînes | | | ✅ Favori accessible avec télécommande (DPAD) | | | ✅ Recherche par nom fonctionne | | `PlayerScreenTest.kt` | ✅ Overlay visible → disparaît après 3s | | | ✅ Zapping gauche → chaîne précédente | | | ✅ Premier retour → overlay réapparaît | | | ✅ Deuxième retour → retour à HomeScreen | ## Exécution ```powershell # Tout .\gradlew.bat test connectedAndroidTest --no-daemon # Tests unitaires uniquement .\gradlew.bat test --no-daemon # Tests UI .\gradlew.bat connectedAndroidTest --no-daemon # Filtre par classe .\gradlew.bat test --no-daemon --tests *AuthRepositoryTest* .\gradlew.bat connectedAndroidTest --no-daemon --tests *LoginScreenTest* # Rapport de couverture .\gradlew.bat :app:createDebugCoverageReport --no-daemon # HTML → app/build/reports/coverage/debug/index.html ``` ## Convention d'écriture ```kotlin class AuthRepositoryTest { private val api = mockk() private val storage = mockk() private val repo = AuthRepository(api, storage) @Test fun `login with valid credentials returns Success`() = runTest { // Given coEvery { api.authenticate(any(), any(), any()) } returns AuthResponse( userInfo = UserInfo(username = "test"), auth = "1", status = "Active" ) // When val result = repo.login("http://server", "user", "pass") // Then assertTrue(result is UiState.Success) assertEquals("test", (result as UiState.Success).data.username) } } ```