Skip to main content

Tests Unitaires avec XUnit

Pour créer un nouveau projet de test avec le framework de tests XUnit :

dotnet new xunit -o "MonProjet.Test"

Test unitaire

Cas de test

Pour créer un test unitaire, créez une classe dans ce projet de test. Une classe correspond à une collection de test, chaque méthode est un cas de test. Pour créer un cas de test, ajoutez une méthode avec l'attribut [Fact] :

public class MaCollectionDeTests {

  [Fact]
  public void MonCasDeTest(){
  	
  }

}

Organisation et nommage

Les tests se déroulent en 3 étapes :

  • "Etant donné" (Given en anglais) : mette en place la situation (souvent les données) requises par le cas de test
  • "Lorsque" (When en anglais) : appel au code que l'on veut tester dans ce cas de test
  • "Ainsi" (Then en anglais) : vérifications (assertions) sur les résultats du tests

Il convient donc, pour avoir des tests lisible des les diviser entre ces trois étapes, ainsi que de nommer les méthode correspondant au cas de tests de façon à expliciter le contenu de ces étapes. Le pattern suivant est utilisé de manière général pour la nommage : Action_Given_Then. "Action" étant souvent le nom de méthode testée.

Exemple :

public class CalculatorTests {

  [Fact]
  public void Multiply_ValidOperands_ReturnsCorrectResult(){
  	// Given
    int leftOperand = 3;
    int rightOperand = 4;
    
    var calculator = new Calculator();
    
    // When
    int result = calculator.Multiply(leftOperand, rightOperand);
    
    // Then
    Assert.Equal(12, result);
  }
}  

Et avec un cas négatif, avec une vérification d'exception :


  [Fact]
  public void Divide_By0_Throws(){
  	// Given
    int leftOperand = 3;
    int rightOperand = 0;
    
    var calculator = new Calculator();
    
    // When
    Action act = () => calculator.Divide(leftOperand, rightOperand);
    
    
    // Then
    Assert.Throws<DivideBy0Exception>(act);
  }

Setup

Pour faire un setup commun à tous les cas de tests d'une collection, il suffit d'utilise le constructeur de la classe qui correspond à cette collection, il est appelé pour chaque cas de test.

Tests paramétrisés

Il est possible de faire des tests paramétrisés, avec l'attribut [Theory]. On peut passer les paramètres de plusieurs manières.

Avec l'attribut [InlineData]

L'attribut [InlineData] permet de passer des données à un test paramètrisé mais ne supporte que les constantes à la compilation (littéraux de types primitif) :

public class CalculatorTests {

  [Theory]
  [InlineData(3,4, 12)]
  [InlineData(2,3, 6)]
  [InlineData(3,5, 15)]
  [InlineData(4,4, 16)]
  public void Multiply_ValidOperands_ReturnsCorrectResult(int leftOperand, int rightOperand, int expectedResult){
  	// Given
    
    var calculator = new Calculator();
    
    // When
    int result = calculator.Multiply(leftOperand, rightOperand);
    
    // Then
    Assert.Equal(expectedResult, result);
  }
}  

Cela exécutera autant d'instant de cas de tests que de [InlineData], ce cas de test en est en fait quatres !

Avec TheoryData

TheoryData Permet de passer des données à un test paramètrisé qui ne sont pas des constantes de compilation, et ce de façon fortement typée.

public class CalculatorTests {

  public static TheoryData<int, int, int> Data =>
    new TheoryData<int, int, int>
        {
            { 1, 2, 3 },
            { -4, -6, -10 },
            { -2, 2, 0 },
            { int.MinValue, -1, int.MaxValue }
        };

  
  [Theory]
  [MemberData(nameof(Data))]
  public void Add_ValidOperands_ReturnsCorrectResult(int leftOperand, int rightOperand, int expectedResult){
  	// Given
    
    var calculator = new Calculator();
    
    // When
    int result = calculator.Add(leftOperand, rightOperand);
    
    // Then
    Assert.Equal(expectedResult, result);
  }
}  

Assertions

XUnit fourni un bon nombre de fonctions d'assertion, comme Assert.Equal ou Assert.Throws, mais certains librairies comme FluentAssertions existent pour faire des assertions un petit peu plus expressives :

string actual = "ABCDEFGHI";
actual.Should().StartWith("AB").And.EndWith("HI").And.Contain("EF").And.HaveLength(9);
IEnumerable<int> numbers = new[] { 1, 2, 3 };
numbers.Should().OnlyContain(n => n > 0);
numbers.Should().HaveCount(4, "because we thought we put four items in the collection");