• 12 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 16/12/2019

Utilisez le framework de test MSTest

Alors, nous avons écrit (ou reçu) un code complet, qui semble simple et fonctionnel. Mais je suis sûr que, comme moi, vous débordez d’idées pour l’améliorer afin de créer la futur "killer app" qui nous rendra tous millionnaires. 😝

Sauf que, si nous nous lançons tête baissée dans un nouveau développement, nous faisons peser au-dessus de nos têtes une insidieuse épée de Damoclès prête à nous tomber dessus dès la moindre régression. 😱

Vous avez envie de garder votre tête ? Cela tombe bien, moi aussi !

Je vous propose donc d’écrire des tests unitaires. Découvrons tout de suite MSTest.

Créez un projet de test unitaire

Pour pouvoir utiliser MSTest, la première chose à faire est d’ajouter un projet de tests unitaires à notre application. Pour ce faire, utilisez le clic-droit sur la solution pour ajouter un nouveau projet :

Ajouter un nouveau projet
Ajouter un nouveau projet

Et choisissez un projet de type test unitaire (.NET Framework) :

Ajout d'un projet de test unitaire
Ajout d'un projet de test unitaire

J’ai donné le nom Jeu2.UnitTests comme nom de projet afin de rendre explicite le fait que ce soit un projet de test, qui teste le  Jeu2  de manière unitaire.

Vous pouvez voir que Visual Studio nous génère un squelette de classe :

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
    }
}

Nous pouvons déjà y voir un attribut  [TestClass]  au-dessus de la définition de la classe.

Il s’agit d’un attribut spécifique au framework de test MSTest qui permet de marquer la classe comme étant une classe contenant des tests automatisés. Cela va permettre au framework d’identifier les tests qu’il devra exécuter.

L’attribut  [TestMethod]  fait à peu près la même chose et permet d’indiquer que la méthode est une méthode de test.

Ouvrez la fenêtre de l’explorateur de tests. Si vous ne la voyez pas, il faut aller dans le menu Test, puis Fenêtres, Explorateur de tests :

Afficher la fenêtre de l'explorateur de tests
Afficher la fenêtre de l'explorateur de tests

Vous verrez alors notre test, qui s’appelle TestMethod1 , avec un point d’exclamation bleu indiquant qu’il n’a pas encore été exécuté :

Notre test qui n'a pas encore été exécuté
Notre test qui n'a pas encore été exécuté

Vous pouvez cliquer sur “Exécuter tout” afin de démarrer tous les tests ; vous aurez :

Le test est passé
Le test est passé

La coche verte indique bien sûr que votre test passe, ce qui est le but à atteindre ; la terre promise de tout développeur !

Bon, vu qu'on ne teste rien pour l'instant, cela ne peut que passer. ☺️

Modifiez la méthode de test pour avoir :

[TestMethod]
public void TestMethod1()
{
    throw new NotImplementedException();
}

Relancez les tests, et vous aurez cette fois :

Le test en échec
Le test en échec

Cette fois-ci - et de manière tout à fait surprenante -, la croix rouge indique que le test a échoué. Si l’on clique sur le test, nous aurons également des détails sur les raisons de l’échec. En l'occurrence, nous avons eu ici une exception.

À noter que nous pouvons également démarrer nos tests en mode debug, ce qui est très pratique pour analyser un test qui est en échec :

Nous pouvons déboguer les tests
Nous pouvons déboguer les tests

D’autres attributs existent

Sachez que  [TestClass]  et   [TestMethod]  ne sont pas les seuls attributs utilisables avec le framework MSTest. Il en existe plusieurs, et pour vous donner envie de les découvrir, laissez-moi vous en présenter deux autres : les attributs  TestInitialize  et  TestCleanup .

Ils permettent de décorer des méthodes qui seront appelées respectivement avant et après chaque test. C'est l'endroit idéal pour factoriser des initialisations ou des nettoyages dont dépendent tous les tests :

[TestClass]
public class UnitTest1
{
    [TestInitialize]
    public void InitialisationDesTests()
    {
        // ajouter les initialisations
    }

