24

我正在尝试测试以下内容:

protected IHealthStatus VerifyMessage(ISubscriber destination)
{
    var status = new HeartBeatStatus();

    var task = new Task<CheckResult>(() =>
    {
        Console.WriteLine("VerifyMessage(Start): {0} - {1}", DateTime.Now, WarningTimeout);
        Thread.Sleep(WarningTimeout - 500);
        Console.WriteLine("VerifyMessage(Success): {0}", DateTime.Now);
        if (CheckMessages(destination))
        {
            return CheckResult.Success;
        }

        Console.WriteLine("VerifyMessage(Pre-Warning): {0} - {1}", DateTime.Now, ErrorTimeout);
        Thread.Sleep(ErrorTimeout - 500);
        Console.WriteLine("VerifyMessage(Warning): {0}", DateTime.Now);
        if (CheckMessages(destination))
        {
            return CheckResult.Warning;
        }

        return CheckResult.Error;
    });

    task.Start();

    task.Wait();
    status.Status = task.Result;

    return status;
}

使用以下单元测试:

public void HeartBeat_Should_ReturnWarning_When_MockReturnsWarning()
{
    // Arrange
    var heartbeat = new SocketToSocketHeartbeat(_sourceSubscriber.Object, _destinationSubscriber.Object);
    heartbeat.SetTaskConfiguration(this.ConfigurationHB1ToHB2_ValidConfiguration());

    // Simulate the message being delayed to destination subscriber.
    _destinationSubscriber.Setup(foo => foo.ReceivedMessages).Returns(DelayDelivery(3000, Message_HB1ToHB2()));

    // Act
    var healthStatus = heartbeat.Execute();

    // Assert
    Assert.AreEqual(CheckResult.Warning, healthStatus.Status);
}

Message_HB1ToHB2() 只返回一个字符串,“延迟传递”方法是

private List<NcsMessage> DelayDelivery(int delay, string message)
{
    var sent = DateTime.Now;
    var msg = new NcsMessage()
    {
        SourceSubscriber = "HB1",
        DestinationSubscriber = "HB2",
        SentOrReceived = sent,
        Message = message
    };

    var messages = new List<NcsMessage>();
    messages.Add(msg);

    Console.WriteLine("DelayDelivery: {0}", DateTime.Now);
    Thread.Sleep(delay);
    Console.WriteLine("DelayDelivery: {0}", DateTime.Now);

    return messages;
}

我使用 Moq 作为模拟框架,使用 MSTest 作为测试框架。每当我运行单元测试时,我都会得到以下输出:

DelayDelivery: 04/04/2013 15:50:33
DelayDelivery: 04/04/2013 15:50:36
VerifyMessage(Start): 04/04/2013 15:50:36 - 3000
VerifyMessage(Success): 04/04/2013 15:50:38

除了在上述方法中使用 Thread.Sleep 的明显“代码气味”之外,单元测试的结果并不是我想要完成的。

谁能建议一种更好/更准确的方法来使用 Moq 框架来模拟消息“传递”的延迟。我省略了一些“胶水”代码,只包括了相关部分。让我知道我遗漏的内容是否会妨碍您理解问题。

4

6 回答 6

48

如果您希望 Moq 模拟只是坐着什么也不做一段时间,您可以使用回调:

Mock<IFoo> mockFoo = new Mock<IFoo>();
mockFoo.Setup(f => f.Bar())
       .Callback(() => Thread.Sleep(1000))
       .Returns("test");

string result = mockFoo.Object.Bar(); // will take 1 second to return

Assert.AreEqual("test", result);

我已经在 LinqPad 中尝试过,如果您调整Thread.Sleep()执行时间,则会相应地变化。

于 2013-04-05T10:20:57.983 回答
16

当你设置你的模拟时,你可以告诉线程在返回函数中休眠:

Mock<IMyService> myService = new Mock<IMyService>();

myService.Setup(x => x.GetResultDelayed()).Returns(() => {
    Thread.Sleep(100);
    return "result";
});
于 2016-10-11T03:06:15.070 回答
12

