Thème
Tests .NET
Cinq projets xUnit, un par périmètre. Les tests du frontend vivent ailleurs : specs Vitest co-localisées dans vue/src/ et scénarios Playwright dans vue/e2e/ — voir le README du frontend.
| Projet | Cible | Fichiers | Méthodes de test | Ce qu'il éprouve |
|---|---|---|---|---|
backend | net9.0 | 155 | 1263 | Le service : domaine, services métier, transport Modbus, contrôleurs, chaînes d'intégration — README |
client | net9.0-windows | 5 | 22 | Vérification de licence, empreinte poste, détection de débogueur, hôte virtuel WebView2 — README |
manager | net9.0-windows | 1 | 7 | Chargement de app.config.json par le gestionnaire de service |
simulator | net9.0 | 3 | 18 | Le cœur du simulateur hors interface : réception des commandes, propagation point → zone, affichage des synthèses |
tools | net9.0 | 2 | 26 | Émission et vérification de licences, contre une paire RSA éphémère |
Décomptes obtenus en comptant les attributs [Fact], [Theory] et [SkippableFact] : un [Theory] paramétré produit plusieurs cas à l'exécution, si bien que le nombre de tests rapporté par dotnet test est supérieur.
Exécution
powershell
dotnet test # tous les projets, en Debug
dotnet test dotnet/tests/backend/backend.tests.csproj -c Release # comme l'intégration continueL'intégration continue répartit ces projets sur deux systèmes : backend, tools et simulator sur Ubuntu, client et manager sur Windows. Les trois premiers ne dépendent d'aucune interface graphique, et doivent le rester.
Mécanismes non évidents
Debug et Release ne voient pas le même backend
Plusieurs types du backend sont sous #if DEBUG — DemoSeedController, DemoDataSeeder, des actions de MapsController et d'AppConfigsController. Un test qui parcourt l'assembly par réflexion, comme ResourceChangeCoverageTests, voit donc un jeu de types différent selon la configuration : il peut passer en local et échouer sur l'intégration continue, qui compile en Release.
L'assemblage de tests backend déclare son environnement avant le premier test
TestEnvironment pose ASPNETCORE_ENVIRONMENT=Testing depuis un [ModuleInitializer].
| Raison | Détail |
|---|---|
| Pourquoi une variable d'environnement | Le point d'entrée 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é — journaux et téléversements compris |
Un secret factice est versionné, et lui seul
backend/appsettings.Crypto.json est le seul fichier appsettings.Crypto.* versionné du dépôt, explicitement dé-ignoré 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.
Deux bases de test, deux usages
| Fournisseur | Usage | Limite |
|---|---|---|
| EF Core InMemory | Majorité des tests de service et de dépôt | Ne connaît ni ExecuteDelete, ni les contraintes relationnelles ; GetDbConnection() y lève, d'où les gardes IsRelational() du code de production |
| SQLite | Tests exigeant un fournisseur relationnel | — |
TestDatabaseFixture crée une base InMemory par identifiant unique et l'expose via la collection xUnit DatabaseCollection.
Les tests de licence n'embarquent aucune clé de production
tools/LicenseSigningHelper.cs génère une paire RSA 2048 éphémère au chargement et l'injecte dans LicenseVerifier.Validate. La chaîne complète — contenu, signature, empreinte, expiration — est ainsi éprouvée sans qu'aucune clé privée n'entre dans le dépôt. Validate_AgainstTheProductionKey_RejectsTestLicenses vérifie de plus qu'une licence signée par cette clé de test est refusée par la clé publique de production. Le projet client, lui, n'émet aucune licence : il n'éprouve que les refus.
Neuf tests se sautent d'eux-mêmes selon l'environnement
Xunit.SkippableFact marque un test ignoré plutôt qu'en échec quand son préalable manque.
| Tests | Condition de saut |
|---|---|
backend/Application/Security/CryptoConfigProtectorTests.cs | Skip.IfNot(OperatingSystem.IsWindows()) — DPAPI n'existe que sous Windows |
backend/Integration/PostgresMigrationTests.cs | Aucune chaîne de connexion d'administration fournie |
tools/LicenseGeneratorTests.cs | Skip.IfNot(OperatingSystem.IsWindows()) — l'empreinte passe par WMI |
Les tests de SecurityVerifier du projet client ne passent pas par ce mécanisme : ils sortent par un return conditionné à Debugger.IsAttached, et sont donc rapportés comme réussis sans avoir rien éprouvé.
Contraintes actives
| Contrainte | Conséquence si elle est ignorée |
|---|---|
Rejouer en Release dès qu'un type conditionnel ou un test réflexif est touché | Le test passe en local et fait rougir l'intégration continue |
backend, tools et simulator ne dépendent d'aucune interface graphique | Ils ne tourneraient plus sur les agents Ubuntu de l'intégration continue |
HardwareInfo passe par WMI | Les tests qui en dépendent se sautent hors Windows |
PostgresMigrationTests exige une chaîne de connexion d'administration | Sans elle, la suite est ignorée : une migration cassée passerait inaperçue |