SpecFlow和复杂对象

Ram*_*nas 20 c# bdd nunit specflow

我正在评估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; }
}
Run Code Online (Sandbox Code Playgroud)

有没有人知道如何编写我的功能/场景MyObject将从"给定"步骤实例化并用于"何时"和"然后"步骤?

提前致谢

编辑:记住一点:支持嵌套表吗?

stu*_*rtf 28

我会说Marcus在这里非常正确,但我会编写我的场景,以便我可以在TechTalk.SpecFlow.Assist命名空间中使用一些扩展方法.看到这里.

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        |
Run Code Online (Sandbox Code Playgroud)

对于步骤后面的代码,你可以使用这样的东西,在它中会有更多的错误处理.

    [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());
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

希望这(或其中一些)对你有所帮助


小智 20

对于你所展示的例子,我会说你错了.这个例子看起来更适合用nunit写,可能还有一个对象母.使用specflow或类似工具编写的测试应面向客户,并使用与客户用于描述功能相同的语言.

  • 我同意,规范的语言(因此,行为和设置)应该是用户和域的语言.这太低了. (2认同)

Mar*_*erg 10

我建议您尽量保持场景的清洁,重点关注项目中非技术人员的可读性.然后,在步骤定义中处理如何构造复杂对象图.

有了这个说你还需要一种方法来表达你的规范中的层次结构,即与Gherkin.据我所知,这是不可能的,并且从这篇文章(在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    |
Run Code Online (Sandbox Code Playgroud)

我承认这不是超级漂亮,但它可以奏效.

另一种方法是使用默认值并给出差异.像这样:

Given a standard My Object with the following children:
| Id | Name | Length |
| 1  | Me   | 196    |
| 2  | You  | 120    |
Run Code Online (Sandbox Code Playgroud)

在步骤定义中,然后为MyObject添加"标准"值并填写子项列表.如果你问我这个方法有点可读,但你必须"知道"标准的MyObject是什么以及如何配置它.

基本上 - 小黄瓜不支持它.但是您可以创建一个可以自己解析的格式.

希望这能回答你的问题......


Gre*_*rdt 6

当我的域对象模型开始变得复杂时,我更进一步,并创建我在SpecFlow场景中专门使用的"测试模型".测试模型应该:

  • 专注于商业术语
  • 允许您创建易于阅读的方案
  • 在业务术语和复杂域模型之间提供一层解耦

我们以博客为例.

SpecFlow场景:创建博客帖子

请考虑以下方案编写,以便任何熟悉Blog工作方式的人都知道发生了什么:

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               |
Run Code Online (Sandbox Code Playgroud)

这模拟了复杂的关系,其中博客有许多博客帖子.

领域模型

此博客应用程序的域模型将是:

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
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们的场景具有"状态",其值为"工作草稿",但BlogPostStatus枚举具有WorkingDraft.你如何将这种"自然语言"状态翻译成枚举?现在进入测试模型.

测试模型:BlogPostRow

BlogPostRow堂课是为了做一些事情:

  1. 将SpecFlow表转换为对象
  2. 使用给定值更新您的域模型
  3. 提供"复制构造函数",使用现有域模型实例中的值为BlogPostRow对象设定种子,以便在SpecFlow中比较这些对象

码:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它是在私营GetStatusGetStatusText在人类可读的博客文章状态值转换为枚举,反之亦然.

(披露:我知道Enum不是最复杂的案例,但它是一个易于理解的案例)

拼图的最后一部分是步骤定义.

在步骤定义中将测试模型与您的域模型一起使用

步:

Given a Blog named "Testing with SpecFlow" exists
Run Code Online (Sandbox Code Playgroud)

定义:

[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();
    }
}
Run Code Online (Sandbox Code Playgroud)

步:

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               |
Run Code Online (Sandbox Code Playgroud)

定义:

[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();
    }
}
Run Code Online (Sandbox Code Playgroud)

步:

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               |
Run Code Online (Sandbox Code Playgroud)

定义:

[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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

(TestContext- 某种持久性数据存储,其生命周期是当前场景)

更大背景下的模型

退后一步,"模型"一词变得更加复杂,我们刚刚介绍了一种模型.让我们看看他们如何一起玩:

  • 域模型:一种类,用于模拟业务通常存储在数据库中的内容,并包含对业务规则建模的行为.
  • 视图模型:您的域模型的以演示为中心的版本
  • 数据传输对象:用于将数据从一个层或组件传输到另一个层或组件的数据包(通常用于Web服务调用)
  • 测试模型:用于以对阅读行为测试的业务人员有意义的方式表示测试数据的对象.在域模型和测试模型之间进行转换.

您几乎可以将测试模型视为SpecFlow测试的视图模型,其中"视图"是用Gherkin编写的场景.