Semaine 7
Cette semaine, nous allons nous concentrer sur la qualité et la robustesse de notre application. Nous allons introduire deux concepts fondamentaux pour tout développement professionnel : les Tests Unitaires et le Logging.
Objectifs
- Logging : Mettre en place un système de journalisation pour suivre l’activité de l’application et diagnostiquer les problèmes.
- Tests Unitaires : Créer un projet de test et écrire nos premiers tests pour valider la logique métier de nos services.
Partie 1 : Le Logging
Le logging (journalisation) est essentiel pour comprendre ce qui se passe dans votre application une fois qu’elle est déployée. C’est votre “boîte noire”.
1. Le concept
En .NET, le logging est intégré nativement via l’interface ILogger<T>.
Il existe plusieurs niveaux de logs :
- Trace / Debug : Pour le développement, très verbeux.
- Information : Flux normal de l’application (ex: “Utilisateur connecté”).
- Warning : Quelque chose d’inattendu mais pas bloquant (ex: “Tentative de connexion échouée”).
- Error : Erreur bloquante ou exception gérée (ex: “Erreur lors du paiement”).
- Critical : Crash de l’application.
2. Injecter le Logger
Nous allons ajouter des logs dans notre application.
Consignes
Utilisez l’injection de dépendance pour injecter ILogger<MaClasse> dans vos services, middlewares et contrôleurs.
3. Ajouter des Logs
Ajoutez ensuite du logging dans vos méthodes.
- Logger les erreurs en LogError dans votre middleware ErrorHandlingMiddleware.
- Ajoutez du logs dans vos services pour tracer les actions importantes (Achat d’item, création de compte, Click, etc.).
- Ajoutez du logs dans vos contrôleurs pour remonter les erreurs et les actions importantes si applicable.
- Ajoutez un middleware qui log en Debug les requêtes et les réponses faites à votre API.
- Dans le Program.cs, ajoutez un logging pour indiquer que l’application est en cours de démarrage et que l’initialisation est terminée.
Note : Pour le logging dans le Program.cs, vous pouvez utiliser le logger injecté dans la variable
app.Logger. Example:app.Logger.LogInformation("Application is starting up...");
Note : Utilisez les
{}pour passer des paramètres au message de log (Structured Logging). Ne faites pas de concaténation de chaînes ("User " + username). Example:_logger.LogInformation("User {Username} logged in", username);
4. Astuce : Trop de logs ?
Par défaut, Entity Framework affiche les requêtes SQL exécutées, ce qui peut polluer votre console.
Pour réduire le bruit, vous pouvez modifier le fichier appsettings.json pour augmenter le niveau minimum de log requis pour EF Core.
Dans appsettings.json :
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning" // N'affiche que les warnings et erreurs de EF
}
}
Partie 2 : Tests Unitaires
Les tests unitaires servent à vérifier qu’une petite partie de code (une “unité”, souvent une méthode) fonctionne comme prévu, indépendamment du reste du système (base de données, réseau, etc.).
1. Création du projet de test
Nous allons utiliser xUnit, le framework de test standard en .NET.
Consignes
- À la racine de votre solution (dossier parent de
GameServerApi), créez un nouveau projet de test :dotnet new xunit -o GameServerApi.Tests - Ajoutez le projet à la solution :
dotnet sln add GameServerApi.Tests - Ajoutez une référence vers votre projet principal :
cd GameServerApi.Tests dotnet add reference ../GameServerApi/GameServerApi.csproj
2. Les Outils de Mocking
Pour tester nos services sans utiliser une vraie base de données nous avons besoin de simuler (“mocker”) le comportement de nos dépendances.
Nous allons utiliser deux bibliothèques :
- Moq : Pour créer des faux objets (mocking de services).
- Microsoft.EntityFrameworkCore.InMemory : Pour simuler la base de données en mémoire.
Installez ces packages dans le projet GameServerApi.Tests :
dotnet add package Moq
dotnet add package Microsoft.EntityFrameworkCore.InMemory --version "9.*"
Important : Configuration du Context
Si vous avez configuré votre BDDContext pour utiliser SQLite directement dans la méthode OnConfiguring, cela va entrer en conflit avec la base de données en mémoire utilisée pour les tests.
Il faut modifier GameServerApi/Models/BDDContext.cs pour ne configurer SQLite que si aucune autre configuration n’a été fournie :
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
// Vérifie si une configuration (comme InMemory pour les tests) est déjà présente
if (!options.IsConfigured)
{
// Connexion à la base sqlite par défaut
options.UseSqlite("Data Source=BDD.db");
}
}
3. Premier Test : UserService
Nous allons tester la méthode RegisterAsync de UserService.
Créez un fichier UserServiceTests.cs dans le projet de test.
Structure d’un test (AAA)
- Arrange : Préparer les données et les mocks.
- Act : Exécuter la méthode à tester.
- Assert : Vérifier le résultat.
Exemple de test complet
Voici comment tester que l’inscription fonctionne. Copiez ce code et analysez-le.
using GameServerApi.Models;
using GameServerApi.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace GameServerApi.Tests;
public class UserServiceTests
{
[Fact]
public async Task RegisterAsync_ShouldCreateUser_WhenValidData()
{
// 1. ARRANGE
// Setup InMemory Database
var options = new DbContextOptionsBuilder<BDDContext>()
.UseInMemoryDatabase(databaseName: "TestDb_Register") // Nom unique par test
.Options;
var context = new BDDContext(options);
// Setup Mocks
var passwordHasher = new PasswordHasher<User>();
// Pour JwtService, comme ce n'est pas une interface, le plus simple est d'utiliser le vrai
// en lui fournissant une configuration mockée.
var configMock = new Mock<IConfiguration>();
configMock.Setup(c => c["JWTKey"]).Returns("UneCleSecreteTresLonguePourLesTests123456789");
var jwtService = new JwtService(configMock.Object);
var loggerMock = new Mock<ILogger<UserService>>();
// Création du service à tester
var userService = new UserService(context, passwordHasher, jwtService, loggerMock.Object);
// Données de test
var userPass = new UserPass { Username = "TestUser", Password = "Password123!" };
// 2. ACT
var result = await userService.RegisterAsync(userPass);
// 3. ASSERT
Assert.NotNull(result.Token);
Assert.Equal("TestUser", result.User.Username);
// Vérifier que l'user est bien en BDD
var userInDb = await context.Users.FirstOrDefaultAsync(u => u.Username == "TestUser");
Assert.NotNull(userInDb);
Assert.Equal(UserRole.Admin, userInDb.Role); // Le premier user doit être Admin
}
}
4. Lancer les tests
Exécutez la commande suivante :
dotnet test
Vous devriez voir que le test passe (vert).
5. Tester les cas d’erreur
Un bon testeur vérifie aussi que le code échoue quand il le doit.
Ajoutez un test pour vérifier qu’on ne peut pas créer deux utilisateurs avec le même nom.
Tâches à réaliser
-
Logging :
- Injecter
ILoggerdansUserService,GameServiceetInventoryService. - Ajouter des logs (Info/Warning/Error) aux endroits stratégiques.
- Vérifier que les logs apparaissent dans la console quand vous lancez l’API.
- Injecter
-
Tests Unitaires :
- Configurer le projet
GameServerApi.Tests. - Écrire les tests pour
UserService.RegisterAsync(Succès + Erreur doublon). - Écrire les tests pour
UserService.LoginAsync(Succès + Erreur mauvais mot de passe). - Écrire un test pour
InventoryService.PurchaseItemAsync(Vérifier que l’argent est débité et l’item ajouté).
- Configurer le projet
Livrables
Votre projet doit compiler et la commande dotnet test doit réussir avec au moins 5 tests passants.