    [TestMethod]
    public void MonTest()
    {
        // test à faire
    }

    [TestCleanup]
    public void NettoyageDesTests()
    {
        // nettoyer les variables, ...
    }
}

Vous pouvez retrouver les autres attributs sur cette page  ; ce sont les classes qui finissent par  Attribute .

Cycle de vie d’un test

Nous sommes presque prêts à écrire un test pour notre jeu. Mais comment l’écrire proprement ?

En général, un test se décompose en trois partie, suivant le schéma « AAA », qui correspond aux mots anglais « Arrange, Act, Assert », que l’on peut traduire en français par « Arranger, Agir, Auditer ».

  • Arranger : il s’agit dans un premier temps de définir les objets, les variables nécessaires au bon fonctionnement de son test (initialiser les variables, initialiser les objets à passer en paramètres de la méthode à tester, etc.).

  • Agir : ensuite, il s’agit d’exécuter l’action que l’on souhaite tester (en général, exécuter la méthode que l’on veut tester, etc.)

  • Auditer : et enfin de vérifier que le résultat obtenu est conforme à nos attentes.

Il est temps de pratiquer un peu et pour cela, nous allons écrire une méthode qui teste le tour de jeu ; à savoir la méthode  Tour  de la classe  Jeu  . Le but est de vérifier que le héros gagne bien si son lancer de dés est supérieur à celui du monstre ; et inversement, que le joueur perd des points de vie lorsque le lancer de dés du monstre est meilleur que celui du héros.

Nous pouvons écrire :

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        // Arrange
        Jeu jeu = new Jeu();

        // Act
        var resultat = jeu.Tour(6, 1);

        // Assert
        if (resultat != Resultat.Gagne)
            Assert.Fail();
        if (jeu.Heros.Points != 1)
            Assert.Fail();
        if (jeu.Heros.PointDeVies != 15)
            Assert.Fail();
    }

    [TestMethod]
    public void TestMethod2()
    {
        // Arrange
        Jeu jeu = new Jeu();

        // Act
        var resultat = jeu.Tour(5, 5);

        // Assert
        if (resultat != Resultat.Gagne)
            Assert.Fail();
        if (jeu.Heros.Points != 1)
            Assert.Fail();
        if (jeu.Heros.PointDeVies != 15)
            Assert.Fail();
    }

    [TestMethod]
    public void TestMethod3()
    {
        // Arrange
        Jeu jeu = new Jeu();

        // Act
        var resultat = jeu.Tour(2, 4);

        // Assert
        if (resultat != Resultat.Perdu)
            Assert.Fail();
        if (jeu.Heros.Points != 0)
            Assert.Fail();
        if (jeu.Heros.PointDeVies != 13)
            Assert.Fail();
    }
}

Vous remarquerez à chaque fois les trois phases AAA, illustrées avec le commentaire. Ce schéma vaut pour tous les tests.

La méthode  TestMethod1  permet de s’assurer que, si le joueur a un dé dont la valeur est supérieure à celle du dé de l’adversaire, alors il gagne. Il a un point et toujours 15 points de vie.

De la même façon,  TestMethod2  vérifie des éléments similaires avec un même jet de dés. Enfin,  TestMethod3  s’assure que le héros est bien capable de perdre.

Vous pouvez jouer la suite de tests et vous aurez les trois tests qui passent :

Les trois tests passent
Les trois tests passent

Avoir les tests qui passent est très bien, mais ce n’est pas suffisant. Il faut désormais modifier les valeurs de vérification pour s’assurer que les tests peuvent également échouer et qu’il n’y a pas de faux positifs.

Il arrive en effet que des tests ne vérifient rien du tout et en conséquence, soient verts.

Par exemple, le test suivant :

[TestMethod]
public void TestMethod4()
{
    // Arrange
    Jeu jeu = new Jeu();

    // Act
    var resultat = jeu.Tour(2, 4);
}

