Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Fathi Bellahcene

.Net m'a tuer!

[Fakes Framework] : Comment tester unitairement le code Legacy Part 2/4

 

Vous trouverez la première partie ici.

On doit bien garder en tête notre stratégie: intégrer des tests avec les Shims sans toucher au code existant, le re-factorer pour améliorer sa qualité et ensuite remplacer les Shims par des Stubs.

Dans cette partie, on va:

  1. présenter l’exemple que l’on va tenter de tester.
  2. présenter le system under test (SUT) et les couplages que nous allons traiter avec le Fakes Framework.
  3. Définir les scénarï de tests et isoler le SUT sans modifier le code existant avec les Shims.

 

l’exemple:

Trouver un exemple ne fut pas un chose simple: je devais trouver quelque chose de simple à expliquer, accessible à tous mais qui doit montrer les principaux problèmes rencontrés dans la vraie vie et tout ca en très peu de lignes de code.

il existe plusieurs problèmes qui rendent un code non testable ou difficilement testable, mais le problème qui à mon avis est le plus important c’est le couplage.

il existe plusieurs types de couplages (comprendre plusieurs types d’objets avec lesquelles notre système à tester est couplé). J’ai donc choisis d’en montrer quelques uns :

  • le couplage liée à la construction de l’objet: pour tester une méthode, encore faut-il construire un objet et on est donc obligatoirement dépendant de tout ce qui est nécessaire à la construction de notre objet…utile ou pas.
  • le couplage standard sur des classes externes: notre système à tester (SUT) fait appel à une classe externe pour sous traiter une partie du traitement.
  • le couplage avec des classes/méthodes statiques et donc quelque part avec des singleton, des factory… : un grand débat entre architectes puristes du GoF vs puristes du TDD.

Je vous propose donc l’application suivante :

une petite application WPF qui va récupérer dans le fichier de config le chemin complet d’un fichier, ensuite elle va faire quelques vérification sur ce fichier et en afficher le contenu mis en forme.

le fichier de config est le suivant:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
    <SupportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>

  <appSettings>
    <add key="fileName" value="c:\test\DemoFakesFramework.txt"/>
  </appSettings>
</configuration>

le fichier contient le texte suivant: “Fakes Framework Demo!”

Graphiquement cela donne (après chargement du fichier) :image

Et le code du bestiaux:

   public partial class MainWindow : Window
    {
        public FileNameValidator FileNameValidator { get; set; }
        
        public MainWindow()
        {
            InitializeComponent();
            FileNameValidator = new FileNameValidator();

        }


        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
           TxtFileContent.Text  = GetFileContent();
        }

        public string GetFileContent()
        {
            var fileName = ConfigurationManager.AppSettings["fileName"];


            if (!FileNameValidator.IsValid(fileName))
                throw new SettingsPropertyNotFoundException("pas de param!");

            if (!File.Exists(fileName))
                throw new FileNotFoundException("pas de fichier!");

            var text = File.ReadAllText(fileName);
            text = string.Format("Le fichier contient le texte suivant: {0}",
                                     text.ToUpper());

            return text;
        }
    }
 
la classe en charge de valider le nom du fichier est très simple:
 
    public class FileNameValidator
    {

        public bool IsValid(string fileName)
        {
            return !string.IsNullOrEmpty(fileName) 
                        && fileName.Length>2;
        }
    }

Elle se contente de vérifier que le nom n’est pas nul et qu’il contient plus de deux caractères.

Rien de bien compliqué: tout le traitement est fait dans la méthode GetFileContent, on récupère le paramètre dans le fichier de config, on le valide et on charge le contenu du fichier en majuscule dans une variable auquel on ajoute “Le fichier contient le texte suivant” au début et que l’on renvoi dans la foulée.

SUT & couplages

Le SUT est la méthode GetFileContent dont on a expliqué le fonctionnement ci-dessus.

on va donc recenser les différents couplage que nous devrons adresser avec le Fakes Framework pour savoir quelles sont les Fakes assemblies que nous allons avoir à générer.

On a donc un couplage avec :

  • la méthode InitializeComponent() qui se trouve dans le constructeur et qui n’est pas utile pour notre SUT.Elle se trouve dans la même assembly que notre SUT on aura donc aucune Fakes Assembly à générer de plus.
  • La classe FileNameValidator utilisée pour valider le nom de notre fichier. Elle se trouve dans la même assembly que notre SUT on aura donc aucune Fakes Assembly à générer de plus.
  • la classe ConfigurationManager pour récupérer la valeur de configuration –> on va devoir générer la Fakes Assembly pour System.Configuration.
  • la classe File pour valider la présence du fichier et lire son contenu –> on va devoir générer une Fakes Assembly pour System.dll (qui contient System.IO).

On va donc devoir génerer des Fakes pour les assemblies suivantes:

  • WpfApplication1 (celle contenant notre application)
  • System.Configuration.dll
  • System.dll

la génération des Fakes assemblies se fait très simplement et a  été présenté dans des posts précédents.

Les Shims entrent en actions!

On identifie aussi trois scénarios de tests pour être sûr que notre méthode fonctionne bien. Pour chacun d’entre eux, nous allons créer le test unitaire permettant de s’assurer qu’il est bien respecté.

  • La clé de configuration n’existe pas ou est nulle. Dans ce cas, on doit avoir une exception de type SettingsPropertyNotFoundException.
  • le fichier n’existe pas. on doit avoir une exception de type FileNotFoundException.
  • Le fichier existe et tout est ok: la clé de config est bonne et le fichier existe bien. On s’assure également que le text renvoyé est correctement formaté.

