我正在评估SpecFlow而且我有点卡住了 我发现的所有样品基本上都是简单的物体。
项目我正在努力依赖于复杂的对象。一个接近的样本可能是这个对象:
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; }
}
有没有人知道如何编写我的功能/场景,其中MyObject
将从“给定”步骤实例化并用于“何时”和“然后”步骤?
提前致谢
编辑:请记住:是否支持嵌套表?
答案 0 :(得分:28)
我想说Marcus在这里非常正确,但我会编写我的场景,以便我可以在TechTalk.SpecFlow.Assist命名空间中使用一些扩展方法。请参阅here。
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 |
对于步骤后面的代码,您可以使用类似这样的内容来处理错误。
[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());
}
}
}
}
希望这(或其中一些)对你有帮助
答案 1 :(得分:19)
对于您展示的示例,我会说you're cuking it wrong。此示例看起来更适合使用nunit编写,并且可能使用 object mother 。使用specflow或类似工具编写的测试应面向客户,并使用与客户用于描述功能相同的语言。
答案 2 :(得分:10)
我建议您尽量保持场景的清洁,重点关注项目中非技术人员的可读性。然后,在步骤定义中处理如何构造复杂对象图。
据说,你仍然需要一种方法来表达你的规范中的层次结构,即与Gherkin。据我所知,这是不可能的,而this post(在SpecFlow Google小组中)似乎已经讨论过了。
基本上你可以发明一种自己的格式并在你的步骤中解析它。我自己没有碰到这个,但我想我会尝试一个下一级空白值的表,并在步骤定义中解析它。像这样:
Given I have the following hierarchical structure:
| MyObject.Id | StartDate | EndDate | ChildObject.Id | Name | Length |
| 1 | 20010101 | 20010201 | | | |
| | | | 1 | Me | 196 |
| | | | 2 | You | 120 |
我承认这不是超级漂亮,但它可以奏效。
另一种方法是使用默认值并给出差异。像这样:
Given a standard My Object with the following children:
| Id | Name | Length |
| 1 | Me | 196 |
| 2 | You | 120 |
在步骤定义中,然后为MyObject添加“标准”值并填写子项列表。 如果你问我这个方法有点可读,但你必须“知道”标准的MyObject是什么以及如何配置它。
基本上 - Gherkin不支持它。但是你可以创建一个你可以自己解析的格式。
希望这能回答你的问题...
答案 3 :(得分:5)
当我的域对象模型开始变得复杂时,我更进一步,并创建我在SpecFlow场景中专门使用的“测试模型”。测试模型应该:
我们以博客为例。
考虑以下方案,以便任何熟悉博客如何运作的人知道发生了什么:
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 |
这模拟了一个复杂的关系,其中博客有很多博客帖子。
此博客应用程序的域模型将是:
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
}
请注意,我们的方案的“状态”值为“工作草稿”,但BlogPostStatus
枚举有WorkingDraft
。你如何将这种“自然语言”状态翻译成枚举?现在进入测试模型。
BlogPostRow
课程意味着做一些事情:
代码:
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();
}
}
}
它位于私人GetStatus
和GetStatusText
,其中人类可读博客帖子状态值已转换为枚举,反之亦然。
(披露:我知道Enum不是最复杂的案例,但这是一个易于理解的案例)
最后一个难题是步骤定义。
步骤:
Given a Blog named "Testing with SpecFlow" exists
定义:
[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();
}
}
步骤:
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 |
定义:
[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();
}
}
步骤:
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 |
定义:
[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
- 某种持久性数据存储,其生命周期是当前场景)
退一步说,“模型”一词变得更加复杂,我们刚刚介绍了另一种类型的模型。让我们看看他们如何一起玩:
您几乎可以将测试模型视为SpecFlow测试的视图模型,其中“视图”是用Gherkin编写的场景。
答案 4 :(得分:3)
我现在已经在几个组织工作,这些组织都遇到了你在这里描述的同一个问题。这是促使我(尝试)开始写一本关于这个主题的书的事情之一。
http://specflowcookbook.com/chapters/linking-table-rows/
这里我建议使用一个约定,它允许你使用specflow表头来指示链接项的来源,如何识别你想要的那些,然后使用行的内容来提供数据“查找” “在国外的桌子上。
例如:
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 |
希望这会有所帮助。
答案 5 :(得分:1)
一个好主意是在StepArgumentTransformation方法中重用标准MVC Model Binder的命名约定模式。以下是一个示例:Is model binding possible without mvc?
以下是代码的一部分(只是主要想法,没有任何验证和您的附加要求):
在功能中:
var create_node = (function() {
var memo;
console.log("memo: "+memo);
console.log("create_node")
function f () {
var value;
if(memo){
value = memo.cloneNode();
console.log("clone node");
console.log(value);
}else{
var value = document.createElement("div");
memo = value;
}
console.log("new node");
console.log("value: "+value);
return value;
}
return f;
})();
分步骤:
git add -A
它对我有用。
当然,您必须具有对System.Web.Mvc库的引用。
答案 6 :(得分:0)
使用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>());
}
答案 7 :(得分:0)
您可以使用 Json 语法。
1 - 创建表扩展
public static class TableExtensions
{
public static List <object> ToObjectByJson(this Table table, string modelFullName)
{
var type = Type.GetType(modelFullName);
var jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
var listOfObjects = new List<object>();
foreach(var row in table.Rows)
{
var dynamicObject = new ExpandoObject();
foreach (var header in table.Header)
{
var val = row[header];
if (IsValidJson(val))
{
dynamicObject.TryAdd(header, JsonConvert.DeserializeObject(val, jsonSerializerSettings));
}
else
{
dynamicObject.TryAdd(header, val);
}
}
var json = JsonConvert.SerializeObject(dynamicObject, Formatting.Indented, jsonSerializerSettings);
listOfObjects.Add(JsonConvert.DeserializeObject(json, type, jsonSerializerSettings));
}
return listOfObjects;
}
private static bool IsValidJson(string strInput)
{
if (string.IsNullOrWhiteSpace(strInput)) { return false; }
strInput = strInput.Trim();
if ((strInput.StartsWith("{") && strInput.EndsWith("}")) || //For object
(strInput.StartsWith("[") && strInput.EndsWith("]"))) //For array
{
try
{
var obj = JToken.Parse(strInput);
return true;
}
catch (JsonReaderException jex)
{
//Exception in parsing json
Console.WriteLine(jex.Message);
return false;
}
catch (Exception ex) //some other exception
{
Console.WriteLine(ex.ToString());
return false;
}
}
else
{
return false;
}
}
}
2 - 在您的功能调用中发送模型全名/程序集和表数据的步骤
3 - 在 Steps 类中,您可以转换对象列表中的表格。
[Given(@"informei o seguinte argumento do tipo '(.*)':")]
public void EOsSeguintesValor(string modelType, Table table)
{
var objects = table.ToObjectsByJson(modelType);
}