繁体   English   中英

如何测试 LINQ 范围运算符是否已延迟?

[英]How can I test that LINQ Range operator is deferred?

我已经实现了某种 LINQ Range 运算符,并且确实希望进行测试以验证 Range 运算符实际上是否被延迟。

我的范围运算符方法:

/// <summary>
/// The Range static method, validation part.
/// </summary>
/// <param name="start">The start.</param>
/// <param name="count">The count.</param>
/// <returns></returns>
public static IEnumerable<int> Range(int start, int count)
{
    long max = ((long) start) + count - 1;
    if (count < 0 || max > Int32.MaxValue) throw new ArgumentOutOfRangeException(nameof(count));
    return RangeIterator(start, count);
}

/// <summary>
/// The Range operator iterator.
/// </summary>
/// <param name="start">The start.</param>
/// <param name="count">The count.</param>
/// <returns></returns>
static IEnumerable<int> RangeIterator(int start, int count)
{
    for (int i = 0; i < count; ++i)
    {
        yield return start + i;
    }
}

对于其他延迟运算符,我创建了 ThrowingExceptionEnumerable 实用程序 class,它有助于测试:

/// <summary>
/// The class responsible for verifying that linq operator is deferred.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class ThrowingExceptionEnumerable<T> : IEnumerable<T>
{
    /// <summary>
    /// The methods throws <see cref="InvalidOperationException"/>.
    /// </summary>
    /// <returns></returns>
    public IEnumerator<T> GetEnumerator()
    {
        throw new InvalidOperationException();
    }

    /// <inheritdoc />
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    /// <summary>
    /// The method which checks that the given <see cref="deferredFunction"/> actually uses deferred execution.
    /// When the function just call itself it should not throw an exception. But, when using the result
    /// by calling <see cref="GetEnumerator"/> and than GetNext() methods should throws the <see cref="InvalidOperationException"/>.
    /// </summary>
    /// <typeparam name="TSource">The deferred function source type.</typeparam>
    /// <typeparam name="TResult">The deferred function result type.</typeparam>
    /// <param name="deferredFunction">The deferred function (unit of work under the test).</param>
    public static void AssertDeferred<TSource,TResult>(
        Func<IEnumerable<TSource>, IEnumerable<TResult>> deferredFunction)
    {
        var source = new ThrowingExceptionEnumerable<TSource>();
        
        // Does not throw any exception here, because GetEnumerator() method is not yet used.
        var result = deferredFunction(source);

        // Does not throw InvalidOperationException even here, despite the fact that we retrieve the enumerator.
        using var iterator = result.GetEnumerator();

        Assert.Throws<InvalidOperationException>(() => iterator.MoveNext());
    }

例如,延迟的 Select 运算符具有以下测试:

/// <summary>
/// Should check that Select operator is deferred.
/// </summary>
[Fact]
public void VerifySelectExecutionIsDeferred()
{
    ThrowingExceptionEnumerable<int>.AssertDeferred<int, int>(source => source.Select(x => x));
}

在为 Range 运算符编写此类单元测试时,我遇到的第一个问题是 Range 实际上是 static 方法而不是扩展方法。 另外问题是,Range 签名没有源参数,因此不能使用相同的方法。

你有一些聪明的想法,如何测试它?

外部代码将无法做任何事情来验证这些值是即时生成的。 像这样的方法与实现集合并返回它的方法之间的唯一实际区别是大规模的 memory 足迹,这很难在单元测试中可靠地测试。

您可以清楚地告诉我看代码并没有这样做,但是您需要以某种非常重要的方式更改实现,以最终得到可以让您在单元测试中验证这一点的东西(例如编写一个更通用的“生成”方法,该方法使用委托来生成下一个值)。

如果您有某种硬性要求,即您的实现具有单元测试来验证这些事情,我会编写这样一个Generate方法,通过调用Generate实现您的Range方法,编写一个单元测试来验证Generate不会调用委托直到生成序列中的下一个值,然后断言Range方法延迟执行,因为它使用Generate来生成其序列。 不过,我不想在生产代码中这样做,这实际上只是满足要求并为此牺牲一些可读性和(温和)性能的一种方式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM