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?
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.
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
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...
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:
- Przetłumacz tabelę SpecFlow na obiekt
- Zaktualizuj swój model domeny o podane wartości
- 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.
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.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.
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>());
}
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