Dans le chapitre précédent, nous avons commencé à vérifier les éléments de notre test grâce à un if
et à Assert.Fail
. Vous avez peut-être eu l'impression que ce n’était pas optimal.
Ce chapitre est là pour vous montrer tout ce que l’on peut faire avec des assertions.
Les différentes assertions
Pour vérifier que le test s’est bien comporté, on va en général comparer les résultats des méthodes ou objets aux valeurs que l’on est censé obtenir.
Telle propriété est égale à telle valeur, tel booléen doit être vrai, etc.
Cela se fait avec la classe Assert
de MSTest.
Assert.Fail()
, comme son nom l’indique, va faire échouer le test explicitement. Nous nous en sommes servi dans l’exemple précédent, mais il a des remplaçants bien plus pratiques.
Par exemple, pour remplacer ce test :
if (resultat != Resultat.Gagne)
Assert.Fail();
Nous pouvons simplement faire :
Assert.AreEqual(Resultat.Gagne, resultat);
Vous l’aurez deviné, AreEqual
va permettre de vérifier que les deux paramètres sont identiques.
Notre suite de trois vérifications est donc équivalente à :
// Assert
Assert.AreEqual(Resultat.Gagne, resultat);
Assert.AreEqual(1, jeu.Heros.Points);
Assert.AreEqual(15, jeu.Heros.PointDeVies);
C'est quand même bien plus lisible !
Il est également possible d’indiquer un message spécifique lorsque l’assertion n’est pas vérifiée, avec :
Assert.AreEqual(Resultat.Gagne, resultat, "Il faut absolument que le résultat soit gagné");
Dans le cas où il y a une erreur, alors le message apparaît dans l’explorateur de test :
De la même façon, il est possible de tester l’inégalité entre deux valeurs, la comparaison de références, si un booléen est vrai ou faux, et beaucoup d'autres éléments. Voyez, par exemple :
Assert.AreEqual(1, 1); // égalité entre entier
Assert.AreEqual(3.14, 6.28 /2 ); // égalité entre double
Assert.AreEqual("une chaine", "une " + "chaine"); // égalité entre chaînes
Assert.AreNotEqual(1, 2); // inégalité
Assert.IsFalse(1 == 2); // booléen vaut faux
Assert.IsTrue(1 <= 2); // booléen vaut vrai
Jeu jeu1 = new Jeu();
Jeu jeu2 = jeu1;
Assert.AreSame(jeu1, jeu2); // les références de l'objet sont identiques
jeu2 = new Jeu();
Assert.AreNotSame(jeu1, jeu2); // les références ne sont pas identiques
Assert.IsInstanceOfType(jeu1, typeof(Jeu)); // comparaison de type
Assert.IsNotInstanceOfType(jeu1, typeof(De)); // différence de type
Assert.IsNotNull(jeu); // différence à null
jeu1 = null;
Assert.IsNull(jeu1); // comparaison à null
Toutes ces assertions sont correctes et illustrent les différents types de comparaisons qui existent dans le framework MSTest.
À noter que, dans la pratique, on se sert beaucoup de Assert.AreEqual
, ou Assert.IsTrue
, ou Assert.IsFalse
. La vérification de booléen nous permet de vérifier toutes sortes de choses à partir du moment où l'on est capable de faire l’opération booléenne en code :
Assert.IsTrue(4 > 5 || !false && !(!true) != false);
Oui, mon exemple ne sert à rien. 😉
Améliorez la lisibilité des assertions
Nous avons vu que le framework MSTest mettait à notre disposition de nombreuses possibilités de vérifications via la classe Assert
.
Mais nous pouvons faire encore plus lisible, et ceci très facilement. Il suffit d'ajouter une petite bibliothèque gratuite, que l’on trouvera sur le gestionnaire de package : Nuget.
Il s’agit de Fluent Assertions.
Ce package Nuget contient un ensemble de méthodes d’extensions nous permettant d’écrire nos assertions dans un style qui se rapproche du langage naturel.
Ici, cette bibliothèque améliore la lisibilité du code. Rappelez-vous : plus un code est lisible, plus il décrit facilement son intention. Il nous faut donc absolument cette bibliothèque !
Vous pouvez par exemple remplacer :
Assert.AreEqual(Resultat.Gagne, resultat);
par :
resultat.Should().Be(Resultat.Gagne);
Plutôt clair et lisible, non ?
Vous pouvez spécifier un message personnalisé également :
resultat.Should().Be(Resultat.Gagne, "Nonon, pas d'autres possibilités !");
Mais il y a d’autres types de comparaisons, par exemple pour tester la supériorité :
jeu.Heros.PointDeVies.Should().BeGreaterThan(0);
Et beaucoup d'autres encore ! Voici quelques exemples que je trouve sympathiques et qui parlent d’eux-mêmes, grâce à l’expressivité de la bibliothèque :
var valeur = -1;
valeur.Should().BeNegative();
Math.PI.Should().BeApproximately(3.14, 0.1);
valeur.Should().BeInRange(-5, 5);
"chaine".Should().Contain("i").And.Contain("e").And.NotStartWith("p");
jeu.Should().BeOfType<Jeu>().Which.Heros.PointDeVies.Should().Be(15);
string email = "nico@openclassrooms.com";
email.Should().Match("*@*.com");
DateTime.Now.Should().BeAfter(new DateTime(2000, 1, 1));
var dateDeLivraison = DateTime.Now.AddDays(3);
DateTime.Now.Should().BeAtLeast(2.Days()).Before(dateDeLivraison);
Je vous encourage à aller voir la documentation pour avoir une meilleure idée de l’étendue des possibilités.
Il est toujours intéressant d’améliorer la lisibilité de son code, et même de ses tests. Ce package est non seulement une aide pour cela, mais apporte aussi un gros lot de méthodes d’extensions pour faire des vérifications plus facilement. N’hésitez pas à en abuser.
Vérifiez les exceptions
Un cas particulier des vérifications est la vérification des exceptions.
Il peut arriver que l’on veuille s’assurer que, dans une situation limite, une exception est bien levée.
Nous pourrions envisager de faire :
int entier = 0;
int autreEntier = 5;
try
{
int division = autreEntier / entier;
Assert.Fail();
}
catch (DivideByZeroException ex)
{
ex.Message.Should().NotBeNullOrEmpty();
}
Cela fonctionnerait.
Assert.Fail
nous assure que nous passons bien dans le catch
, car sinon, le test échouerait en arrivant sur la ligne 7.
Mais il y a plus simple. Il existe un attribut spécifique pour vérifier qu’un test lève bien une exception :
[TestMethod]
[ExpectedException(typeof(DivideByZeroException))]
public void VerifieLaDivisionParZero()
{
int entier = 0;
int autreEntier = 5;
int division = autreEntier / entier;
}
Il s’agit de l’attribut ExpectedException
que l’on doit renseigner avec le type de l’exception qui doit être levée. Dans cet exemple, le test passe.
Alors que, si je remplace entier par 1, j’aurai :
Avec FluentAssertions, c’est un peu plus compliqué pour reproduire ce même exemple :
[TestMethod]
public void VerifieLaDivisionParZero()
{
int entier = 0;
int autreEntier = 5;
Action division = () => { int resultat = autreEntier / entier; };
division.Should().Throw<DivideByZeroException>().WithMessage("Tentative de division par zéro.");
}
Dans ce cas précis, on aura plutôt intérêt à utiliser l'attribut ExpectedException
. Mais l'écriture de FluentAssertions peut prendre tout son sens lors de l’appel d’une méthode qui doit lever une exception :
[TestMethod]
public void VerifieException()
{
new Test().Invoking(t => t.VaLeverUneException()).Should().Throw<InvalidOperationException>().WithMessage("Impossible de faire ça !");
}
public class Test
{
public void VaLeverUneException()
{
throw new InvalidOperationException("Impossible de faire ça !");
}
}
En effet, il faut que vous testiez votre code aux limites, histoire de voir que tout est bien pris en compte et qu’une valeur inhabituelle ne fait pas planter votre application.