大量调用后,Moq 验证依赖关系很慢 [英] Moq is slow to verify dependency after a large number calls

查看:86
本文介绍了大量调用后,Moq 验证依赖关系很慢的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在使用 Moq 模拟被多次调用的依赖项时遇到了障碍.当我调用 Verify 时,Moq 需要很长时间(几分钟)来响应,有时会因 NullReferenceException 而崩溃(我想这是可以理解的,考虑到数据量Moq 必须累积才能从冷启动"开始执行 Verify).

I've hit a snag when using Moq to simulate an dependency which is called a large number of times. When I call Verify, Moq takes a long time (several minutes) to respond, and sometimes crashes with a NullReferenceException (I guess this is understandable, given the amount of data that Moq would have to accumulate to do the Verify from a "cold start").

所以我的问题是,是否有另一种策略可以使用 Moq 来做到这一点,或者我应该针对这种相当不寻常的情况恢复到手工制作的存根.具体来说,有没有办法预先告诉 Moq 我只对验证参数上的特定过滤器感兴趣,而忽略所有其他值?

So my question is, is there another strategy that I can use to do this using Moq, or should I revert to a hand-crafted stub for this rather unusual case. Specifically, is there a way to tell Moq up front that I'm only interested in verifying specific filters on the parameters, and to ignore all other values?

以下方法都不令人满意.

Neither of the approaches below is satisfactory.

给定 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);

推荐答案

通常当达到这样的限制时,我会重新考虑我的设计(无意冒犯,我看到了你的代表).看起来被测方法做了太多工作,违反了单一职责原则.它首先生成一个大的项目列表,然后验证为每个项目调用了一个工作程序,同时还验证该序列是否包含正确的元素.

Usually when such a limitation is reached, I would reconsider my design (no offense, I see your rep). Looks like the method under test does too much work, which is violation of the single responsibility principle. It first generates a large list of items, and then verifies a worker is called for each one of them, while also verifying that the sequence contains the right elements.

我将功能拆分为一个序列生成器,并验证该序列是否具有正确的元素,以及另一个作用于该序列的方法,并验证它是否为每个元素执行了工作程序:

I'd split the functionality into a sequence generator, and verify that the sequence has the right elements, and another method which acts on the sequence, and verify that it executes the worker for each element:

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

Different approaches can be mixed to define a specific expectations, incl. When(), the obsoleted AtMost(), MockBehavior.Strict, Callback, etc.

同样,Moq 并非设计用于大型集,因此会降低性能.您最好使用其他方法来验证将传递给模拟的数据.

Again, Moq is not designed to work on large sets, so there is performance penalty. You are still better off using another measures to verify what data will be passed to the mock.

对于 OP 中的示例,这里是一个简化的设置:

For the example in the OP, here is a simplified setup:

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 进行断言.

This will throw for -1, for more than one invocation with 12, and assertion can be made on cnt != 0.

这篇关于大量调用后,Moq 验证依赖关系很慢的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