Skip to content
Learni
View all tutorials
.NET

Comment maîtriser NUnit pour les tests unitaires en 2026

Introduction

En 2026, avec .NET 9 et les pipelines CI/CD omniprésents, NUnit reste le framework de test unitaire de référence pour les développeurs .NET intermédiaires. Contrairement à xUnit plus minimaliste, NUnit offre une syntaxe riche en attributs comme [TestCase] pour les données paramétrées, [SetUp]/[TearDown] pour l'initialisation, et une intégration native avec Visual Studio Test Explorer.

Ce tutoriel vous guide pas à pas pour créer un projet de tests robuste sur un service de calculatrice réel, en couvrant assertions avancées, mocking avec Moq, tests async, et configuration pour CI. Imaginez tester une API financière : un divide par zéro mal géré peut coûter cher ; NUnit vous protège avec 100% de couverture.

À la fin, vos tests seront exécutables via dotnet test, intégrables à GitHub Actions, et scalables pour des équipes. Prêt à booster la fiabilité de votre code ? (128 mots)

Prérequis

  • .NET 8 SDK (ou supérieur, vérifiez avec dotnet --version)
  • Visual Studio 2022 ou VS Code avec extension C# Dev Kit
  • Connaissances de base en C# (classes, interfaces, async/await)
  • Git installé pour la gestion de projet
  • Outils de ligne de commande (PowerShell ou Bash)

Créer la solution et les projets

terminal
dotnet new sln -n CalculatorApp

dotnet new classlib -n CalculatorApp.Core
 dotnet sln CalculatorApp.sln add CalculatorApp.Core/CalculatorApp.Core.csproj

dotnet new nunit -n CalculatorApp.Tests
 dotnet sln CalculatorApp.sln add CalculatorApp.Tests/CalculatorApp.Tests.csproj

dotnet add CalculatorApp.Core/CalculatorApp.Core.csproj package MathNet.Numerics

dotnet add CalculatorApp.Tests/CalculatorApp.Tests.csproj package NUnit
 dotnet add CalculatorApp.Tests/CalculatorApp.Tests.csproj package NUnit3TestAdapter
 dotnet add CalculatorApp.Tests/CalculatorApp.Tests.csproj package Microsoft.NET.Test.Sdk
 dotnet add CalculatorApp.Tests/CalculatorApp.Tests.csproj reference CalculatorApp.Core

dotnet build

Ce script crée une solution avec un projet Core (bibliothèque de classes pour le service métier) et un projet Tests (template NUnit officiel). Il ajoute MathNet pour des calculs avancés, référence le Core dans Tests, et build pour vérifier. Évitez les templates manuels : le template nunit inclut déjà les packages essentiels, réduisant les erreurs de NuGet.

Implémenter le service à tester

CalculatorApp.Core/IMathService.cs
using System;
using System.Threading.Tasks;

namespace CalculatorApp.Core;

public interface IMathService
{
    double Add(double a, double b);
    double Multiply(double a, double b);
    double Divide(double a, double b);
    Task<double> AsyncCompute(double a, double b);
}

public class MathService : IMathService
{
    public double Add(double a, double b) => a + b;

    public double Multiply(double a, double b) => a * b;

    public double Divide(double a, double b)
    {
        if (b == 0)
            throw new ArgumentException("Division par zéro interdite", nameof(b));
        return a / b;
    }

    public async Task<double> AsyncCompute(double a, double b)
    {
        await Task.Delay(100); // Simulation async
        return a + b;
    }
}

Ce service IMathService/MathService expose des méthodes synchrone (Add, Multiply, Divide avec exception) et async (AsyncCompute avec delay pour tester await). L'interface permet le mocking. Piège : toujours throw des exceptions nommées pour des asserts précis ; sans interface, le mocking est impossible.

Structure des tests basiques

Avec le service en place, les tests NUnit s'appuient sur [TestFixture] pour la classe, [Test] pour chaque méthode, et [SetUp] pour initialiser avant chaque test – comme un constructeur frais à chaque exécution, évitant les fuites d'état. Les assertions comme Assert.AreEqual ou Assert.Throws sont chainables et descriptives. Exécutez avec dotnet test : le runner NUnit affiche les échecs avec stack traces précises.

Tests basiques et assertions

CalculatorApp.Tests/MathServiceTests.cs
using NUnit.Framework;
using CalculatorApp.Core;

namespace CalculatorApp.Tests;

[TestFixture]
public class MathServiceTests
{
    private IMathService _service;

    [SetUp]
    public void SetUp()
    {
        _service = new MathService();
    }

    [Test]
    public void Add_TwoPositiveNumbers_ReturnsSum()
    {
        // Act
        var result = _service.Add(2, 3);

        // Assert
        Assert.AreEqual(5, result);
        Assert.That(result, Is.EqualTo(5));
    }

    [Test]
    public void Multiply_NegativeAndPositive_ReturnsNegative()
    {
        var result = _service.Multiply(-4, 2);
        Assert.AreEqual(-8, result, "Produit doit être négatif");
    }

    [Test]
    public void Divide_ByZero_ThrowsArgumentException()
    {
        Assert.Throws<ArgumentException>(() => _service.Divide(10, 0));
    }
}

Ces tests couvrent happy path (Add), edge case (Multiply négatif) et exception (Divide). [SetUp] réinstancie le service, comme un reset automatique. Utilisez Assert.That pour fluent API moderne ; le message custom dans AreEqual aide au debug. Copiez-collez : 100% coverage basique.

