Passer au contenu du pied de page
Iron Academy Logo
Apprendre le C#
Apprendre le C#

Autres catégories

Assertions Fluent dans les tests unitaires en C#

[[academy-video-youtube({"vid": "TytferBCLOo", "start_time": "0", "title": "Fluent Assertions in Unit Testing in C#", "creator": "Tim Corey", "length": "10m 6s"})]]

Les tests unitaires sont écrits une fois et lus plusieurs fois, ce qui rend la ligne d'assertion l'un des détails les plus importants d'une suite de tests. Assert.Equal(expected, actual) fonctionne, mais un message d'erreur qui compare simplement deux chaînes n'explique pas toujours ce que le test vérifiait. Fluent Assertions remodèle la syntaxe d'assertion pour que la ligne se lise comme la règle qu'elle impose, et le message d'échec devient une phrase plutôt qu'une différence.

Dans sa vidéo "Fluent Assertions dans les tests unitaires en C#", Tim Corey prend un projet xUnit qui teste déjà un SampleClass parcours heureux, puis introduit un cas limite (Eddie Van Halen) qui casse la logique simple de séparation par espace. Il installe le package Fluent Assertions, réécrit l'assertion en utilisant la chaîne .Should().Be(), montre comment chaîner plusieurs conditions sur une seule valeur, et termine par AssertionScope de sorte qu'un seul test signale chaque condition échouée à la fois au lieu de quitter à la première. Les équipes dont la suite de tests a dépassé le point où les différences d'égalité pure sont claires trouveront ici le chemin pour une mise à jour.

Le point de départ d'xUnit

[0:35 - 2:46] Le projet à l'écran est petit : une bibliothèque de classes avec un seul SampleClass qui prend un nom complet dans son constructeur et le sépare par l'espace en FirstName et LastName, ainsi qu'un projet de tests xUnit qui teste le parcours heureux. Les tests utilisent la théorie avec des attributs InlineData pour exécuter la même logique sur deux entrées ('Tim Corey' et 'Sue Storm'), et chaque assertion est de la forme standard Assert.Equal(expected, actual).

[Theory]
[InlineData("Tim Corey", "Tim")]
[InlineData("Sue Storm", "Sue")]
public void TestFirstNameProperty(string fullName, string expected)
{
    var sample = new SampleClass(fullName);
    Assert.Equal(expected, sample.FirstName);
}
[Theory]
[InlineData("Tim Corey", "Tim")]
[InlineData("Sue Storm", "Sue")]
public void TestFirstNameProperty(string fullName, string expected)
{
    var sample = new SampleClass(fullName);
    Assert.Equal(expected, sample.FirstName);
}

L'exécution de la suite donne six tests réussis. Il n'y a rien de mauvais là-dedans, et pour les cas qui prennent la forme "prénom espace nom", les assertions se lisent assez clairement. Le territoire intéressant commence lorsque le nom ne correspond pas à cette forme.

Où le chemin de bonheur cesse de fonctionner

[2:46 - 4:32] Le cas limite que Tim choisit est "Eddie Van Halen", un nom en trois mots où le nom de famille est "Van Halen", pas seulement le jeton après le premier espace. L'implémentation actuelle attrape l'index 1 de la division et l'appelle nom de famille, donc la classe renvoie "Van" pour le nom de famille et ignore complètement "Halen". Le bug est réel, et écrire un test échoué pour cela est la première étape pour le réparer.

Écrire ce test avec Assert.Equal("Van Halen", sample.LastName) fonctionne, mais le message d'erreur ressemble à une comparaison de chaînes sans contexte. Basculer vers Fluent Assertions permet au même test d'exprimer la règle en mots et de produire un message d'erreur qui nomme la propriété testée. Pour une petite suite, la différence semble cosmétique ; pour une suite de quelques centaines d'assertions, la différence de lisibilité se multiplie.

Installing Fluent Assertions

[4:32 - 5:10] The package lives on NuGet under FluentAssertions. Tim le signale comme l'un des paquets les plus téléchargés sur le registre, ce qui est un contexte utile : tout nouvel contributeur est statistiquement susceptible de l'avoir déjà vu. Le projet de test obtient deux directives using en haut :

using FluentAssertions;
using FluentAssertions.Execution;
using FluentAssertions;
using FluentAssertions.Execution;

La première apporte les méthodes d'extension d'assertion, ce dont la plupart des tests ont besoin. La deuxième existe pour le type AssertionScope couvert plus tard dans la vidéo; les tests qui ne groupent pas les assertions peuvent s'en passer.

La syntaxe Should.Be

[5:10 - 6:14] La récriture minimale avec Fluent Assertions du test Van Halen :

[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");
    sample.LastName.Should().Be("Van Halen");
}
[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");
    sample.LastName.Should().Be("Van Halen");
}

La chaîne se lit presque comme de l'anglais parlé. L'extension .Should() retourne un objet d'assertion dont les méthodes décrivent la comparaison; .Be(...) effectue une vérification d'égalité. La sensibilité à la casse, les espaces blancs en tête et en queue font tous partie de cette vérification, qui correspond au comportement de la plupart des comparaisons de chaînes dans le code de production.

