ドメイン オブジェクト モデルが複雑になり始めたら、さらに一歩進んで、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 で記述されたシナリオです。