2

カスタム FXCop ルールを使用して、各単体テストの先頭でメソッドが呼び出され、すべての単体テスト コードがそのメソッドに渡されるアクションの一部であることを確認したいと考えています。基本的に私はこれが欲しい:

    [TestMethod]
    public void SomeTest()
    {
        Run(() => {
            // ALL unit test code has to be inside a Run call
        });
    }

Run が実際に呼び出されていることを確認するのは難しくありません。

public override void VisitMethod(Method member)
    {
        var method = member as Method;
        if (method == null || method.Attributes == null)
            return;

        if (method.Attributes.Any(attr => attr.Type.Name.Name == "TestMethodAttribute") &&
            method.Instructions != null)
        {
             if (!method.Instructions.Any(i => i.OpCode == OpCode.Call || i.Value.ToString() == "MyNamespace.Run"))
            {
                this.Problems.Add(new Problem(this.GetResolution(), method.GetUnmangledNameWithoutTypeParameters()));
            }

        base.VisitMethod(method);
    }

秘訣は、テストの先頭に、Run ステートメントの前に呼び出されるものがないことを確認することです。 ここ数時間、Instructions コレクションのパターンをハッキングし、コードで Body.Statements コレクションを効果的に使用する方法を理解しようと努めてきました。


これは、単純な IL の質問として提起することもできます。 これを受け入れる検証可能な特定のパターンを知りたい:

public void SomeTest()
{
    Run(() => {
        // Potentially lots of code
    });
}

ただし、次のいずれかを拒否します。

public void SomeTest()
{
    String blah = “no code allowed before Run”;
    Run(() => {
        // Potentially lots of code
    });
}

public void SomeTest()
{
    Run(() => {
        // Potentially lots of code
    });
    String blah = “no code allowed after Run”;
}
4

1 に答える 1

1

を使用して式ツリーのような構造Method.Bodyにアクセスできますが、過去によくある状況 (インライン配列の初期化など) で混乱することがわかったので、おそらくとにかく命令を調べます。

C# がラムダ式を生成する方法は、ラムダ式が何にアクセスするかによって異なります。

  • ラムダ式が任意のローカル/パラメーターにアクセスする場合、ラムダ式からアクセスできる場所にそれらの値を保持するオブジェクトを作成し、オブジェクトのインスタンス メソッドへのデリゲートを作成します。
  • ラムダ式が を介して任意のインスタンス メンバーにアクセスする場合this、 のインスタンス メソッドへのデリゲートを作成するだけthisです。
  • それ以外の場合、ラムダ式がローカルまたはフィールドにアクセスしない場合:
    • VS2015 に付属する Roslyn コンパイラより前は、静的メソッドへのデリゲートを作成し、それを静的フィールドにキャッシュしていました。
    • Roslyn コンパイラの時点では、ネストされたクラスでインスタンス メソッドを作成し、デリゲートを静的メソッドにキャッシュします。(この変更はパフォーマンス上の理由から行われました。インスタンス メソッドへのデリゲートの呼び出しは、引数をシャッフルする必要がないため、静的メソッドへのデリゲートの呼び出しよりも高速です。)

おそらく、独自の評価スタックを作成し、メソッドを介して値をトレースすることができます (私は過去にこれを行う必要がありましたが、重要でかなりのコードでしたが、特に難しくはありませんでした)、「十分に良い」を達成できると思います。 " 次のルールを適用するだけです。

  • メソッドのすべてのローカルは、コンパイラによって生成される必要があります。
  • 静的フィールドは、コンパイラによって生成された場合にのみアクセスできます。
  • System.Actionコンパイラで生成された型のみを作成できます。
  • のみRun呼び出すことができ、一度だけ呼び出す必要があります。
  • への呼び出しのRun後には、命令が続く必要があります(それらの間に出現する可能性retのある命令は無視されます)。nop
  • への呼び出しの後に分岐命令がジャンプすることはありませんRun
  • 次の指示以外は禁止します。
    • 分岐命令 (条件付きおよび無条件)
    • 引数、ローカルおよびフィールド アクセサー命令。
    • newobj, call, callvirt, ldftn, nop, ret_ldnull
    • _Locals(これは、FxCop がローカル変数に挿入する単なる疑似命令です。)

FxCop はRuleUtilities.IsCompilerGenerated、ローカルがコンパイラによって生成されたものかどうかを判断する機能を提供しますが、フィールドの場合は役に立たず、FxCop が pdb ファイルを見つけることができる場合、ローカルのみを疑っています。「型名が C# で有効な識別子でない場合、ローカル/フィールド/型はコンパイラによって生成される」と言う方が簡単かもしれません。


そうは言っても、すべてのテストがRunメソッドを介して完全に実行されると主張することは、少し恣意的なように思えます。メソッドが共通のセットアップ/破棄ロジックを提供することが目標である場合、Runnunit を介して提供されるより良い方法があります。特定の方法でテストを書くことを強制するよりも、アクション属性を適用することを強制する方がはるかに簡単です。

または、代わりに Roslyn アナライザーを作成することもできます。アナライザーは、IL およびメタデータから構造をリバース エンジニアリングすることなく、コードがどのように記述されたかを説明する構文ツリーにアクセスできます。

于 2014-11-23T03:40:01.420 に答える