Exécuter ce test contre l'implémentation défectueuse produit un message d'erreur qui nomme la propriété et explique l'écart : 'Attendu que sample.LastName soit 'Van Halen' avec une longueur de 9, mais 'Van' a une longueur de 3.' Le message identifie la valeur testée par l'expression qui l'a produite, ce qui est le genre de contexte que la forme brute Assert.Equal ne fournit pas.

Chainer plusieurs conditions

[6:14 - 8:10] Une seule propriété a souvent besoin de satisfaire plus d'une règle. Fluent Assertions compose les conditions avec .And afin que la chaîne reste sur une seule instruction :

sample.LastName.Should()
    .StartWith("Van")
    .And.EndWith("len")
    .And.Contain(" ");
sample.LastName.Should()
    .StartWith("Van")
    .And.EndWith("len")
    .And.Contain(" ");

Chaque lien dans la chaîne est une condition distincte. Par défaut, la chaîne cesse de signaler à la première erreur : si StartWith("Van") passe mais EndWith("len") échoue, le message d'erreur nommera EndWith et l'assertion s'arrête là. Pour les tests où chaque condition est indépendante et que vous souhaitez faire apparaître chaque échec, la portée des assertions (section suivante) modifie ce comportement.

La syntaxe de chaînage penche vers l'expression des intentions plutôt que vers le dénombrement des égalités. Un test qui dit "le nom de famille devrait commencer par Van, se terminer par len et contenir un espace" se lit comme une spécification. La même logique écrite avec trois appels Assert.True séparés se lirait comme trois vérifications booléennes sans récit les reliant.

Signalement de chaque échec avec les portées d'assertion

[8:10 - 9:34] Le comportement par défaut de s'arrêter à la première défaillance est acceptable pour les chaînes où les conditions ultérieures dépendent des premières. Pour les chaînes où chaque condition compte indépendamment, envelopper les assertions dans un AssertionScope fait que le test signale chaque échec en une seule exécution :

[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");

    using var _ = new AssertionScope();
    sample.LastName.Should().StartWith("Van");
    sample.LastName.Should().EndWith("len");
    sample.LastName.Should().Contain(" ");
}
[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");

    using var _ = new AssertionScope();
    sample.LastName.Should().StartWith("Van");
    sample.LastName.Should().EndWith("len");
    sample.LastName.Should().Contain(" ");
}

La ligne using-var-discard est le modèle standard : la portée dure jusqu'à ce que la variable sorte du champ à la fin de la méthode, et l'underscore de rejet signale que la variable elle-même n'est jamais lue. Lorsque le test échoue, le message contient chaque condition défaillante sous forme de ligne distincte, ce qui signifie qu'une seule exécution donne toute l'histoire au lieu de forcer trois exécutions pour faire apparaître trois problèmes. Pour les tests qui vérifient la forme d'un objet retourné sur plusieurs propriétés, cela fait la différence entre corriger un bug par cycle de test et les corriger tous à la fois.

Conclusion : des tests qui se lisent comme des spécifications

[9:34 - 10:06] Fluent Assertions ne modifie pas ce que les tests vérifient ; il change la façon dont la vérification se lit. La chaîne .Should(), la composition .And et le groupement AssertionScope rapprochent le code de test d'une spécification écrite du comportement testé. Combiné avec le même type de style fluide utilisé par FluentValidation pour la validation d'entrée, le modèle produit des suites de tests et des règles de validation que les nouveaux arrivants peuvent lire sans besoin de visite guidée.

Conclusion

[9:34 - 10:06] Ajouter Fluent Assertions à un projet de test requiert une installation NuGet, deux directives using, et une réécriture de la ligne d'assertion de Assert.Equal(expected, actual) à actual.Should().Be(expected). Les chaînes expriment des règles à conditions multiples en une seule déclaration, et AssertionScope fait qu'un seul test signale chaque échec plutôt que de s'arrêter au premier. L'avantage est des messages d'erreur qui expliquent la règle qui a échoué, ce qui est la différence entre une trace de pile et une phrase.

Conseil d'exemple : Lors de l'assertion sur des collections, préférez result.Should().BeEquivalentTo(expected) à la comparaison propriété par propriété. Il parcourt le graphe d'objets pour vous et signale la première propriété divergente par chemin (par exemple, users[2].Address.City), ce qui est bien plus utile qu'un message 'les collections ne sont pas égales' lorsqu'une liste de 50 éléments est en désaccord sur un champ imbriqué.

Regardez la vidéo complète sur sa chaîne YouTube et obtenez plus d'informations sur l'écriture de tests C# lisibles et maintenables dans la série de formation de 10 minutes.

Hero Worlddot related to Assertions Fluent dans les tests unitaires en C#
Hero Affiliate related to Assertions Fluent dans les tests unitaires en C#

Gagnez plus en partageant ce que vous aimez

Vous créez du contenu pour les développeurs travaillant avec .NET, C#, Java, Python ou Node.js ? Transformez votre expertise en revenu supplémentaire !

Équipe de soutien Iron

Nous sommes en ligne 24 heures sur 24, 5 jours sur 7.
Chat
Email
Appelez-moi