Thème
Tests du backend
Tests unitaires par couche et tests d'intégration traversant la chaîne complète, du bit Modbus jusqu'au DTO poussé à l'interface. 155 fichiers, 1263 méthodes de test.
| Technologie | xUnit, FluentAssertions, Moq, Xunit.SkippableFact |
| Bases | EF Core InMemory, et SQLite là où un fournisseur relationnel est requis |
| Hôte HTTP | Microsoft.AspNetCore.Mvc.Testing — WebApplicationFactory<Program> |
| Référence projet | dotnet/backend — qui lui ouvre ses types internes par InternalsVisibleTo |
Structure
Le découpage suit celui du backend, plus un dossier d'intégration qui traverse les couches.
| Dossier | Fichiers | Méthodes | Contenu |
|---|---|---|---|
Domain/ | 11 | 118 | Entités, modèles et objets valeur |
Application/ | 50 | 506 | Services métier, DTO, sécurité, rapports |
Infrastructure/ | 37 | 208 | Transport Modbus, adaptateurs, persistance, télémétrie |
Presentation/ | 30 | 313 | Contrôleurs et hub temps réel |
Integration/ | 14 | 101 | Chaînes complètes |
Repositories/ | 3 | 14 | Accès aux données |
Shared/ | 1 | 3 | Configuration applicative partagée |
TestHelpers/ | 6 | — | Client Modbus en mémoire, harnais RTU sur tube nommé, constructeur de scénarios, faux journal d'audit, décodeur JWT, déclaration d'environnement |
À la racine : TestDatabaseFixture.cs (base InMemory par identifiant unique, exposée par la collection xUnit DatabaseCollection), TestDbContext.cs et TestFirePanel.cs.
Suites d'intégration
| Suite | Ce qu'elle éprouve |
|---|---|
EndToEndChainTests | Une table de bobines réelle lue par le vrai adaptateur, jusqu'au PanelDto tel qu'il serait poussé par SignalR — y compris les cas dégradés du transport : lecture tronquée, exception en cours de cycle, déconnexion pendant l'interrogation |
SystemFlagTruthTableTests | La table de vérité des états système. Les cas sont dérivés des définitions du profil : un état ajouté est couvert sans qu'aucun test soit écrit |
ResourceChangeCoverageTests | Parcourt les contrôleurs par réflexion et exige [NotifiesResource] sur toute action modifiant des données partagées |
AcknowledgmentCascadeTests | La descente d'un acquittement : synthèse → zones → points, et son absence de remontée |
SecurityBoundaryTests | Les frontières d'autorisation des points d'entrée |
HttpApiIntegrationTests | L'API réelle derrière WebApplicationFactory<Program> |
PostgresMigrationTests | L'application des migrations sur un fournisseur relationnel |
BackupResilienceTests, DatabaseBackupIntegrationTests | Aller-retour export / restauration |
CommandLifecycleTests, IncidentWorkflowIntegrationTests | Cycle de vie d'une commande et d'un incident |
CustomProfileLifecycleTests, SystemEntityEndToEndTests | Profil déclaré par l'opérateur, entités système IHM |
ReportsIntegrationTests | Génération et archivage des rapports |
Exécution
powershell
dotnet test dotnet/tests/backend/backend.tests.csproj
dotnet test dotnet/tests/backend/backend.tests.csproj -c Release # comme l'intégration continue
dotnet test dotnet/tests/backend/backend.tests.csproj --filter "FullyQualifiedName~FirePanelTests"Mécanismes non évidents
Debug et Release ne voient pas le même backend
L'intégration continue compile en Release, dotnet test sans option compile en Debug. Plusieurs types du backend n'existent qu'en Debug — DemoSeedController, DemoDataSeeder, des actions de MapsController et d'AppConfigsController. Un test réflexif comme ResourceChangeCoverageTests voit donc un jeu de types différent selon la configuration, et peut passer en local tout en échouant sur l'intégration continue. Rejouer la suite en Release dès qu'un type conditionnel ou un test réflexif est touché.
L'environnement est déclaré avant le premier test
TestHelpers/TestEnvironment.cs pose ASPNETCORE_ENVIRONMENT=Testing depuis un [ModuleInitializer].
| Point | Détail |
|---|---|
| Pourquoi une variable d'environnement | Le point d'entrée du backend résout ses chemins de données avant que le constructeur d'hôte n'existe ; UseEnvironment arriverait trop tard |
| Pourquoi un initialiseur de module | Il s'exécute au chargement de l'assemblage, avant le premier test |
| Ce que cela évite | Sans lui, un test qui téléverse un fichier ou démarre l'hôte écrirait dans les dossiers du service installé |
Le secret applicatif de test est versionné
appsettings.Crypto.json est le seul fichier appsettings.Crypto.* versionné du dépôt, dé-ignoré explicitement par .gitignore. Le démarrage du backend refuse un secret absent : sans ce fichier, WebApplicationFactory<Program> ne pourrait pas construire l'hôte. Il est copié en Always pour primer sur l'artefact transitif hérité du projet backend, et la constante FireSupervisorApiFactory.SuperAdminPassword doit rester alignée sur sa valeur.
Le fournisseur InMemory n'est pas relationnel
| Ce qu'il ne sait pas faire | Conséquence |
|---|---|
ExecuteDelete et les opérations en masse | Le code de production les garde derrière IsRelational() |
GetDbConnection() | Lève sous InMemory |
| Contraintes et clés étrangères | Non appliquées : un test peut passer sur un graphe qu'une vraie base refuserait |
Les tests qui exigent un fournisseur relationnel passent par SQLite.
Contraintes actives
| Contrainte | Conséquence si elle est ignorée |
|---|---|
Le projet cible net9.0 sans dépendance graphique | Il ne tournerait plus sur les agents Ubuntu de l'intégration continue |
| Un test va dans le dossier de la couche éprouvée | Integration/ est réservé à ce qui traverse plusieurs couches |
SQLitePCLRaw.lib.e_sqlite3 est référencé directement | La version tirée par EF Core 9.0.18 est affectée par GHSA-2m69-gcr7-jv3q ; la référence directe force la version corrigée |