簡體   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