SpecFlow : Convertir des Tables en objets avec les AssistHelpers

# BDD# test# Agile# SpecFlow
écrit par Damien publié le vendredi 8 décembre 2017
Lorsqu'on implémente un scénario SpecFlow, on est vite confronté au problème de transformation des données fournies par les features en objet model. On se retrouve donc à faire le mapping champ par champ à la main. Pour faciliter cette étape fastidieuse, je vous propose de voir, comment en quelques lignes de code, on peut facilement réaliser ce travail.

Une implémentation classique

Prenons le step suivant :
Given les clients suivants
	| nom   | prenom | 
	| Bob   | Durant | 
	| Marie | Dupont |
On souhaite transformer la table en objet "Client" :
    public class Client
    {
        public string Nom { get; set; }
        public string Prenom { get; set; }
    }
La transformation la plus simple serait celle-ci :
        public List<Client> TransformTableToClient(Table clientTable)
        {
            var clients = new List<Client>();

            foreach (var row in clientTable.Rows)
            {
                clients.Add(new Client
                {
                    Nom = row["nom"],
                    Prenom = row["prenom"]
                });
            }

            return clients;
        }
Ici, nous n'avons que 2 propriétés, cependant, si nous avions un objet avec beaucoup de propriétés cette implémentation deviendrait vite lourde et pas très maintenable. Sans compter sur le fait que les "rows" sont de type string et qu'il faudrait donc faire un cast sur chaque propriété qui ne sont pas des string.

Les Assist Helpers

Pour simplifier la transformation des Tables en objet modèle, SpecFlow nous met à disposition un certain nombre d'extensions pour les Tables. Ces extensions sont situées dans le namespace "TechTalk.SpecFlow.Assist". Nous pouvons simplifier la transformation précédente de cette façon :
        public IEnumerable<Client> TransformTableToClient(Table clientTable)
        {
            var clients = clientTable.CreateSet<Client>().ToList();

            return clients;
        }
L'extension "CreateSet" :
  • Va mapper les données en faisant correspondre le nom des colonnes de l'objet Table (correspondant au nom des colonnes définies dans la feature) avec le nom des propriétés.
  • N'est pas sensible à la casse et gère les espaces dans le nom des colonnes (par exemple, une colonne "nom client" sera mappée sur la propriété "NomClient").
  • Si elle ne trouve pas de correspondance entre une colonne et une propriété, alors la colonne n'est pas mappée.
Si on veut mapper uniquement un objet, on peut utiliser "CreateInstance" qui fonctionne de la même façon que le "CreateSet".

Comment faire si je veux mapper une colonne sur une propriété avec un nom différent ?

Imaginons qu'on rajoute une colonne avec le numéro de la carte bancaire du client
Given les clients suivants
	| nom   | prenom | numero cb |
	| Bob   | Durant | 123456    |
	| Marie | Dupont | 789456    |
et que notre modèle ressemble à ceci
    public class Client
    {
        public string Nom { get; set; }
        public string Prenom { get; set; }
        public int NumCb  { get; set; }
    }
Lors de l'exécution du "CreateSet" la colonne "numero cb" sera ignorée et la propriété "NumCb" ne sera jamais peuplée. Pour résoudre ce problème, nous allons utiliser la fonctionnalité de renommage des colonnes
        public IEnumerable<Client> TransformTableToClient(Table clientTable)
        {
            clientTable.RenameColumn("numero cb", nameof(Client.NumCb));
            var clients = clientTable.CreateSet<Client>().ToList();

            return clients;
        }
Dans le code ci-dessus, on procède au renommage de la colonne "numero cb" en "NumCb". Grâce à cette opération, le "CreateSet" sera capable de peupler la propriété "NumCb".

Comment m'assurer que toutes mes colonnes sont mappées à une propriété de mon modèle ?

Pour s'assurer que son jeu de données est correctement initialisé, on peut utiliser l'extension "CompareToSet" qui va vérifier que les données des colonnes soient bien mappées à une propriété de notre modèle. En voici un exemple :
        public IEnumerable<Client> TransformTableToClient(Table clientTable)
        {
            clientTable.RenameColumn("numero cb", nameof(Client.NumCb));
            var clients = clientTable.CreateSet<Client>().ToList();

            clientTable.CompareToSet(clients);

            return clients;
        }
Si l'on retire la ligne de renommage de la colonne, alors le "CompareToSet" lèvera une exception, car toutes les valeurs des colonnes n'auront pas de correspondance dans le modèle. Le "CompareToSet" peut également s'utiliser durant la phase d'assertion d'un scénario.

En conclusion

Le namespace "TechTalk.SpecFlow.Assist" comporte de nombreuses extensions permettant de faciliter la traduction des données des features en objet. L'utilisation de ces méthodes, combiné avec les SpecArgumentTransformation permet d'avoir une phase Arrange plus lisible et maintenable.