FxCop 规则确保在测试中首先调用某个接受 lambda 的方法

Posted

技术标签:

【中文标题】FxCop 规则确保在测试中首先调用某个接受 lambda 的方法【英文标题】:FxCop rule around ensuring a certain method accepting a lambda is called first in a test 【发布时间】: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,不能访问源代码。 【参考方案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 和元数据中对结构进行逆向工程。

【讨论】:

以上是关于FxCop 规则确保在测试中首先调用某个接受 lambda 的方法的主要内容,如果未能解决你的问题,请参考以下文章

FxCop 规则确保每个类都有唯一的标识符

如何编写代码分析 (FxCop) 规则以防止方法调用

FxCop,组成依赖程序集的调用者列表

单元测试自定义 FxCop 规则

FXCop 规则接口方法应该可以被子类型调用

FxCop:检查程序集信息值的自定义规则