在模拟中测试多线程服务方法

3
例如,我想测试我的多线程方法是否在从不同的线程中给它n个数据块时调用存储库方法n次。当然,模拟对象不是线程安全的,也不应该是线程安全的。
[Test]
public void CanSaveCustomersInParallel()
{
    var customers = new List<List<Customer>>
                        {
                            new List<Customer>
                                {
                                    new Customer {FirstName = "FirstName1"},
                                    new Customer {FirstName = "FirstName2"}
                                },
                            new List<Customer>
                                {
                                    new Customer {FirstName = "FirstName3"},
                                    new Customer {FirstName = "FirstName4"}
                                }
                        };
    _serviceCustomers.ParallelSaveBatch(customers);
    _repoCustomers
        .Verify(x => x.SaveBatch(It.IsAny<List<Customer>>()), Times.Exactly(2));
}

当然,这个测试有时失败,有时不会失败。但从本质上讲,它是不正确的。您能给我建议如何重新编写它吗?

3个回答

5
好的,下一个存根就行了:
internal class ServiceStub: Service<DummyEntity>
{
    private int _count;

    public int Count
    {
        get { return _count; }
    }

    public override void SaveBatch(IEnumerable<object> entities)
    {
       lock(this)
       {
           _count++;
       }
    }

    public ServiceStub(IRepository<DummyEntity> repository):base(repository)
    {
        _count = 0;
    }
}

而单元测试看起来是这样的:

    [Test]
    public void CanSaveCustomersInParallel()
    {
        var service = new ServiceStub(new DummyRepository());
        var customers = new List<List<Customer>>
                            {
                                new List<Customer>
                                    {
                                        new Customer {FirstName = "FirstName1"},
                                        new Customer {FirstName = "FirstName2"}
                                    },
                                new List<Customer>
                                    {
                                        new Customer {FirstName = "FirstName3"},
                                        new Customer {FirstName = "FirstName4"}
                                    }
                            };
        service.ParallelSaveBatch(customers);
        Assert.AreEqual(service.Count, customers.Count);
    }

4

你可能会感兴趣知道,从v 4.1开始,Moq的线程安全性得到了很大改进,你应该会发现像你这样的测试,在并行运行时,现在应该会按预期进行验证。

更多关于不稳定的Moq行为请看这里

在版本<= 4.0中遇到的典型问题包括随机的NullReferenceExceptionIndexOutOfRangeException以及mock.Verify(<>, Times.Exactly(N))失败(通常会少算)。从4.1开始,这些问题现在似乎已经被修复(或者像麦克盖尔一样靠创造力解决),感谢社区!

编辑 根据@Danny的评论,需要注意的是,4.1中所做的更改包括锁定Mock,如果您需要测试代码的并行性,则没有什么用处。


2
我们刚刚发现这不是真的... 4.1版本在涉及到模拟对象的部分添加了lock(Mock),这使得模拟对象对于多线程代码完全无用。这似乎并没有“改进”... 我们不得不回滚到4.0,因为它破坏了我们的多线程/并发测试! :( - Danny Tuppeny
哎呀 - 我没注意到这个。这里有一个例子。 - StuartLC
1
我的名字是丹尼,不是大卫;-) - Danny Tuppeny
2
我已经提出了一个问题,希望他们关注这个破坏性的变化:https://github.com/Moq/moq4/issues/62 - Danny Tuppeny

-1

一个设计变更可以简化这个测试。创建一个执行实际工作的SaveWorker,和一个代理去在另一个线程上执行SaveWork(相同的抽象)。然后创建一个SaveWorkerFactory,在给定顾客的情况下返回一个ThreadedSaveWorker。最后,将SaveWorkerFactory的Mock注入到_serviceCustomers中,并验证它进行了2次调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接