SpecFlow et objets complexes

je suis en train d'évaluer SpecFlow et je suis un peu coincé.

Tous les échantillons que j'ai trouvés sont essentiellement avec des objets simples.

le projet sur lequel je travaille s'appuie fortement sur un objet complexe. Un échantillon proche pourrait être cet objet:

public class MyObject
{
    public int Id { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public IList<ChildObject> Children { get; set; }

}

public class ChildObject
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Length { get; set; }
}

est-ce que quelqu'un a une idée de comment un écrire mes fonctionnalités / scénarios où MyObject serait instancié à partir d'une étape "donnée" et utilisé dans "quand" et " puis" les étapes?

Merci d'avance

EDIT: Juste un coup à l'esprit: sont des tables imbriquées pris en charge?

20
demandé sur Ramunas 2011-04-26 14:24:09

7 réponses

pour l'exemple que vous avez montré, je dirais vous faites fausse route . Cet exemple semble plus approprié pour écrire avec nunit et probablement en utilisant un object mother . Les Tests écrits avec specflow ou un outil similaire doivent être orientés vers le client et utiliser la même langue que celle utilisée par votre client pour décrire la fonctionnalité.

19
répondu Lazydev 2011-04-26 18:38:25

je dirais que Marcus a à peu près raison ici, cependant j'écrirais mon scénario pour que je puisse utiliser certaines des méthodes d'extensions pour dans le TechTalk.SpecFlow.Aider l'espace de noms. Voir ici .

Given I have the following Children:
| Id | Name | Length |
| 1  | John | 26     |
| 2  | Kate | 21     |
Given I have the following MyObject:
| Field     | Value      |
| Id        | 1          |
| StartDate | 01/01/2011 |
| EndDate   | 01/01/2011 |
| Children  | 1,2        |

pour le code derrière les étapes que vous pourriez utiliser quelque chose comme cela sera un peu plus de traitement des erreurs dans elle.

    [Given(@"I have the following Children:")]
    public void GivenIHaveTheFollowingChildren(Table table)
    {
        ScenarioContext.Current.Set(table.CreateSet<ChildObject>());
    }


    [Given(@"I have entered the following MyObject:")]
    public void GivenIHaveEnteredTheFollowingMyObject(Table table)
    {
        var obj = table.CreateInstance<MyObject>();
        var children = ScenarioContext.Current.Get<IEnumerable<ChildObject>>();
        obj.Children = new List<ChildObject>();

        foreach (var row in table.Rows)
        {
            if(row["Field"].Equals("Children"))
            {
                foreach (var childId in row["Value"].Split(new char[]{','}, StringSplitOptions.RemoveEmptyEntries))
                {
                    obj.Children.Add(children
                        .Where(child => child.Id.Equals(Convert.ToInt32(childId)))
                        .First());
                }
            }
        }
    }

J'espère que ceci (ou une partie de cela) vous aidera

25
répondu stuartf 2012-04-26 11:39:28

je suggère que vous essayiez de garder vos scénarios aussi propres que possible, en se concentrant sur la lisibilité pour les personnes non techniques dans votre projet. Comment l'objet complexe graphiques sont construits sont ensuite traités dans votre démarche définitions.

