SpecFlow i obiekty złożone

Oceniam SpecFlow i trochę utknąłem.
Wszystkie próbki, które znalazłem są w zasadzie z prostych obiektów.

Projekt, nad którym pracuję w dużej mierze opiera się na złożonym obiekcie. Zbliżoną próbką może być Ten obiekt:

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

Czy ktoś ma jakiś pomysł, jak napisać moje funkcje / scenariusze, w których MyObject byłyby utworzone z "danego" kroku i użyte w krokach" When" I "Then"?

Z góry dzięki

EDIT: Just a shot in mind: are zagnieżdżone tabele obsługiwane?

Author: Ramunas, 2011-04-26

7 answers

Dla przykładu, który pokazałeś powiedziałbym , że źle to robisz . Ten przykład bardziej nadaje się do pisania z nunit i prawdopodobnie za pomocą obiekt matka. Testy pisane za pomocą specflow lub podobnego narzędzia powinny być skierowane do klienta i używać tego samego języka, jakiego klient użyłby do opisania funkcji.

 19
Author: Lazydev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-26 18:38:25

Powiedziałbym, że Marcus jest tu całkiem poprawny, jednak napisałbym mój scenariusz, aby móc użyć niektórych metod rozszerzeń w TechTalk.SpecFlow.Assist namespace. Zobacz TUTAJ .

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        |

Dla kodu za kroki można użyć czegoś takiego będzie nieco więcej obsługi błędów w nim.

    [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());
                }
            }
        }
    }

Mam nadzieję, że to (lub niektóre z tego) ci pomoże

 25
Author: stuartf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-26 11:39:28

Sugerowałbym, abyś starał się utrzymać swoje scenariusze tak czyste, jak to możliwe, koncentrując się na czytelności dla osób nie będących technikami w Twoim projekcie. Sposób konstruowania złożonych wykresów obiektowych jest następnie obsługiwany w definicjach kroków.

Z tym, że powiedział nadal trzeba sposób, aby wyrazić struktury hierarchiczne w specyfikacji, tj. z korniszonem. Z tego co wiem to nie jest to możliwe i z tego posta (w grupie SpecFlow Google) wydaje się, że zostało to omówione wcześniej.

Zasadniczo możesz wymyślić własny format i przeanalizować go w swoim kroku. Nie natknąłem się na to sam, ale myślę, że chciałbym spróbować tabeli z pustymi wartościami dla następnego poziomu i przetworzyć to w definicji kroku. TAK:

Given I have the following hierarchical structure:
| MyObject.Id | StartDate | EndDate  | ChildObject.Id | Name | Length |
| 1           | 20010101  | 20010201 |                |      |        |
|             |           |          | 1              | Me   | 196    |
|             |           |          | 2              | You  | 120    |
Przyznam, że nie jest zbyt ładna, ale może się udać.

Innym sposobem jest użycie wartości domyślnych i podanie różnic. TAK:

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

W definicji kroku dodajesz" standardowe " wartości dla MyObject i wypełnij listę dzieci. To podejście jest nieco bardziej czytelne, jeśli pytasz mnie, ale musisz "wiedzieć", co to jest standardowy MyObject i jak to jest skonfigurowane.

Zasadniczo-korniszony go nie wspiera. Ale możesz utworzyć format, który możesz przeanalizować.

Mam nadzieję, że to odpowiedź na twoje pytanie...

 9
Author: Marcus Hammarberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-26 11:13:57

Idę o krok dalej, gdy mój Domain Object Model zaczyna się składać i tworzę "modele testowe", których używam specjalnie w moich scenariuszach SpecFlow. Model testowy powinien:

  • skoncentruj się na terminologii biznesowej
  • pozwalają tworzyć łatwe do odczytania scenariusze
  • [18]}zapewnienie warstwy oddzielającej między terminologią biznesową a złożonym modelem domeny

Weźmy bloga jako przykład.

Scenariusz SpecFlow: tworzenie bloga Post

