迭代器块的奇怪测试覆盖率结果,为什么这些语句没有被执行?

时间:2012-08-12 00:40:41

标签: c# unit-testing iterator code-coverage dotcover

我正在使用dotCover来分析我的单元测试的代码覆盖率,并且我得到了一些奇怪的结果......我有一个迭代器方法,覆盖范围不完整,但是未涵盖的语句只是方法结束时的结束括号。

这是我正在测试的方法:

    public static IEnumerable<T> CommonPrefix<T>(
        this IEnumerable<T> source,
        IEnumerable<T> other,
        IEqualityComparer<T> comparer)
    {
        source.CheckArgumentNull("source");
        other.CheckArgumentNull("other");

        return source.CommonPrefixImpl(other, comparer);
    }

    private static IEnumerable<T> CommonPrefixImpl<T>(
        this IEnumerable<T> source,
        IEnumerable<T> other,
        IEqualityComparer<T> comparer)
    {
        comparer = comparer ?? EqualityComparer<T>.Default;

        using (IEnumerator<T> en1 = source.GetEnumerator(),
                              en2 = other.GetEnumerator())
        {
            while (en1.MoveNext() && en2.MoveNext())
            {
                if (comparer.Equals(en1.Current, en2.Current))
                    yield return en1.Current;
                else
                    yield break;
            }
        } // not covered
    } // not covered

单元测试:

    [Test]
    public void Test_CommonPrefix_SpecificComparer()
    {
        var first = new[] { "Foo", "Bar", "Baz", "Titi", "Tata", "Toto" };
        var second = new[] { "FOO", "bAR", "baz", "tata", "Toto" };

        var expected = new[] { "Foo", "Bar", "Baz" };
        var actual = first.CommonPrefix(second, StringComparer.CurrentCultureIgnoreCase);
        Assert.That(actual, Is.EquivalentTo(expected));
    }

报道结果:

coverage results

我假设using块的右括号实际上是对枚举器的Dispose的调用;但是,为什么它没有被执行?我首先怀疑NUnit没有处理调查员,但是如果我在actual做一个foreach,我会得到相同的结果。

至于第二个未覆盖的右括号,我不知道它代表什么......我猜这与编译器如何转换迭代器块有关。

任何人都可以了解这两个“陈述”是什么,以及为什么不执行这些陈述?


编辑:Peter提出了一个非常好的问题:上面显示的结果是在调试版本上运行测试时获得的。如果我在发布版本上运行测试,CommonPrefixImpl方法的覆盖率为100%,因此它可能与编译器优化有关。

2 个答案:

答案 0 :(得分:12)

迭代器方法的一个问题是编译器生成一个相当大且复杂的状态机来管理迭代器方法中代码的延迟执行。这通常会产生一两个类。这些类是为了处理一般情况而不是你的特定情况,因此可能至少有一些从未使用过的代码。您可以通过使用ILSpy,JustDecompile或Reflector等工具查看程序集来查看生成的内容。它将显示由C#编译器生成的程序集中的类(通常包含'&lt;'等的类名)

探查器知道的是PDB如何与您的代码相关联,尽管您编写的所有代码可能正在被执行,但仍有可能并非所有代码都由编译器已执行。探查器可能不知道这一点,只是说某个特定迭代器方法的某个百分比(小于100)被执行了。

可能产生的一件事是异常处理代码。因为编译器不知道你的代码不会或者可能不能生成异常,所以它仍然会生成代码来补偿异常 - 它需要保持它的状态不被破坏。我敢打赌,如果你包含一种方法在基于某个标志的迭代器方法中的各个地方抛出一个异常,并且运行该方法两次(一次没有异常,一次在同一次运行中有异常),百分比会有所不同 - 可能更高因为生成的异常处理代码会被执行。

方法“似乎”未执行的事实可能是因为该代码是状态机中执行的不同方法的一部分,并且编译器从不生成从生成的代码到代码的关联在你班上。

更新:,以便更好地了解编译器正在做什么,并查看它生成的代码类型的示例,请参阅C#规范中的 10.14 Iterators 部分( http://www.microsoft.com/en-us/download/details.aspx?id=7029

答案 1 :(得分:0)

除了您的问题和详细的答案之外,我还有以下行为。

    // less than 100% coverage
    public static IEnumerable<T> ForEachYieldDo<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var x in source)
        {
            action(x);
            yield return x;
        }
    }

    // 100% code coverage
    public static IEnumerable<T> ForEachSelectDo<T>(this IEnumerable<T> source, Action<T> action)
    {
        return source.Select(x =>
        {
            action(x);
            return x;
        });
    }

两个功能具有相同的行为。仅在处理项目后才执行该动作。如果停止检索项目,则不会执行该操作。