Files
iptv-android-tv-hermes/TESTING.md
T

131 lines
4.5 KiB
Markdown

# 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<XtreamApi>()
private val storage = mockk<SecureStorage>()
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)
}
}
```