1

1 日で 2 回目の MSpec に関する質問です。これは新記録です。私は MSpec をすぐに理解できるようにしようとしていますが、MSpec で常に抱えていた古い問題に遭遇しました。

シナリオ: 大量の漫画を含むリポジトリがあります。現時点では、文字列である単一のNameパラメータでこのセットをフィルタリングするだけで済みます。後でさらに多くのプロパティでこれをフィルタリングする必要があると言われたので、IoC を介して ICartoonRepository を取り込み、GetByName(文字列名) と呼ばれる単純なメソッドを含むクラスを作成することにしました。

これはやり過ぎだと言う人もいるかもしれませんが、私は MSpec の使い方とより TDD 的な方法で作業する方法を独学で学ぼうとしています。

だから私は以下を作成します:

[Subject(typeof(CartoonViewModelBuilder))]
public class when_cartoon_repository_is_asked_to_get_by_id : specification_for_cartoon_viewmodel_builder
{
    static string name;
    static Cartoon the_cartoon;
    static Cartoon result;

    Establish context = () =>
    {
        name = "Taz";
        the_cartoon = new Cartoon();
        the_cartoon_repository.Stub(r => r.GetAll().Where(x=>x.Name == name).FirstOrDefault()).Return(the_cartoon);
    };

    Because of = () => result = subject.GetByName(name);

    It should_return_cartoon = () => result.ShouldBeTheSameAs(the_cartoon);
}

リポジトリが空であるため、これはスタブで失敗します。他にも問題なく合格するテストがいくつかあります(単に GetAll() などをテストするだけです)。テストするためにリポジトリに何かを追加する必要がありますか? 困ったところです、お手柔らかにお願いします。

また、スタブにlinq文を書いていると、実際の実装とテストの2回やっているような気がします。これがポイントですか?気分が悪い。このテストを書くためのより良い方法はありますか?

わかりやすくするために、実際の実装を次に示します (インターフェイスとクラスは省略していますが、プロパティは 1 つだけです。

public class CartoonViewModelBuilder: ICartoonViewModelBuilder
{
    readonly ICartoonRepository _cartoonRepository;

    public CartoonQueryObject(ICartoonRepository  cartoonRepository)
    {
        _cartoonRepository = cartoonRepository;
    }

    public IList<Cartoon> GetAllCartoons()
    {
        return _cartoonRepository.GetAll();
    }

    public Cartoon GetByName(string name)
    {
        return _cartoonRepository.GetAll().Where(x => x.Name == name).FirstOrDefault();
    }
}

編集 1: 応答の欠如に基づいて、NUnit のようなものを使用していた場合、「LoadDummyData」のようなテスト クラスでメソッドを作成し、データをリポジトリにスローしたと言う必要があります。複雑なフィルタリングやビューモデルの構築を行い、何が起こったのかを手動でチェックしました。これにより、大規模なリファクタリングが雑用になりました。仕様上、それを回避できるように見えますか?

編集 2: これが私の修正されたテストで、現在合格しています。私がそれを正しく行っているかどうか教えてください、私はそう思います。握手してくれてありがとう!

    static string name;
    static Cartoon the_cartoon;
    static Cartoon result;
    static IQueryable<Cartoon> the_cartoons;

    Establish context = () =>
    {
        name = "Taz";
        the_cartoon = new Cartoon {Name = name};
        the_cartoons = new List<Cartoon> {the_cartoon, new Cartoon(), new Cartoon() }.AsQueryable();
        the_cartoon_repository.Stub(r => r.GetAll()).Return(the_cartoons.ToList());
    };

    Because of = () => result = subject.GetByName(name);

    It should_return_cartoon = () => result.ShouldBeTheSameAs(the_cartoon);

編集 3: 両方のポイントを与えましたが、残念ながらベスト アンサーは 1 つしか与えられません。

4

2 に答える 2

1

リポジトリの実装をテストしたい場合は、スタブしないでください。MSpecであろうとなかろうと、既知のアイテムのリストをリポジトリに追加してから、GetByNameを使用してクエリを発行します。次に、期待するアイテムだけが返されたと主張します。また、リポジトリは追加したアイテムを処理して別のインスタンスを返す可能性があるため、ShouldEqualを使用しますが、等しいと見なされます(集約IDは等しい)。

于 2011-06-19T08:30:22.147 に答える
1

The actual reason of this test failing is the way you're mocking your repository. I would be very surprised if method chains like r.GetAll().Where(x=>x.Name == name).FirstOrDefault() could be mocked so easily, as it uses LINQ extension methods and lambda clauses. The framework should really throw NotSupported exception or something to let you know that you can't mock LINQ queries as a whole.

To mock LINQ query result, you should provide properly prepared underlying data collection, which is the starting point of LINQ query. In your example you should mock just r.GetAll() to return a collection containing your element with proper name. The actual query will run on your "mocked" data and retrieve the object you expect.

This removes the need to duplicate your LINQ query in code and in test, what is strange, as you noted.

EDIT: Code in your edit is like I've suggested, technically OK.

Anyway, by now it's a bit overkill, as you've said. Your class under test doesn't do anything beside the call to the mocked repository, so the value of that test is rather small. But it may be a good start if you're going to have some more logic in GetByName method.

于 2011-06-19T08:23:18.643 に答える