21

私は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「与えられた」ステップからインスタンス化され、「いつ」および「その後」ステップで使用される私の機能/シナリオをどのように書くことができるか、誰にも分かりませんか?

前もって感謝します

編集:覚えておいてください: ネストされたテーブルはサポートされていますか?

4

8 に答える 8

31

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        |

ステップの背後にあるコードについては、このようなものを使用すると、エラー処理が少し増えます。

    [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());
                }
            }
        }
    }

これ(またはこれの一部)があなたに役立つことを願っています

于 2011-04-26T21:46:44.483 に答える
20

あなたが示した例については、あなたはそれを間違っていると思います。この例は、nunit で記述し、おそらくオブジェクトMother を使用するのに適しているように見えます。specflow または同様のツールで記述されたテストは、顧客向けであり、顧客が機能を説明するために使用するのと同じ言語を使用する必要があります。

于 2011-04-26T18:38:25.103 に答える
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    |

それは私が認める超きれいではありませんが、うまくいく可能性があります.

それを行う別の方法は、デフォルト値を使用して違いを与えることです。このような:

Given a standard My Object with the following children:
| Id | Name | Length |
| 1  | Me   | 196    |
| 2  | You  | 120    |

次に、ステップ定義で、MyObject の「標準」値を追加し、子のリストを入力します。私に言わせれば、そのアプローチはもう少し読みやすいですが、標準の MyObject とは何か、そしてそれがどのように構成されているかを「知っている」必要があります。

基本的に - Gherkin はそれをサポートしていません。ただし、自分で解析できる形式を作成することはできます。

これがあなたの質問に答えてくれることを願っています...

于 2011-04-26T11:13:57.757 に答える
6

ドメイン オブジェクト モデルが複雑になり始めたら、さらに一歩進んで、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クラスは、いくつかのことを行うためのものです。

  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();
        }
    }
}

これは非公開GetStatusGetStatusTextあり、人間が判読できるブログ投稿ステータス値が 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 で記述されたシナリオです。

于 2015-09-28T18:04:07.370 に答える
3

私は現在、いくつかの組織で働いていますが、それらはすべて、あなたがここで説明した同じ問題に遭遇しました. これは、私がこの主題に関する本を書き始める (試みた) きっかけの 1 つです。

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   |

うまくいけば、これはいくつかの助けになるでしょう。

于 2014-01-16T11:24:30.460 に答える
1

StepArgumentTransformation メソッドで標準の MVC モデル バインダーの命名規則パターンを再利用することをお勧めします。以下に例を示します: mvc なしでモデル バインドは可能ですか?

コードの一部を次に示します (検証や追加要件を除いた主なアイデアのみ)。

特徴:

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                  |

手順:

[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;
}

わたしにはできる。

もちろん、System.Web.Mvc ライブラリへの参照が必要です。

于 2015-07-27T14:26:47.670 に答える