简体   繁体   English

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

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

I have implemented some sort of LINQ Range operator and do want to have a test which will verify that the Range operator is actually deferred.我已经实现了某种 LINQ Range 运算符,并且确实希望进行测试以验证 Range 运算符实际上是否被延迟。

My Range operator methods:我的范围运算符方法:

/// <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;
    }
}

For the other deferred operators I have created ThrowingExceptionEnumerable utility class, which helps with testing:对于其他延迟运算符,我创建了 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());
    }

And for instance deferred Select operator has the following test:例如,延迟的 Select 运算符具有以下测试:

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

The first problem I have faced during writing such unit test for the Range operator is that Range is actually a static method and not an extension method.在为 Range 运算符编写此类单元测试时,我遇到的第一个问题是 Range 实际上是 static 方法而不是扩展方法。 Also the thing is, that Range signature does not have a source parameter, so the same approach can not be used.另外问题是,Range 签名没有源参数,因此不能使用相同的方法。

Do you have some clever ideas, how it can be tested?你有一些聪明的想法,如何测试它?

External code isn't going to be able to do anything to verify that the values are generated on the fly.外部代码将无法做任何事情来验证这些值是即时生成的。 The only actual difference between a method like this and one that materializes a collection and returns it is the memory footprint at scale, which is quite difficult to reliably test in a unit test.像这样的方法与实现集合并返回它的方法之间的唯一实际区别是大规模的 memory 足迹,这很难在单元测试中可靠地测试。

You can clearly tell that it doesn't do that my looking at the code, but you'd need to alter the implementation in some pretty significant way to end up with something that would allow you to verify that in a unit test (such as writing a more generalized "Generate" method that used a delegate to generate the next value).您可以清楚地告诉我看代码并没有这样做,但是您需要以某种非常重要的方式更改实现,以最终得到可以让您在单元测试中验证这一点的东西(例如编写一个更通用的“生成”方法,该方法使用委托来生成下一个值)。

If you had some sort of hard requirement that your implementation has unit tests to verify such things, I'd write such a Generate method, implement your Range method by calling Generate , write a unit test to verify that Generate doesn't call the delegate until generating the next value in the sequence, and then assert that the Range method defers execution because it uses Generate to produce its sequence.如果您有某种硬性要求,即您的实现具有单元测试来验证这些事情,我会编写这样一个Generate方法,通过调用Generate实现您的Range方法,编写一个单元测试来验证Generate不会调用委托直到生成序列中的下一个值,然后断言Range方法延迟执行,因为它使用Generate来生成其序列。 I wouldn't want to do this in production code though, this would really be just a way of meeting the requirement and making some sacrifices in readability and (mild) performance for the sake of it.不过,我不想在生产代码中这样做,这实际上只是满足要求并为此牺牲一些可读性和(温和)性能的一种方式。

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

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