meteocompare

Stratégie de tests MeteoCompare

Cette base sépare volontairement les tests rapides JVM des tests Android instrumentés.

Pyramide de tests

Tests JVM (app/src/test)

Ils couvrent les règles métier, les agrégations météo, le mapping réseau, les ViewModels, les widgets purs et les schedulers testables sans appareil. Le moteur d’évolution y vérifie notamment la sélection des snapshots locaux ~24/~48/~72 h, l’intersection des modèles comparables, les révisions, la détection des tendances et l’absence d’impact d’une erreur d’historisation sur la prévision principale. Ils ne doivent dépendre ni du réseau réel, ni de l’heure système non contrôlée, ni d’une base Android persistante.

Tests instrumentés (app/src/androidTest)

Ils couvrent ce qui nécessite Android :

Les parcours d’application n’utilisent jamais Open-Meteo. TestRepositoryModule remplace les repositories de production par des fakes déterministes. Les dates de fixtures sont calculées relativement au jour d’exécution puis figées pour tout le processus de test.

Commandes

Sous Windows :

.\gradlew.bat testDebugUnitTest
.\gradlew.bat connectedDebugAndroidTest
.\gradlew.bat lintDebug
.\gradlew.bat assembleDebug

Validation complète :

.\gradlew.bat testDebugUnitTest connectedDebugAndroidTest lintDebug assembleDebug

Sous macOS/Linux, remplacer gradlew.bat par ./gradlew.

Environnement instrumenté recommandé

Avant une release, exécuter au minimum la suite sur une locale française et une locale anglaise, ainsi que sur un appareil physique si possible.

Règles pour les nouveaux tests

  1. Un test Compose appelle setContent une seule fois.
  2. Les interactions ciblent un testTag, un rôle sémantique ou une ressource localisée, jamais une chaîne française arbitraire quand elle peut varier.
  3. Les tests de ViewModel utilisent un dispatcher principal de test et des flows contrôlés.
  4. Les tests instrumentés ne contactent aucun service externe.
  5. Toute correction de bug ajoute un test de non-régression au niveau le plus bas possible.
  6. Un test ne dépend jamais des données laissées par un autre test.

Diagnostic en cas d’échec

Pour obtenir davantage de détails :

.\gradlew.bat connectedDebugAndroidTest --stacktrace --info

Les rapports sont générés dans :

Diagnostic des notifications météo locales

Le résumé quotidien n’utilise plus WorkManager.setInitialDelay() comme horloge. Une alarme RTC inexacte (AlarmManager.setAndAllowWhileIdle) réveille l’application autour de l’heure choisie puis déclenche un work immédiat portant le tag meteocompare_notifications. Les alertes divergence/révision restent périodiques, avec un contrôle immédiat lors d’une modification explicite des réglages.

Logs utiles :

adb logcat -s MeteoCompare/Notif WM-WorkerWrapper WM-Processor WM-SystemJobService
adb shell dumpsys alarm | findstr /I "meteocompare WEATHER_NOTIFICATION_DAILY_SUMMARY"
adb shell dumpsys jobscheduler | findstr /I "meteocompare notifications"

Recette de non-régression du résumé quotidien :

  1. choisir une ville et activer son résumé quotidien ;
  2. régler l’heure 3 à 5 minutes dans le futur ;
  3. revenir à l’écran d’accueil Android sans rouvrir MeteoCompare ;
  4. vérifier dans logcat Daily alarm scheduled for ..., puis Daily summary alarm fired ..., Starting notification cycle: DAILY_SUMMARY et enfin Posted daily|... ;
  5. modifier uniquement l’heure et refaire le test : aucune désactivation / réactivation ne doit être nécessaire ;
  6. répéter après un reboot et après un changement de fuseau/heure système.

Test Doze recommandé :

adb shell dumpsys deviceidle force-idle
# Laisser passer l'heure choisie, sans ouvrir l'application.
adb shell dumpsys deviceidle unforce
adb shell input keyevent KEYCODE_WAKEUP

setAndAllowWhileIdle() est volontairement inexact : Android ne doit jamais notifier avant l’heure choisie, mais peut décaler l’alarme selon ses politiques d’économie d’énergie. Le critère de régression essentiel est que la notification ne dépende plus de la réouverture de l’application.

Pour les alertes :

Diagnostic du rafraîchissement des widgets

Le travail périodique porte le tag meteocompare_widget et le nom unique meteocompare_widget_refresh. Pour diagnostiquer un téléphone où le launcher semble conserver une ancienne vue :

