繁体   English   中英

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

[英]FxCop rule around ensuring a certain method accepting a lambda is called first in a test

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

尽管您可以使用Method.Body访问类似表达式树的结构, Method.Body我可能还是会仔细研究一下指令,因为我发现它与过去的常见情况(例如,内联数组初始化)相混淆。

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

  • 如果lambda表达式访问任何局部变量/参数,它将创建一个对象以保存可以从lambda表达式访问它们的值,然后在该对象上创建一个实例方法的委托。
  • 如果lambda表达式通过this访问任何实例成员,则它将仅在this上创建实例方法的委托。
  • 否则,如果lambda表达式无法访问本地或字段:
    • 在VS2015随附的Roslyn编译器之前,它将创建静态方法的委托并将其缓存在静态字段中。
    • 从Roslyn编译器开始,它将在嵌套类上创建实例方法,并将委托缓存在静态方法中。 (进行此更改是出于性能方面的考虑,调用实例方法的委托要比调用静态方法的委托要快,因为它不必在参数之间进行随机组合。)

您可能可以创建自己的评估堆栈并通过该方法跟踪值(我过去曾经做过,虽然很简单,并且代码很少,但并不是特别困难),但是我怀疑您可以实现“足够好只需执行以下规则即可:

  • 该方法的所有本地语言都必须由编译器生成。
  • 只有静态字段是编译器生成的,才可以访问它们。
  • 只能创建System.Action和编译器生成的类型。
  • 只能调用Run ,并且必须恰好调用一次。
  • Run的调用必须紧跟一个ret指令(忽略它们之间可能出现的任意数量的nop指令)。
  • 调用Run之后,任何分支指令都不能跳转到某个位置。
  • 禁止以下所有指示:
    • 分支指令(有条件的和无条件的)
    • 参数,本地和字段访问器说明。
    • newobjcallcallvirtldftnnopretldnull
    • _Locals (这只是FxCop为局部变量插入的伪指令。)

FxCop提供RuleUtilities.IsCompilerGenerated来确定本地是否由编译器生成,但是它对字段没有帮助,我怀疑如果FxCop可以找到pdb文件,则仅对本地用户有用。 您可能会发现说“本地/字段/类型是编译器生成的,如果它的类型名称在C#中不是有效的标识符”会更容易说出来。


说了这么多,坚持所有测试都完全通过Run方法Run似乎有些武断。 如果目标是Run方法提供通用的设置/拆卸逻辑,则可以通过nunit提供更好的方法。 强制人们应用您的动作属性比强制他们以特定方式编写测试要容易得多。

另外,您也可以编写罗斯林分析仪。 分析人员可以访问描述代码编写方式的语法树,而无需对IL和元数据进行结构反向工程。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM