简体   繁体   中英

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.

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:

/// <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:

/// <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. Also the thing is, that Range signature does not have a source parameter, so the same approach can not be used.

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.

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. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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