Rozważ następujący scenariusz napisany tak, aby każdy, kto zna zasady działania bloga, wiedział o co chodzi: {]}

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               |

Modeluje to skomplikowaną relację, w której Blog ma wiele wpisów na blogu.

Model Domeny

Model domeny dla tego bloga będzie następujący:

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
}

Zauważ, że nasz scenariusz ma "Status" o wartości "Working Draft", ale BlogPostStatus enum ma WorkingDraft. Jak przetłumaczyć ten status "języka naturalnego" na enum? Teraz wprowadź model testowy.

Model Testowy: BlogPostRow

Klasa BlogPostRow jest przeznaczona do zrobienia kilku rzeczy:

  1. Przetłumacz tabelę SpecFlow na obiekt
  2. Zaktualizuj swój model domeny o podane wartości
  3. dostarcz "Konstruktor kopiujący" do zalążka obiektu BlogPostRow z wartościami z istniejącej instancji modelu domeny, aby można było porównać te obiekty w SpecFlow

Kod:

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();
        }
    }
}
Jest w prywatnym GetStatus i GetStatusText gdzie czytelne dla człowieka wartości statusu postu na blogu są tłumaczone na Liczby i vice versa. Nie jest to jednak zbyt skomplikowany przypadek, ale jest to łatwy do naśladowania przypadek.]}

Ostatni element układanki to definicje kroków.

Używanie modeli testowych z modelem domeny w kroku Definicje

Krok:

Given a Blog named "Testing with SpecFlow" exists

Definicja:

[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();
    }
}

Krok:

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               |

Definicja:

[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();
    }
}

Krok:

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               |

Definicja:

[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 - pewnego rodzaju trwały magazyn danych, którego żywotność jest bieżącym scenariuszem)

Modele w szerszym kontekście

Cofając się o krok, termin "Model" stał się bardziej złożony, a my właśnie wprowadziliśmy kolejny rodzaj modelu. Zobaczmy, jak oni wszyscy grają razem:
  • model domeny: Klasa modelująca to, czego chce firma, często przechowywana w bazie danych i zawiera zachowanie modelujące reguły biznesowe.
  • model widoku: wersja modelu domeny ukierunkowana na prezentację
  • Data Transfer Object: worek danych używany do przesyłania danych z jednej warstwy lub komponentu do drugiej (często używany przy wywołaniach usługi sieciowej)
  • model testowy: obiekt używany do reprezentowania danych testowych w sposób, który ma sens dla osoba biznesowa czytająca twoje testy zachowania. Przekłada się między modelem domeny a modelem testowym.

Można prawie myśleć o modelu testowym jako modelu widoku dla testów SpecFlow, przy czym "widok" jest scenariuszem napisanym w Gherkin.

 5
Author: Greg Burghardt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-29 18:38:12

Pracowałem w kilku organizacjach, które napotkały ten sam problem, który tu opisałeś. Jest to jedna z rzeczy, które skłoniły mnie do (próby), aby rozpocząć pisanie książki na ten temat.

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

Tutaj sugeruję użycie konwencji, która pozwala używać nagłówków tabeli SpecFlow, aby wskazać, skąd pochodzą połączone elementy, jak określić, które z nich chcesz, a następnie użyć zawartości wierszy aby dostarczyć dane do "lookup" w obcych tabelach.

Na przykład:

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   |
Mam nadzieję, że to będzie pomocne.
 3
Author: user3202264,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-16 11:24:30

Dobrym pomysłem jest ponowne wykorzystanie standardowego wzorca konwencji nazewnictwa modelu MVC Bindera w metodzie StepArgumentTransformation. Oto przykład: czy Wiązanie modelu jest możliwe bez mvc?

Oto część kodu (tylko główna idea, bez żadnych walidacji i dodatkowych wymagań):

In 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                  |

W krokach:

[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;
}
Dla mnie działa.

Oczywiście musisz mieć odniesienie do systemu.Www.Biblioteka Mvc.

 1
Author: Vetal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:59

Korzystanie Z 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
Author: Gomes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-19 21:56:30