------------ TEXTE
Bonne pratiques
Structure des Triple A
• Arrange, Act, Assert
• Les sections du test doivent être indiquées par un commentaire
[Fact]
public void ExempleStructureTripleA_AvecCommentaires()
{
// Arrange
// Act
// Assert
}
Section Arrange
• Contient tout ce qui est nécessaire pour mettre en place les éléments nécessaires au test
o Création de l'élément de programme testé (SUT- ou System Under Test)
o Arguments
o Résultat attendu s'il y a lieu (pourrait aussi être fait dans la section Assert)
o Exécution d'autres méthodes ou fonction
Section Act
• Exécution de l'élément de programme testé
o Appel d'une méthode
o Exécution d'une fonction qui soulève une exception
o etc.
• En général une seule ligne de code
Section Assert
• Contient les assertions nécessaires à la vérification du comportement attendu
Quoi tester?
• En boîte noire, donc pas de test pour les méthodes privées, seulement les méthodes publiques
Comportement de l'élément de programme
• Comportement : Résultat produit par la fonctionnalité d'un élément de programme en fonction de certaines préconditions
o Préconditions : généralement l'intrant de l'élément de programme
o Résultat produit = Postconditions + effets de bord
Questions à se poser
• Quelle forme peut prendre l'intrant de l'élément de programme testé?
o Est-ce que toutes les valeurs permises ont été spécifiées?
• De quelle façon le succès ou l'échec est-il communiqué?
o Réaction aux intrants invalides
Recouvre?
Crash?
o Comportement inhabituel ou inattendu?
Assertions
Définition
• Ce sont des vérifications
• Est-ce que l'élément de programme se comporte tel qu'attendu?
o Est-ce que l'élément de programme fait ce qu'il est supposé faire?
o Est-ce que l'élément de programme ne fait PAS ce qu'il n'est pas
supposé faire?
• Vérifier le comportement
o Par la vérification du résultat produit, qui peut être
Une valeur de retour
Un changement à l'état du système (effet de bord)
Un appel à un contributeur
Quantité d'assertions nécessaire
• Idéalement : une seule assertion à la fin du test
• Les assertions multiples ajoutent de la complexité avec peu de valeur et rendent plus difficile l'identification du véritable problème
o Le test s'arrête dès la première assertion en échec
o Les assertions suivantes ne sont alors pas exécutées, se qui rend le diagnostique du problème incomplet
Quantité d'assertions nécessaire : Exceptions
Assertion de garde
• Une assertion de garde est une vérification de sécurité qui permet d'éviter d'avoir de la logique conditionnelle et protège des erreurs à l'exécution
• Exemples :
o Vérifier que le retour d'une méthode n'est pas null avant de faire d'autres assertions
o Vérifier le nombre d'éléments d'une collection avant d'accéder aux éléments
• Donc pas de if-else dans les tests!
Un concept sémantique qui n'est pas vérifiable par une seule assertion
• Par exemple, la vérification des éléments d'une collection pourrait nécessiter une assertion par élément
• Il existe des outils dans ce cas-ci
o FluentAssertions permet la vérification d'une collection avec une seule assertion, ainsi que CollectionAssert de MSTest
Noms des tests et gabarit
• Gherkin, style BDD
o ÉtantDonné_Lorsque_Alors
o Méthode_Contexte_RésultatAttendu
• Sous-classes de tests avec nom méthode testée
o Permet de renommer facilement si méthode renommée
o Organise les tests
public class SutTests
{
public class UneMethode
{
[Fact]
public void Contexte_RésultatAttendu()
{
\...
}
}
public class UneAutreMethode
{
[Fact]
public void Contexte_RésultatAttendu()
{
\...
}
}
}
Éléments à considérer
• Résistance au réusinage -\tenir au minimum l'utilisation des mocks
• Penser à la maintenabilité des tests : aussi important que le code de production
o Retour sur investissement vs coûts de maintenance des tests
o Cibler les parties les plus importantes de la base de code ->Domaine d'affaires
• Les mêmes règles de Clean Code / Bonnes pratiques s'appliquent ici!
o Code auto-documenté, clair, concis, expressif, etc.
o L'intention du test peut être comprise rapidement
Code ciblé
Domaine d'affaire
• Idéalement 100% de couverture de code
Infrastructure
• Repousser aux limites de la composante les I/O et effets de bord
• Couvrir les cas principaux (Happy path et Sad path)
Couverture de code
• Ne pas exclure de code de la couverture pour avoir la vérité en tout temps
Comportement vs interaction : Mocks
Pourquoi
• Pour isoler un élément de programme de ses dépendances sortantes
Quand
• Lorsque le contributeur est une dépendance sur laquelle nous n'avons pas le contrôle
o BD
o Service externe (SAGO, Moneris, etc) Système de fichier
o Librairie patrimoniale non
o etc.
Comment
• Principe : substituer la dépendance gênante pour un simulacre
o Si cette dépendance implémente une interface, doubler (mocker) cette interface
o Sinon, ajouter une couche d'abstraction supplémentaire devant cette dépendance, qui agira comme une sorte de proxy et qui deviendra la nouvelle dépendance de l'élément de programme testé
• Utiliser un framework d'isolation comme FakeItEasy
Bonnes pratiques
• Un seul mock par dépendance par test
• Une seule assertion sur le mock par test
o Vérifier soit le comportement, soit les interactions, mais pas les deux
• Éviter les new dans les objets gérés par injection de dépendances
• Repousser ces dépendances aux frontières du système (couche d'infrastructure) et garder le domaine d'affaires le plus pur possible
Tests paramétrés
• Ces tests sont pratiques pour spécifier par l'exemple
Exemple
private const string ouvertureSpan = "<span style=\"border-bottom: 4px solid #f19e8f; width: 6px">;
private const string fermetureSpan = "</span>";
public static IEnumerable<object>ScenariosTest() => new[]
{
new object[] { "", $"{ouvertureSpan}{fermetureSpan}" },
new object[] { " un deux", $"{ouvertureSpan}{fermetureSpan} un deux" },
new object[] { "t un deux", $"{ouvertureSpan}t{fermetureSpan} un deux" },
new object[] { "te un deux", $"{ouvertureSpan}te{fermetureSpan} un deux" },
new object[] { "test un deux", $"{ouvertureSpan}tes{fermetureSpan}t un deux" },
new object[] { "testtest un deux", $"{ouvertureSpan}tes{fermetureSpan}ttest un deux" },
};
[Theory, MemberData(nameof(ScenariosTest))]
public void SelonTexteEnEntree_VerifierChaineHtmlAttendue(string texte, string chaineHtmlAttendue)
{
var htmlHelper = CreerHtmlHelper();
var resultat = htmlHelper.SoulignerTroisPremieresLettres(texte);
resultat.Should().Be(chaineHtmlAttendue);
}