【问题标题】:FxCop rule around ensuring a certain method accepting a lambda is called first in a testFxCop 规则确保在测试中首先调用某个接受 lambda 的方法
【发布时间】:2013-03-01 02:58:28
【问题描述】:

使用自定义 FXCop 规则,我想确保在每个单元测试的顶部调用一个方法,并且所有单元测试代码都是传递给该方法的 Action 的一部分。基本上我想要这个:

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

诀窍是确保在运行语句之前没有在测试顶部调用的内容。过去几个小时我一直在研究 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”;
}

【问题讨论】:

  • 您也许可以使用表达式树来实现这一点。我会尝试发布一个示例,可能是。
  • 如何使用表达式树? FxCop 只能访问 IL,不能访问源代码。

标签: c# visual-studio-2010 analysis fxcop il


【解决方案1】:

虽然您可以使用 Method.Body 访问类似结构的表达式树,但我可能还是会检查这些指令,因为我发现它会被过去的常见情况(例如内联数组初始化)弄糊涂。

C# 如何生成 lambda 表达式取决于 lambda 表达式访问的内容:

  • 如果 lambda 表达式访问任何局部变量/参数,它将创建一个对象来保存可以从 lambda 表达式访问的值,然后创建一个委托给该对象的实例方法。
  • 如果 lambda 表达式通过 this 访问任何实例成员,那么它将简单地为 this 上的实例方法创建委托。
  • 否则,如果 lambda 表达式不访问局部变量或字段:
    • 在 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 方法运行似乎有点武断。如果目标是让Run 方法提供通用的设置/拆卸逻辑,则可以通过 nunit 提供更好的方法。强制人们使用您的action attribute 比强制他们以特定方式编写测试要容易得多。

或者,您可以编写一个 Roslyn 分析器;分析器可以访问描述代码编写方式的语法树,而无需从 IL 和元数据中对结构进行逆向工程。

【讨论】:

    猜你喜欢
    • 2014-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-09
    相关资源
    最近更新 更多