Friday, November 12, 2021

Règles sur le tests unitaires dans un projet x











------------ 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);

}