Vous voyez, j’exécute du code, mais je ne vérifie rien. Donc le test est également vert. Sauf que ce test est un faux positif qui vous fait croire que le test est fonctionnel, alors qu’en vrai, rien n’est testé. Supprimez-moi vite ce test contreproductif ! 😈

Pour ce faire, modifions par exemple  TestMethod1  pour avoir :

[TestMethod]
public void TestMethod1()
{
    // Arrange
    Jeu jeu = new Jeu();

    // Act
    var resultat = jeu.Tour(6, 1);

    // Assert
    if (resultat != Resultat.Perdu)
        Assert.Fail();
    if (jeu.Heros.Points != 1)
        Assert.Fail();
    if (jeu.Heros.PointDeVies != 15)
        Assert.Fail();
}

À la ligne 11, j’ai changé le test sur le résultat de la méthode  Tour() . Après avoir relancé les tests, je peux constater que le test échoue bien :

Le test est en erreur
Le test est en erreur

Il a donc un intérêt. Il ne vous reste plus qu’à vous empresser de remettre le test en état. 🙂

Given When Then

AAA n’est pas le seul schéma qui existe pour structurer ses tests. Given When Then (que l’on peut traduire par : “étant donné que … lorsque … alors …) est un schéma issu de la méthodologie BDD qui est souvent considérée comme une évolution de TDD.

L’idée est que le test puisse devenir compréhensible par des non-développeurs grâce à une formulation représentant le scénario à tester.

  • Given : décrit l’état du système avant que l’on démarre le comportement du scénario à tester. Il s’agit des préconditions du test.

  • When : correspond au comportement que l’on veut tester.

  • Then : sert à décrire les changements que l’on attend à la suite de l’exécution du comportement.

Vous voyez, ces trois phases sont assez proches de AAA.

En général, le schéma qui est retenu comporte une phase supplémentaire en fin de test, où l'on va remettre le système dans son état initial, tel qu’il était avant l’exécution du test.

Nommez correctement vos tests

Bon, avez-vous bien étudié nos précédents tests ? Ils vous paraissent bien ? Ne manque-t-il pas un petit quelque chose pour en faire de super tests ?

Je vous ai déjà indiqué qu’il faut nommer correctement ses projets de tests, mais ceci est valable également pour les classes de tests, ainsi que pour les méthodes de tests.

Vous serez d’accord avec moi pour dire que  TestMethod1  n’est pas un nom très explicite.

Il faut que vous fassiez l’effort de nommer correctement vos tests pour en rendre la lecture et la maintenance plus faciles.

Commençons par la classe de test. Il n'y a pas de règle de nommage obligatoire, mais il est intéressant d’avoir une norme pour s’y retrouver facilement. Je vous propose de nommer les classes de tests en commençant par le nom de la classe que l’on doit tester, suivie du mot Tests. Ce qui donne :  JeuTests .

Et pour les méthodes de tests ?

Ici aussi, il est intéressant de suivre une règle de nommage afin de pouvoir identifier rapidement l’intention de la méthode de test. Je vous propose un nommage de la forme suivante :

MethodeTestee_EtatInitial_EtatAttendu()

Nous pourrions donc renommer  TestMethod1  en :

public void Tour_AvecUnDeSuperieurAuSecond_RetourneGagneAvecUnPointEtSansPerdreDePointsDeVie()
{
}

Il est vrai que le nom de la méthode est un peu long, mais vous me remercierez plus tard quand cela vous aura fait gagner un temps précieux.

À noter que certains lanceurs de tests peuvent faire apparaître une description, saisie grâce à un attribut :

[TestMethod]
[Description("Etant donné un tour de jeu, lorsque j'ai un lancer supérieur au second, alors le résultat est gagné avec un point sans perdre de points de vie")]
public void Tour_AvecUnDeSuperieurAuSecond_RetourneGagneAvecUnPointEtSansPerdreDePointsDeVie()
{
}

Et voilà. Vous commencez à découvrir le framework de test MSTest. Continuons notre chemin et passons aux assertions.

Exemple de certificat de réussite
Exemple de certificat de réussite