如果运行异步代码,Moq 可以通过第二个参数延迟响应TimeSpan

mockFooService
    .Setup(m => m.GetFooAsync())
    .ReturnsAsync(new Foo(), TimeSpan.FromMilliseconds(500)); // Delay return for 500 milliseconds.

如果每次调用方法都需要指定不同的延迟,可以使用.SetupSequencelike

mockFooService
    .SetupSequence(m => m.GetFooAsync())
    .Returns(new Foo())
    .Returns(Task.Run(async () => 
    {
        await Task.Delay(500) // Delay return for 500 milliseconds.
        return new Foo();
    })

于 2021-02-23T16:10:42.027 回答
2

我无法让最小起订量版本工作,所以我最终做了这样的事情:

一个使用 WaitHandle 的小例子:

[TestFixture]
public class EventWaitHandleTests
{
    class Worker {
        private volatile bool _shouldStop;
        public EventWaitHandle WaitHandleExternal;

        public void DoWork ()
        {
            while (!_shouldStop)
            {
                Console.WriteLine("worker thread: working...");
                Thread.Sleep(1000);
                WaitHandleExternal.Set();
            }
        }

        public void RequestStop()
        {
            _shouldStop = true;
        }

    }

    [Test]
    public void WaitForHandleEventTest()
    {
        EventWaitHandle _waitHandle = new AutoResetEvent (false); // is signaled value change to true

        // start a thread which will after a small time set an event
        Worker workerObject = new Worker ();
        workerObject.WaitHandleExternal = _waitHandle;
        Thread workerThread = new Thread(workerObject.DoWork);

        // Start the worker thread.
        workerThread.Start();

        Console.WriteLine ("Waiting...");
        _waitHandle.WaitOne();                // Wait for notification
        Console.WriteLine ("Notified");

        // Stop the worker thread.
        workerObject.RequestStop();

    }

}
于 2016-06-03T16:35:44.970 回答
1

我喜欢并投票支持 serup 的解决方案。我的答案是他的一个版本被转换为图书馆。

using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

/// <summary>
/// support halting a workflow and waiting for a finish request
/// </summary>
public class MockWorker
{
    private readonly DateTime? _end;
    private volatile bool _shouldStop;

    /// <summary>
    /// Create a worker object
    /// </summary>
    /// <param name="timeoutInMilliseconds">How long before DoWork will timeout.  default - Null will not timeout.</param>
    public MockWorker(int? timeoutInMilliseconds = null)
    {
        if (timeoutInMilliseconds.HasValue)
            _end = DateTime.Now.AddMilliseconds(timeoutInMilliseconds.Value);
    }

    /// <summary>
    /// Instruct DoWork to complete
    /// </summary>
    public void RequestStop()
    {
        _shouldStop = true;
    }

    /// <summary>
    /// Do work async will run until either timeoutInMilliseconds is exceeded or RequestStop is called.
    /// </summary>
    public async Task DoWorkAsync()
    {
        while (!_shouldStop)
        {
            await Task.Delay(100);
            if (_end.HasValue && _end.Value < DateTime.Now)
                throw new AssertFailedException("Timeout");
        }
    }

    /// <summary>
    /// Do work async will run until either timeoutInMilliseconds is exceeded or RequestStop is called.
    /// </summary>
    /// <typeparam name="T">Type of value to return</typeparam>
    /// <param name="valueToReturn">The value to be returned</param>
    /// <returns>valueToReturn</returns>
    public async Task<T> DoWorkAsync<T>(T valueToReturn)
    {
        await DoWorkAsync();
        return valueToReturn;
    }
}
于 2018-10-14T15:53:22.713 回答
1

我有类似的情况,但是使用了 Async 方法。对我有用的是执行以下操作:

 mock_object.Setup(scheduler => scheduler.MakeJobAsync())
  .Returns(Task.Run(()=> { Thread.Sleep(50000); return Guid.NewGuid().ToString(); }));
于 2019-02-14T22:08:16.197 回答