异步速率限制器的单元测试定期失败

时间:2019-01-25 02:38:02

标签: c# .net unit-testing mstest

我已经写了一个非常基本的基于滑动窗口的异步速率限制器,主要是通过使用信号量及其AsyncWait方法修改同步滑动窗口解决方案。

public class TaskRateLimiter : ITaskRateLimiter
{
    private SlidingWindow _window;

    public TaskRateLimiter(int maxCount, TimeSpan time)
    {
        _window = new SlidingWindow(maxCount, time);
    }

    public async Task RateLimit(Func<Task> function)
    {
        await _window.WaitExecute();
        await function();
    }

    public async Task<T> RateLimit<T>(Func<Task<T>> function)
    {
        await _window.WaitExecute();
        return await function();
    }

    private class SlidingWindow
    {
        private int _count;
        private TimeSpan _time;
        private Queue<DateTime> _window = new Queue<DateTime>();
        private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

        public SlidingWindow(int count, TimeSpan time)
        {
            _count = count;
            _time = time;
        }

        private void RefreshWindow()
        {
            var now = DateTime.Now;

            while (_window.Count > 0 && _window.Peek().Add(_time) < now)
            {
                _window.Dequeue();
            }
        }

        private bool CanExecute()
        {
            RefreshWindow();
            return _window.Count < _count;
        }

        public async Task WaitExecute()
        {
            await _semaphore.WaitAsync();

            try
            {
                if (!CanExecute())
                {
                    await Task.Delay(_window.Peek().Add(_time).Subtract(DateTime.Now));
                }

                _window.Enqueue(DateTime.Now);
            }

            finally
            {
                _semaphore.Release();
            }
        }
    }
}

在开发环境中,该速率限制器的性能似乎不错,但是我担心没有工作单元测试就可以投入生产,但是我的单元测试会定期失败。

[TestClass]
public class TaskRateLimiterTests
{
    private int _numEvents;
    private TimeSpan _timeSpan;
    private TaskRateLimiter _rateLimiter;
    private Stopwatch _stopWatch;
    private List<int> _testList;
    private Func<Task> _funcToLimit;
    private Func<Task<int>> _funcWithReturnToLimit;

    [TestInitialize]
    public void Setup()
    {
        _numEvents = 10;
        _timeSpan = TimeSpan.FromMilliseconds(50);
        _rateLimiter = new TaskRateLimiter(_numEvents, _timeSpan);
        _stopWatch = new Stopwatch();
        _testList = new List<int>();
        _funcWithReturnToLimit = new Func<Task<int>>(() => { _testList.Add(5); return Task.FromResult(5); });
    }

    [TestMethod]
    public async Task RateLimit_NotExceeded()
    {
        var tempList = new List<int>();

        _stopWatch.Start();

        for (int i = 0; i < _numEvents + 1; i++)
        {
            tempList.Add(await _rateLimiter.RateLimit(_funcWithReturnToLimit));
        }

        _stopWatch.Stop();

        Assert.IsTrue(_stopWatch.Elapsed > _timeSpan)
        Assert.IsTrue(tempList.All(x => x.Equals(5)));
    }
}

我不喜欢测试与时间相关的任何内容的单元测试,但是对于速率限制器,我想不出任何其他方法来做到这一点。当测试失败时,我通常会看到以下内容:

实际:492630,下限:500000

秒表经过的刻度线刚好在下限的下方。 我怀疑执行时间短的函数可以修复测试。 但是,这并不能告诉我为什么它会定期失败,是因为秒表的不精确性吗? 我有更好的方法来测量这些时间间隔吗? 我忽略的被测代码中有错误吗? 我通常会在10,000次中发生5次。

0 个答案:

没有答案