Les tests automatiques sont un filet de sécurité : à chaque modification, ils vérifient que rien de ce qui marchait n'est cassé. La CI/CD (intégration et déploiement continus) lance ces vérifications automatiquement à chaque commit, puis déploie sans étape manuelle risquée.
La stratégie de test Junior
| Niveau | Cible | Outils | Vitesse |
|---|---|---|---|
| Unitaire | Domaine, handlers, règles | xUnit v3, NUnit ou MSTest ; NSubstitute ; Shouldly ou AwesomeAssertions | Millisecondes |
| Intégration | API + vraie base en conteneur | WebApplicationFactory, Testcontainers, Respawn | Secondes |
| Architecture | Règle de dépendance, conventions | NetArchTest, ArchUnitNET | Millisecondes |
| Contrat | Compatibilité producteur / consommateur | Pact, snapshots OpenAPI avec Verify | Secondes |
| Charge | Latence et débit | k6, NBomber | Minutes |
FluentAssertions est passé sous licence commerciale à partir de la v8 ; AwesomeAssertions (fork communautaire) et Shouldly sont des alternatives libres.
// Test d'intégration avec une vraie base SQL Server en conteneur
public sealed class OrdersApiTests : IAsyncLifetime
{
private readonly MsSqlContainer _db = new MsSqlBuilder().Build();
private WebApplicationFactory<Program> _app = null!;
public async ValueTask InitializeAsync()
{
await _db.StartAsync();
_app = new WebApplicationFactory<Program>().WithWebHostBuilder(b =>
b.UseSetting("ConnectionStrings:Shop", _db.GetConnectionString()));
}
[Fact]
public async Task Post_order_returns_201_with_location()
{
var client = _app.CreateClient();
var res = await client.PostAsJsonAsync("/v1/orders", new { customerId = Guid.NewGuid() },
TestContext.Current.CancellationToken);
res.StatusCode.ShouldBe(HttpStatusCode.Created);
res.Headers.Location.ShouldNotBeNull();
}
public async ValueTask DisposeAsync() { await _app.DisposeAsync(); await _db.DisposeAsync(); }
}
// Test d'architecture : le domaine ne dépend de rien
[Fact]
public void Domain_has_no_dependency_on_infrastructure() =>
Types.InAssembly(typeof(Order).Assembly)
.ShouldNot().HaveDependencyOnAny("Microsoft.EntityFrameworkCore", "Shop.Infrastructure")
.GetResult().IsSuccessful.ShouldBeTrue();
Organisation de la solution Confirmé
- Central Package Management : un
Directory.Packages.propsfixe chaque version NuGet une seule fois pour toute la solution. - Format
.slnx: le format de solution XML, lisible et sans conflits de fusion, est le défaut des nouveaux projets depuis le SDK .NET 10. - .NET Aspire (Aspire 13) : orchestre en local l'API, la base, Redis et les dépendances avec un tableau de bord OpenTelemetry intégré.
- Versionnement sémantique des API et des packages internes ; changelog généré depuis des commits conventionnels.
Pipeline CI/CD Confirmé
| Étape | Commande / outil | Bloquant |
|---|---|---|
| Restore, build (avertissements = erreurs) | dotnet build -c Release | Oui |
| Format | dotnet format --verify-no-changes | Oui |
| Tests + couverture | dotnet test | Oui |
| Vulnérabilités | dotnet list package --vulnerable --include-transitive, scan d'image (Trivy, Artifact Analysis) | Oui si critique |
| Image | Build multi-stage, tag = version + SHA, push Artifact Registry | — |
| Migration de schéma | Bundle EF Core ou script idempotent | Oui |
| Déploiement progressif | Cloud Run : révision sans trafic, puis bascule partielle (canary) ; GKE : rolling ou Cloud Deploy | Rollback automatique sur alerte |
- Mocker
DbContext: les tests passent, la vraie requête SQL échoue. Tester l'accès aux données contre une vraie base (Testcontainers). - Base de données en mémoire EF (
UseInMemoryDatabase) : pas de transactions, pas de contraintes ; déconseillée par l'équipe EF elle-même pour les tests. - Tests dépendants de l'heure : injecter
TimeProvider(.NET 8+) et utiliserFakeTimeProvider.
À retenir
- Beaucoup de tests unitaires sur le domaine, des tests d'intégration sur vraie base, des tests d'architecture.
- Versions centralisées, SDK figé, avertissements bloquants.
- Déploiement progressif avec rollback, jamais de bascule totale à l'aveugle.