Avec ces trois tests, on doit avoir une couverture de code maximale pour notre SUT. Bien évidement, on ne doit en aucun cas toucher au code existant car on ne maitrise pas les effets de ces modifications avec du code Legacy.

Test 1: La clé de config est mauvaise

On va devoir utiliser les Shims pour tout ce qui est utiliser avant la validation du nom du fichier: le constructeur (du moins la partie InitialiseComponent et la classe FileNameValidator):

 

        [TestMethod]
        [ExpectedException(typeof(SettingsPropertyNotFoundException))]
        public void ConfigKeyIsEmpty()
        {

            using (ShimsContext.Create())
            {

                ShimMainWindow.AllInstances.InitializeComponent = 
                           window => { };
                ShimConfigurationManager.AppSettingsGet = () =>
                {
                    var dictionary = new NameValueCollection() { };
                    dictionary.Add("fileName", "filenameisOK");

                    return dictionary;
                };
                ShimFileNameValidator.AllInstances.IsValidString = 
                          (validator, s) => false;
                

                var mw = new MainWindow();
                mw.GetFileContent();

            }
        }

Pour rappel, on utilise la classe ShimsContext pour délimiter un block dans lequel les shims seront opérationnels: dans ce block, tout appel à une méthode redéfinie par un Shims sera intercepté et redirigé. il est donc très important de TOUJOURS utiliser un block using pour éviter des interférences entres les différents tests.

Je commence par redéfinir la méthode InitializeComponent: celle-ci fait appel à du code géneré servant uniquement la partie graphique: il n’est donc pas utile de l’avoir dans notre code (nous ne sommes même pas sûr que cela fonctionne dans un test unitaire). je l’a remplace par une méthode qui ne fait rien:

ShimMainWindow.AllInstances.InitializeComponent = window => { };

ensuite, je remplace l’appel au dictionnaire (singleton) AppSettings qui se trouvent dans la classe ConfigurationManager. je le remplace par un dictionnaire contenant une seul entrée  : “fileName” avec la valeur “filenameisOK”.

ShimConfigurationManager.AppSettingsGet = () => { var dictionary = new NameValueCollection() { }; dictionary.Add("fileName", "filenameisOK"); return dictionary; };

Si on exécute le code de validation pour cette valeur, celle-ci est bonne (non nulle et plus de deux caractères) mais on va également redéfinir la méthode IsValid pour que celle-ci renvoi toujours false: ce qui correspond à notre scénario de test:

ShimFileNameValidator.AllInstances.IsValidString = 
                          (validator, s) => false;

Si j’exécute le tests, j’obtiens bien une exception de type

SettingsPropertyNotFoundException: nous avons fini notre premier test!

 
test 2 : Le fichier n’existe pas
Nous allons reprendre le premier test avec quelques modification: tout d’abord le nom 
de fichier sera valide et ensuite et ensuite nous signifierons au SUT qu’il n’existe pas: Pour cela, 
nous allons utiliser un Shims sur la classe File.
  [TestMethod]
        [ExpectedException(typeof(FileNotFoundException))]
        public void FileNotExist()
        {

            using (ShimsContext.Create())
            {


                ShimMainWindow.AllInstances.InitializeComponent =
                       window => { }; 
                ShimConfigurationManager.AppSettingsGet = () =>
                {
                    var dictionary = new NameValueCollection() { };
                    dictionary.Add("fileName", "");

                    return dictionary;
                };
                ShimFileNameValidator.AllInstances.IsValidString = 
                          (validator, s) => true;

                ShimFile.ExistsString = s => false ;
 

                var mw = new MainWindow();
                mw.GetFileContent();

            }
        }
Test 3 : Tout est ok!
on reprend le test pécédent et on dit que le fichier existe, qu’il nous renvoi une valeur (via le 
Shims sur la classe File) et on vérifie le formatage :
 
 [TestMethod]
        public void FileExistAndConfigExist()
        {

            using (ShimsContext.Create())
            {


                ShimMainWindow.AllInstances.InitializeComponent =
                                       window => { };
                ShimConfigurationManager.AppSettingsGet = () =>
                   {
                      var dictionary = new NameValueCollection() {};
                      dictionary.Add("fileName", "");
                      return dictionary;
                    };

                ShimFileNameValidator.AllInstances.IsValidString = 
                              (@this,s) => true;

                ShimFile.ExistsString = s => true; 
                ShimFile.ReadAllTextString = s => "Fake content";


                var mw = new MainWindow();
                var calculatedValue = mw.GetFileContent();
                

                string retVal = "Le fichier contient le text suivant: "
                                  + "FAKE CONTENT";

                Assert.AreEqual(retVal, calculatedValue);

            }
        }

On exécute tout ca depuis VS 2012 et on obtient :

image
Des tests verts sans avoir modifié le code existant et…
image
 

un SUT couvert à 100%! le compte est bon!! Rendez-vous pour la partie la plus interresante de cette série de posts: la refactorisation du code et le remplacement des Shims par des Stubs.

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Posted: lundi 31 décembre 2012 12:11 par fathi

Commentaires

Pas de commentaires

Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01