1

在使用 Moq 模拟被多次调用的依赖项时,我遇到了障碍。当我打电话时Verify,起订量需要很长时间(几分钟)才能响应,有时会崩溃(我想这是可以理解的,考虑到从“冷启动”开始必须累积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);
        }
    }
}

起订量策略 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());

起订量策略 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.Strict,Callback

同样,最小起订量不是为大型集而设计的,因此存在性能损失。您最好使用其他措施来验证将传递给模拟的数据。

对于 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 回答