当我的领域对象模型开始变得复杂时,我会更进一步,并创建我在 SpecFlow 场景中专门使用的“测试模型”。测试模型应该:
- 专注于业务术语
- 让您创建易于阅读的场景
- 在业务术语和复杂的领域模型之间提供一层解耦
让我们以博客为例。
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
该BlogPostRow
课程旨在做一些事情:
- 将您的 SpecFlow 表转换为对象
- 使用给定的值更新您的领域模型
- 提供一个“复制构造函数”来为 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();
}
}
}
它是私有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
- 某种持久性数据存储,其生命周期是当前场景)
更大范围内的模型
退后一步,“模型”一词变得更加复杂,我们刚刚介绍了另一种模型。让我们看看他们是如何一起玩的:
- 领域模型:一个对业务想要的东西进行建模的类,通常存储在数据库中,并包含对业务规则进行建模的行为。
- 查看模型:您的领域模型的以演示为中心的版本
- 数据传输对象:用于将数据从一层或组件传输到另一层的数据包(通常与 Web 服务调用一起使用)
- 测试模型:用于以对阅读您的行为测试的业务人员有意义的方式表示测试数据的对象。在领域模型和测试模型之间转换。
您几乎可以将测试模型视为 SpecFlow 测试的视图模型,“视图”是用 Gherkin 编写的场景。