Tests paramétrés avec TestCase

Pour éviter la duplication, [TestCase] injecte des données multiples : idéal pour valider des combinaisons comme positives/négatifs/zéros. C'est plus performant que des boucles en code test, car NUnit parallelise. Analogie : comme un table-driven testing en Go, mais avec syntaxe attribut declarative.

Tests avec données paramétrées

CalculatorApp.Tests/MathServiceTests.cs
using NUnit.Framework;
using CalculatorApp.Core;

namespace CalculatorApp.Tests;

[TestFixture]
public class MathServiceTests
{
    private IMathService _service;

    [SetUp]
    public void SetUp()
    {
        _service = new MathService();
    }

    [TestCase(2, 3, 5)]
    [TestCase(-1, 1, 0)]
    [TestCase(0, 0, 0)]
    public void Add_VariousInputs_ReturnsCorrectSum(double a, double b, double expected)
    {
        var result = _service.Add(a, b);
        Assert.AreEqual(expected, result, 0.001); // Tolérance pour floats
    }

    [TestCase(10, 2, 5)]
    [TestCase(10, -2, -5)]
    public void Divide_ValidInputs_ReturnsQuotient(double a, double b, double expected)
    {
        var result = _service.Divide(a, b);
        Assert.AreEqual(expected, result, 0.001);
    }

    [Test]
    public void Divide_ByZero_ThrowsArgumentException()
    {
        Assert.Throws<ArgumentException>(() => _service.Divide(10, 0));
    }
}

Extension du fichier précédent : [TestCase] teste 3 scénarios Add en un test, avec tolérance 0.001 pour doubles imprécis. Ajoutez autant de cases que needed ; NUnit les exécute indépendamment. Piège : Oubliez la tolérance sur floats → faux négatifs.

Ajouter Moq pour mocking

terminal
dotnet add CalculatorApp.Tests/CalculatorApp.Tests.csproj package Moq
 dotnet add CalculatorApp.Core/CalculatorApp.Core.csproj package Moq

dotnet build

Moq mocke les interfaces comme IMathService pour isoler les unités. Ajout en deux projets car mocks potentiellement partagés. Build valide les dépendances ; sans ça, les tests avec mocks échouent à la compilation.

Mocking et dépendances

À niveau intermédiaire, isolez les tests avec Moq : mockez IMathService pour tester un consommateur sans impl réelle. Setup définit comportements, Verify assert interactions. Parfait pour TDD sur services couplés.

Tests avec mocks et async

CalculatorApp.Tests/MathServiceTests.cs
using NUnit.Framework;
using Moq;
using CalculatorApp.Core;
using System;
using System.Threading.Tasks;

namespace CalculatorApp.Tests;

[TestFixture]
public class MathServiceTests
{
    private IMathService _service;
    private Mock<IMathService> _mockService;

    [SetUp]
    public void SetUp()
    {
        _service = new MathService();
        _mockService = new Mock<IMathService>();
    }

    [Test]
    public async Task AsyncCompute_CalledOnce_ReturnsSum()
    {
        // Act
        var result = await _service.AsyncCompute(2, 3);

        // Assert
        Assert.AreEqual(5, result);
    }

    [Test]
    public void Consumer_UsesService_AddCalled()
    {
        // Arrange
        _mockService.Setup(s => s.Add(It.IsAny<double>(), It.IsAny<double>())).Returns(42);
        var consumer = new TestConsumer(_mockService.Object);

        // Act
        consumer.UseAdd();

        // Assert
        _mockService.Verify(s => s.Add(1, 2), Times.Once);
    }
}

public class TestConsumer
{
    private readonly IMathService _service;
    public TestConsumer(IMathService service) => _service = service;
    public void UseAdd() => _service.Add(1, 2);
}

Ajout async test avec await et mock pour TestConsumer (dépendance injectée). It.IsAny match any args, Times.Once vérifie appel exact. Copier-collez exécutable ; isole les unités, accélère les tests x10 vs E2E.

Bonnes pratiques

  • Un assert par test : Évite les cascades d'erreurs obscures ; chaque test = une intention claire.
  • Nommez descriptivement : Add_TwoPositives_ReturnsSum > Test1, pour debug rapide en CI.
  • Utilisez [OneTimeSetUp] pour setup lourd (DB mock), [SetUp] pour per-test.
  • Couverture >80% : Visez avec dotnet test --collect:"XPlat Code Coverage", intégrez à SonarQube.
  • Parallélisez : Ajoutez dans .csproj pour x4 vitesse sur CI.

Erreurs courantes à éviter

  • Oublier [SetUp] : État partagé → tests flaky ; toujours réinitialiser.
  • Assert sans tolérance sur double/float : AreEqual(0.1 + 0.2, 0.3) échoue (0.300000004 vs 0.3).
  • Mock non Verified : Couvre interactions manquées ; toujours Verify() post-act.
  • Tests async sans await : Exceptions silencieuses ; utilisez Assert.ThrowsAsync.

Pour aller plus loin

  • Documentation officielle : NUnit GitHub
  • Moq avancé : Moq Quickstart
  • CI/CD : Intégrez à GitHub Actions avec dotnet test --logger "trx"
  • Formations expertes : Découvrez nos formations .NET avancées chez Learni pour TDD et architecture clean.
  • Outils : Coverlet pour coverage, FluentAssertions pour assertions plus lisibles.