adb logcat -s MeteoCompare/Widget WM-WorkerWrapper WM-Processor
adb shell dumpsys jobscheduler | findstr /I "meteocompare widget_refresh"

Test de reprise après veille profonde :

adb shell dumpsys deviceidle force-idle
# Attendre, puis sortir du mode idle
adb shell dumpsys deviceidle unforce
adb shell input keyevent KEYCODE_WAKEUP

Points à vérifier :

Recette de stabilisation R9

Avant toute release construite à partir de l’audit R9 :

.\gradlew.bat clean testDebugUnitTest testReleaseUnitTest lintDebug assembleDebug assembleRelease
.\gradlew.bat connectedDebugAndroidTest

Points manuels ciblés à vérifier en plus de la suite automatisée :

  1. Ajouter une ville côtière et une ville intérieure : la pastille bleue du menu ne doit apparaître que si le mode Mer/côte est disponible ; l’icône 🌊 près du nom ne doit apparaître qu’après activation.
  2. Passer hors ligne avec un ancien cache marin : l’état connu peut rester visible, puis doit être revalidé au retour réseau.
  3. Changer successivement les quatre moteurs dans Settings : Home, Détails et widgets doivent changer de centrale sans modifier les badges de convergence brute.
  4. Ouvrir Engine Comparison : les quatre courbes utilisent les mêmes données brutes ; le changement de moteur actif ne doit pas refetcher la météo.
  5. Dans Évolution des prévisions, vérifier que les seuils dessinés correspondent aux seuils métier : T 0,5/1,0 °C ; pluie 1/2 mm ; vent 3/5 km/h.

JDK 25 et MockK

Les tests JVM utilisent MockK 1.14.11. Son agent déclare Byte Buddy 1.18.2 transitivement ; MeteoCompare force byte-buddy et byte-buddy-agent en 1.18.9 dans le classpath test afin d’utiliser le chemin d’injection compatible JDK 25 et d’éviter l’appel terminalement déprécié à sun.misc.Unsafe::objectFieldOffset.

Pour vérifier la résolution réellement utilisée :

.\gradlew.bat :app:dependencyInsight --dependency byte-buddy --configuration testReleaseRuntimeClasspath
.\gradlew.bat :app:dependencyInsight --dependency byte-buddy-agent --configuration testReleaseRuntimeClasspath

La version sélectionnée doit être 1.18.9.

R10 — consensus hybride des conditions météo

Le libellé de condition ne doit plus sur-représenter « Couvert » lorsque les modèles secs sont répartis entre CLEAR, MAINLY_CLEAR, PARTLY_CLOUDY et OVERCAST.

Tests automatiques ciblés :

Recette manuelle recommandée : choisir une ville où plusieurs modèles oscillent entre codes WMO 1/2/3, comparer le pourcentage de nébulosité et l’icône affichée sur Home, Détails et widgets. Entre 45 et 84 % de nébulosité centrale, un ciel sec doit rester « Partiellement nuageux » ; pluie/neige/brouillard/orage doivent continuer à primer lorsqu’ils gagnent le vote catégoriel familial.

R11 — consensus hiérarchique généralisé des conditions météo

R11 remplace le regroupement ponctuel DRY_SKY de R10 par un arbre sémantique complet. Le test doit vérifier la décision à chaque niveau, pas seulement le ciel sec.

Arbre de référence :

ROOT
├─ NON_PRECIPITATION
│  ├─ SKY → CLEAR / MAINLY_CLEAR / PARTLY_CLOUDY / OVERCAST
│  └─ FOG
└─ PRECIPITATION
   ├─ LIQUID → DRIZZLE / RAIN_SHOWERS / RAIN
   ├─ FROZEN → SNOW_SHOWERS / SNOW
   ├─ FREEZING_RAIN
   └─ THUNDERSTORM

Tests ciblés dans WeatherConditionConsensusTest :

Recette manuelle : vérifier sur Home, Détails, widgets et Engine Comparison qu’une majorité sémantique ne se fragmente plus entre sous-types voisins, tout en contrôlant que le pourcentage de convergence reste inchangé par rapport aux codes WMO bruts.

Cartes compactes / dépliées sur l’accueil

À vérifier également manuellement sur un petit écran et en mode sombre : largeurs très étroites, longues dénominations de villes, lecteur d’écran, unités impériales et actions contextuelles après dépliage.