cela dit, vous avez encore besoin d'un moyen d'exprimer les structures hiérarchiques dans vos spécifications, c'est-à-dire avec Gherkin. Pour autant que je sais que ce N'est pas possible et de ce post (dans le SpecFlow Google il semble que cela ait déjà été discuté auparavant.

fondamentalement, vous pourriez inventer un format de votre propre et analyser que dans vous étape. Je ne suis pas tombé sur cela moi-même, mais je pense que je voudrais essayer une table avec des valeurs Vierges pour le niveau suivant et analyser cela dans la définition de pas. Comme ceci:

Given I have the following hierarchical structure:
| MyObject.Id | StartDate | EndDate  | ChildObject.Id | Name | Length |
| 1           | 20010101  | 20010201 |                |      |        |
|             |           |          | 1              | Me   | 196    |
|             |           |          | 2              | You  | 120    |

ce n'est pas très joli, je l'admets, mais ça pourrait marcher.

une autre façon de le faire est d'utiliser des valeurs par défaut et de donner simplement les différences. Comme ceci:

Given a standard My Object with the following children:
| Id | Name | Length |
| 1  | Me   | 196    |
| 2  | You  | 120    |

dans votre définition étape, vous ajoutez ensuite les valeurs" standard " pour le MyObject et remplissez la liste des enfants. Cette approche est un peu plus lisible si vous me le demandez, mais vous devez "savoir" ce qu'est un MyObject standard et comment il est configuré.

en gros-Gherkin ne le supporte pas. Mais vous pouvez créer un format que vous pouvez traiter vous-même.

J'espère que cette réponse à votre question...

9
répondu Marcus Hammarberg 2011-04-26 11:13:57

je vais un peu plus loin quand mon modèle D'objet de domaine commence à devenir complexe, et je crée des" modèles de Test " que j'utilise spécifiquement dans mes scénarios SpecFlow. Un modèle D'essai devrait:

  • Être concentré sur la Terminologie des Affaires
  • vous permettent de créer des scénarios faciles à lire
  • fournissent une couche de découplage entre la terminologie commerciale et le modèle du domaine complexe

let's prenez un Blog comme exemple.

La SpecFlow Scénario: Création d'un Blog

envisager le scénario suivant écrit de sorte que quiconque connaissant la façon dont un Blog fonctionne sait ce qui se passe:

Scenario: Creating a Blog Post
    Given a Blog named "Testing with SpecFlow" exists
    When I create a post in the "Testing with SpecFlow" Blog with the following attributes:
        | Field  | Value                       |
        | Title  | Complex Models              |
        | Body   | <p>This is not so hard.</p> |
        | Status | Working Draft               |
    Then a post in the "Testing with SpecFlow" Blog should exist with the following attributes:
        | Field  | Value                       |
        | Title  | Complex Models              |
        | Body   | <p>This is not so hard.</p> |
        | Status | Working Draft               |

Ce modèle une relation complexe, où un Blog a de nombreux billets de Blog.

Le Modèle De Domaine

le modèle de domaine pour cette application de Blog serait ceci:

public class Blog
{
    public string Name { get; set; }
    public string Description { get; set; }
    public IList<BlogPost> Posts { get; private set; }

    public Blog()
    {
        Posts = new List<BlogPost>();
    }
}

public class BlogPost
{
    public string Title { get; set; }
    public string Body { get; set; }
    public BlogPostStatus Status { get; set; }
    public DateTime? PublishDate { get; set; }

    public Blog Blog { get; private set; }

    public BlogPost(Blog blog)
    {
        Blog = blog;
    }
}

public enum BlogPostStatus
{
    WorkingDraft = 0,
    Published = 1,
    Unpublished = 2,
    Deleted = 3
}

notez que notre scénario a un" statut "avec une valeur de" draft de travail", mais le BlogPostStatus enum a WorkingDraft . Comment traduire ce statut de" langage naturel " en enum? Maintenant, entrez le modèle de Test.

Le Modèle D'Essai: BlogPostRow

la classe BlogPostRow est destinée à faire quelques choses:

  1. Traduire votre SpecFlow table à un objet
  2. Mettez à jour votre modèle de domaine avec les valeurs données
  3. fournir un "constructeur de copie" pour semer un objet BlogPostRow avec des valeurs d'une instance de modèle de domaine existante afin que vous puissiez comparer ces objets dans SpecFlow

Code:

class BlogPostRow
{
    public string Title { get; set; }
    public string Body { get; set; }
    public DateTime? PublishDate { get; set; }
    public string Status { get; set; }

    public BlogPostRow()
    {
    }

    public BlogPostRow(BlogPost post)
    {
        Title = post.Title;
        Body = post.Body;
        PublishDate = post.PublishDate;
        Status = GetStatusText(post.Status);
    }

    public BlogPost CreateInstance(string blogName, IDbContext ctx)
    {
        Blog blog = ctx.Blogs.Where(b => b.Name == blogName).Single();
        BlogPost post = new BlogPost(blog)
        {
            Title = Title,
            Body = Body,
            PublishDate = PublishDate,
            Status = GetStatus(Status)
        };

        blog.Posts.Add(post);

        return post;
    }

    private BlogPostStatus GetStatus(string statusText)
    {
        BlogPostStatus status;

        foreach (string name in Enum.GetNames(typeof(BlogPostStatus)))
        {
            string enumName = name.Replace(" ", string.Empty);

            if (Enum.TryParse(enumName, out status))
                return status;
        }

        throw new ArgumentException("Unknown Blog Post Status Text: " + statusText);
    }

    private string GetStatusText(BlogPostStatus status)
    {
        switch (status)
        {
            case BlogPostStatus.WorkingDraft:
                return "Working Draft";
            default:
                return status.ToString();
        }
    }
}

C'est dans le privé GetStatus et GetStatusText où l'lisible par l'homme après le blog de valeurs d'état sont traduits pour les Énumérations, et vice versa.

(Divulgation: je connais un Enum n'est pas le cas le plus complexe, mais il est un outil facile à suivre)

La dernière pièce du puzzle est l'étape définitions.

utilisation de Modèles D'essai avec votre modèle de domaine dans les définitions de pas

l'Étape:

Given a Blog named "Testing with SpecFlow" exists

définition:

[Given(@"a Blog named ""(.*)"" exists")]
public void GivenABlogNamedExists(string blogName)
{
    using (IDbContext ctx = new TestContext())
    {
        Blog blog = new Blog()
        {
            Name = blogName
        };

        ctx.Blogs.Add(blog);
        ctx.SaveChanges();
    }
}

l'Étape:

When I create a post in the "Testing with SpecFlow" Blog with the following attributes:
    | Field  | Value                       |
    | Title  | Complex Models              |
    | Body   | <p>This is not so hard.</p> |
    | Status | Working Draft               |

définition:

[When(@"I create a post in the ""(.*)"" Blog with the following attributes:")]
public void WhenICreateAPostInTheBlogWithTheFollowingAttributes(string blogName, Table table)
{
    using (IDbContext ctx = new TestContext())
    {
        BlogPostRow row = table.CreateInstance<BlogPostRow>();
        BlogPost post = row.CreateInstance(blogName, ctx);

        ctx.BlogPosts.Add(post);
        ctx.SaveChanges();
    }
}

l'Étape:

Then a post in the "Testing with SpecFlow" Blog should exist with the following attributes:
    | Field  | Value                       |
    | Title  | Complex Models              |
    | Body   | <p>This is not so hard.</p> |
    | Status | Working Draft               |

définition:

[Then(@"a post in the ""(.*)"" Blog should exist with the following attributes:")]
public void ThenAPostInTheBlogShouldExistWithTheFollowingAttributes(string blogName, Table table)
{
    using (IDbContext ctx = new TestContext())
    {
        Blog blog = ctx.Blogs.Where(b => b.Name == blogName).Single();

        foreach (BlogPost post in blog.Posts)
        {
            BlogPostRow actual = new BlogPostRow(post);

            table.CompareToInstance<BlogPostRow>(actual);
        }
    }
}

( TestContext - une sorte de magasin de données persistant dont la durée de vie est le scénario actuel)

Modèles dans un contexte plus large

en prenant un peu de recul, le terme" modèle "est devenu plus complexe, et nous venons d'introduire encore un autre type de modèle. Voyons comment ils jouent tous ensemble:

  • Modèle du Domaine: Une classe de modélisation de ce que l'entreprise veut souvent être stockées dans une base de données, et contient la modélisation du comportement de l'entreprise des règles.
  • Modèle de Vue: Une présentation axée version de votre Modèle de Domaine
  • Objet de Transfert de Données: Un sac de données utilisé pour transférer des données à partir d'une couche ou d'un composant à un autre (souvent utilisé avec des appels de service web)
  • Modèle de Test: Un objet utilisé pour représentez les données de test d'une manière qui a du sens pour une personne d'affaires lisant vos tests de comportement. Traduit entre le modèle de domaine et le modèle D'essai.

vous pouvez presque penser à un modèle de Test comme un modèle de vue pour vos tests SpecFlow, avec la" vue " étant le scénario écrit dans Gherkin.

5
répondu Greg Burghardt 2018-07-29 18:38:12

j'ai travaillé dans plusieurs organisations qui ont tous couru dans le même problème que vous décrivez ici. C'est l'une des choses qui m'a incité à (tenter de) pour commencer à écrire un livre sur le sujet.

http://specflowcookbook.com/chapters/linking-table-rows /

ici, je suggère d'utiliser une convention qui vous permet d'utiliser les en-têtes de table specflow pour indiquer d'où viennent les éléments liés, comment identifier ceux que vous voulez, puis utilisez le contenu des lignes pour fournir les données pour "rechercher" dans les tableaux étrangers.

par exemple:

Scenario: Letters to Santa appear in the emailers outbox

Given the following "Children" exist
| First Name | Last Name | Age |
| Noah       | Smith     | 6   |
| Oliver     | Thompson  | 3   |

And the following "Gifts" exist
| Child from Children    | Type     | Colour |
| Last Name is Smith     | Lego Set |        |
| Last Name is Thompson  | Robot    | Red    |
| Last Name is Thompson  | Bike     | Blue   |

espérons que cela vous sera utile.

3
répondu user3202264 2014-01-16 11:24:30

une bonne idée est de réutiliser le modèle de convention de nommage standard MVC Model Binder dans une méthode de transformation progressive. Voici un exemple: est-il possible de lier un modèle sans mvc?

Voici une partie du code (juste l'idée principale, sans validation et de vos exigences supplémentaires):

dans features:

Then model is valid:
| Id  | Children[0].Id | Children[0].Name | Children[0].Length | Children[1].Id | Children[1].Name | Children[1].Length |
| 1   | 222            | Name0            | 5                  | 223            | Name1            | 6                  |

par étapes:

[Then]
public void Then_Model_Is_Valid(MyObject myObject)
{
    // use your binded object here
}

[StepArgumentTransformation]
public MyObject MyObjectTransform(Table table)
{
    var modelState = new ModelStateDictionary();
    var model = new MyObject();
    var state = TryUpdateModel(model, table.Rows[0].ToDictionary(pair => pair.Key, pair => pair.Value), modelState);

    return model;
}

ça marche m'.

bien sûr, vous devez avoir une référence au système.Web.Bibliothèque Mvc.

1
répondu Vetal 2017-05-23 11:54:59

en utilisant TechTalk.SpecFlow.Assist;

https://github.com/techtalk/SpecFlow/wiki/SpecFlow-Assist-Helpers

    [Given(@"resource is")]
    public void Given_Resource_Is(Table payload)
    {
        AddToScenarioContext("payload", payload.CreateInstance<Part>());
    }
0
répondu Gomes 2017-05-19 21:56:30