13

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

カバレッジの結果は次のとおりです。

カバレッジ結果

ブロックの右中括弧は、using実際にDisposeは列挙子に対する呼び出しであると想定しています。しかし、なぜ実行されないのですか?私は最初、NUnit が列挙子を破棄していないのではないかと疑っていましたが、 foreach を実行しても同じ結果が得られactualます。

2 つ目のカバーされていない右中括弧については、それが何を表しているのかわかりません...コンパイラが反復子ブロックを変換する方法に関連していると思います。

これら2つの「ステートメント」が何であるか、そしてなぜそれらが実行されないのかについて、誰かが光を当てることができますか?


編集: ピーターは非常に良い質問を提起しました: 上記の結果は、デバッグ ビルドでテストを実行したときに得られたものです。リリース ビルドでテストを実行すると、メソッドのカバレッジCommonPrefixImplは 100% になるため、コンパイラの最適化に関連している可能性があります。

4

2 に答える 2

12

反復子メソッドの問題の 1 つは、反復子メソッド内のコードの遅延実行を管理するために、コンパイラがかなり大きく複雑なステート マシンを生成することです。これにより、通常、1 つまたは 2 つのクラスが生成されます。これらのクラスは、特定のケースではなく一般的なケースを処理することを目的としているため、使用されないコードが少なくとも少し含まれている可能性があります。ILSpy、JustDecompile、Reflector などのツールを使用してアセンブリを調べることで、生成されたものを確認できます。C# コンパイラによって生成されたアセンブリ内のクラスが表示されます (通常、「<」などを含むクラス名)。

プロファイラーが知っているのは、PDB がコードにどのように関連付けられているかということです。作成したすべてのコードが実行される可能性があるにもかかわらずコンパイラーによって生成されたすべてのコードが実行されるわけではない可能性があります。プロファイラーはおそらくこれを認識しておらず、単に特定の反復子メソッドの特定の割合 (100 未満) が実行されたと言っています。

生成される可能性が高いものの 1 つは、例外処理コードです。コンパイラは、コードが例外を生成しない、または生成できない可能性があることを認識していないため、例外を補うコードを生成します。状態が破損しないようにする必要があります。何らかのフラグに基づいて反復子メソッドのさまざまな場所で例外をスローする方法を含め、そのメソッドを 2 回 (同じ実行で例外なしで 1 回、例外ありで 1 回) 実行した場合、パーセンテージが異なる可能性が高くなります。生成された例外処理コードが実行されるためです。

メソッドの最後が「実行されていないように見える」という事実は、そのコードが実行されるステートマシンの別のメソッドの一部であり、コンパイラがその生成されたコードからクラス内のコードへの関連付けを生成しないためです。 .

更新:コンパイラが何をしているかをよりよく理解し、コンパイラが生成するコードの種類の例を確認するには、C# 仕様のセクション10.14 イテレータを参照してください ( http://www.microsoft.com/en-us/download/details .aspx?id=7029 )

于 2012-08-15T02:14:36.557 に答える