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

本文关键字:lambda 方法 调用 确保 规则 测试 FxCop | 更新日期: 2023-09-27 18:01:36

使用自定义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);
    }

技巧是确保在测试的顶部没有被称为Run语句之前的东西。在过去的几个小时里,我一直在研究模式的指令集,并试图理解如何使用Body。有效地在代码中收集语句。


这也可以作为一个简单的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规则确保在测试中首先调用某个接受lambda的方法

虽然您可以使用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可以提供更好的方法。强制人们应用您的操作属性比强制他们以特定的方式编写测试要容易得多。

或者,你可以写一个Roslyn分析器代替;分析器可以访问描述代码如何编写的语法树,而不必从IL中反向工程结构。元数据。