1

何度も呼び出される依存関係をシミュレートするために Moq を使用しているときに、問題が発生しました。を呼び出すとVerify、Moq は応答に長い時間 (数分) を要し、時々クラッシュします( 「コールド スタート」から実行するために蓄積する必要がNullReferenceExceptionあるデータの量を考えると、これは理解できると思います)。MoqVerify

私の質問は、Moq を使用してこれを行うために使用できる別の戦略があるか、またはこのかなり珍しいケースのために手作りのスタブに戻す必要があるかということです。具体的には、パラメーターの特定のフィルターを検証することにのみ関心があり、他のすべての値を無視することを Moq に前もって伝える方法はありますか?

以下のアプローチはどちらも満足のいくものではありません。

与えられた CUT と Dep :

public interface ISomeInterface
{
    void SomeMethod(int someValue);
}

public class ClassUnderTest
{
    private readonly ISomeInterface _dep;
    public ClassUnderTest(ISomeInterface dep)
    {
        _dep = dep;
    }

    public void DoWork()
    {
        for (var i = 0; i < 1000000; i++) // Large number of calls to dep
        {
            _dep.SomeMethod(i);
        }
    }
}

Moq 戦略 1 - 検証

        var mockSF = new Mock<ISomeInterface>();
        var cut = new ClassUnderTest(mockSF.Object);
        cut.DoWork();
        mockSF.Verify(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == 12345)),
                      Times.Once());
        mockSF.Verify(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == -1)),
                      Times.Never());

Moq 戦略 2 - コールバック

        var mockSF = new Mock<ISomeInterface>();
        var cut = new ClassUnderTest(mockSF.Object);
        bool isGoodValueAlreadyUsed = false;
        mockSF.Setup(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == 12345)))
              .Callback(() =>
            {
                if (isGoodValueAlreadyUsed)
                {
                    throw new InvalidOperationException();
                }
                isGoodValueAlreadyUsed = true;
            });
        mockSF.Setup(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == -1)))
              .Callback(() =>
                { throw new InvalidOperationException(); });

        cut.DoWork();
        Assert.IsTrue(isGoodValueAlreadyUsed);
4

1 に答える 1

3

通常、そのような制限に達した場合、私は自分のデザインを再検討します (問題はありません。担当者に連絡します)。テスト中のメソッドがあまりにも多くの作業を行っているように見えますが、これは単一責任の原則に違反しています。最初に項目の大きなリストを生成し、次に、シーケンスに適切な要素が含まれていることも確認しながら、それらのそれぞれに対してワーカーが呼び出されることを確認します。

機能をシーケンス ジェネレーターに分割し、シーケンスに適切な要素と、シーケンスに作用する別のメソッドがあることを確認し、各要素に対してワーカーが実行されることを確認します。

namespace StackOverflowExample.Moq
{
    public interface ISequenceGenerator
    {
        IEnumerable<int> GetSequence();
    }

    public class SequenceGenrator : ISequenceGenerator
    {
        public IEnumerable<int> GetSequence()
        {
            var list = new List<int>();
            for (var i = 0; i < 1000000; i++) // Large number of calls to dep
            {
                list.Add(i);
            }
            return list;
        }
    }

    public interface ISomeInterface
    {
        void SomeMethod(int someValue);
    }

    public class ClassUnderTest
    {
        private readonly ISequenceGenerator _generator;
        private readonly ISomeInterface _dep;

        public ClassUnderTest(ISomeInterface dep, ISequenceGenerator generator)
        {
            _dep = dep;
            _generator = generator;
        }

        public void DoWork()
        {
            foreach (var i in _generator.GetSequence())
            {
                _dep.SomeMethod(i);
            }
        }
    }

    [TestFixture]
    public class LargeSequence
    {
        [Test]
        public void SequenceGenerator_should_()
        {
            //arrange
            var generator = new SequenceGenrator();

            //act
            var list = generator.GetSequence();

            //assert
            list.Should().Not.Contain(-1);
            Executing.This(() => list.Single(i => i == 12345)).Should().NotThrow();
            //any other assertions
        }

        [Test]
        public void DoWork_should_perform_action_on_each_element_from_generator()
        {
            //arrange
            var items = new List<int> {1, 2, 3}; //can use autofixture to generate random lists
            var generator = Mock.Of<ISequenceGenerator>(g => g.GetSequence() == items);
            var mockSF = new Mock<ISomeInterface>();

            var classUnderTest = new ClassUnderTest(mockSF.Object, generator);

            //act
            classUnderTest.DoWork();

            //assert
            foreach (var item in items)
            {
                mockSF.Verify(c=>c.SomeMethod(item), Times.Once());
            }
        }
    }
}

編集: さまざまなアプローチを組み合わせて、特定の期待を定義できます。When()、廃止されAtMost()た 、MockBehavior.StrictCallbackなど。

繰り返しますが、Moq は大きなセットで動作するように設計されていないため、パフォーマンスが低下します。どのデータがモックに渡されるかを確認するには、別の手段を使用する方がよいでしょう。

OPの例では、簡単なセットアップを次に示します。

var mockSF = new Mock<ISomeInterface>(MockBehavior.Strict);
var cnt = 0;

mockSF.Setup(m => m.SomeMethod(It.Is<int>(i => i != -1)));
mockSF.Setup(m => m.SomeMethod(It.Is<int>(i => i == 12345))).Callback(() =>cnt++).AtMostOnce();

これは -1 をスローし、12 で複数の呼び出しをスローし、 でアサーションを行うことができますcnt != 0

于 2013-05-28T20:11:47.367 に答える