简体   繁体   English

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

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

Using a custom FXCop rule, I want to ensure that a method is called at the top of each unit test and that all unit test code is part of an Action passed into that method. 使用自定义FXCop规则,我想确保在每个单元测试的顶部都调用一个方法,并且确保所有单元测试代码都是传递给该方法的Action的一部分。 Essentially I want this: 本质上我想要这样:

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

It's not hard to ensure that Run is indeed called: 确保确实调用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);
    }

The trick is to ensure there isn't something at the top of the test that is called BEFORE the Run statement. 诀窍是确保测试的顶部没有Run语句之前的内容。 I've spent the past few hours hacking at the Instructions collection for patterns and trying to understand how to use the Body.Statements collection in code effectively. 在过去的几个小时中,我花了很多时间在指令集上研究模式,并试图了解如何在代码中有效地使用Body.Statements集。


This could also be posed as a simple IL question. 这也可以作为一个简单的IL问题提出。 I want to know a specific pattern I can validate that will accept this: 我想知道一个可以验证的特定模式,我可以接受它:

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

But will reject either of these: 但是将拒绝以下任何一个:

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”;
}

Although you can access an expression tree like structure using Method.Body I would probably examine the instructions anyway as I have found it to get confused by common situations in the past (eg. inline array initialisation). 尽管您可以使用Method.Body访问类似表达式树的结构, Method.Body我可能还是会仔细研究一下指令,因为我发现它与过去的常见情况(例如,内联数组初始化)相混淆。

How C# generates the lambda expression depends on what the lambda expression accesses: C#如何生成lambda表达式取决于lambda表达式访问的内容:

  • If the lambda expression accesses any locals/parameters, it will create an object to hold those values where they can be accessed from the lambda expression, and then create a delegate to an instance method on the object. 如果lambda表达式访问任何局部变量/参数,它将创建一个对象以保存可以从lambda表达式访问它们的值,然后在该对象上创建一个实例方法的委托。
  • If the lambda expression accesses any instance members via this , then it will simply create the delegate to the instance method on this . 如果lambda表达式通过this访问任何实例成员,则它将仅在this上创建实例方法的委托。
  • Otherwise if the lambda expression doesn't access locals or fields: 否则,如果lambda表达式无法访问本地或字段:
    • Prior to the Roslyn compiler that came with VS2015, it would create a delegate to a static method and cache it in a static field. 在VS2015随附的Roslyn编译器之前,它将创建静态方法的委托并将其缓存在静态字段中。
    • As of the Roslyn compiler, it would create an instance method on a nested class and cache the delegate in a static method. 从Roslyn编译器开始,它将在嵌套类上创建实例方法,并将委托缓存在静态方法中。 (this change was made for performance reasons, calling a delegate to an instance method is faster than calling a delegate to a static method as it doesn't have to shuffle arguments across.) (进行此更改是出于性能方面的考虑,调用实例方法的委托要比调用静态方法的委托要快,因为它不必在参数之间进行随机组合。)

You could probably create your own evaluation stack and trace the values through the method (I have had to do this in the past, non-trivial and a fair bit of code but not particularly difficult), but I suspect you could achieve "good enough" just by enforcing the following rules: 您可能可以创建自己的评估堆栈并通过该方法跟踪值(我过去曾经做过,虽然很简单,并且代码很少,但并不是特别困难),但是我怀疑您可以实现“足够好只需执行以下规则即可:

  • All locals for the method must be compiler generated. 该方法的所有本地语言都必须由编译器生成。
  • Static fields may be accessed only if they are compiler generated. 只有静态字段是编译器生成的,才可以访问它们。
  • Only System.Action and compiler generated types may be created. 只能创建System.Action和编译器生成的类型。
  • Only Run may be called, and it must be called exactly once. 只能调用Run ,并且必须恰好调用一次。
  • The call to Run must be followed by a ret instruction (ignoring any number of nop instructions that may appear between them). Run的调用必须紧跟一个ret指令(忽略它们之间可能出现的任意数量的nop指令)。
  • No branch instruction may jump to a location after the call to Run . 调用Run之后,任何分支指令都不能跳转到某个位置。
  • Prohibit all instructions other than the following: 禁止以下所有指示:
    • branch instructions (conditional and unconditional) 分支指令(有条件的和无条件的)
    • argument, local and field accessor instructions. 参数,本地和字段访问器说明。
    • newobj , call , callvirt , ldftn , nop , ret , ldnull newobjcallcallvirtldftnnopretldnull
    • _Locals (this is just a pseudo instruction that FxCop inserts for local variables.) _Locals (这只是FxCop为局部变量插入的伪指令。)

FxCop provides RuleUtilities.IsCompilerGenerated to determine if a local is compiler generated, but it won't help for fields and I suspect only for locals if FxCop can find the pdb file. FxCop提供RuleUtilities.IsCompilerGenerated来确定本地是否由编译器生成,但是它对字段没有帮助,我怀疑如果FxCop可以找到pdb文件,则仅对本地用户有用。 You might find it easier to say "the local/field/type is compiler generated if it's type name is not a valid identifier in C#". 您可能会发现说“本地/字段/类型是编译器生成的,如果它的类型名称在C#中不是有效的标识符”会更容易说出来。


Having said all that, insisting that all tests run entirely via the Run method seems a little arbitrary. 说了这么多,坚持所有测试都完全通过Run方法Run似乎有些武断。 If the goal is for the Run method to provide common setup/tear-down logic, there are better ways provided via nunit. 如果目标是Run方法提供通用的设置/拆卸逻辑,则可以通过nunit提供更好的方法。 Enforcing that people apply your action attribute is much easier than enforcing that they write their test in a particular way. 强制人们应用您的动作属性比强制他们以特定方式编写测试要容易得多。

Alternatively, you could write a Roslyn analyzer instead; 另外,您也可以编写罗斯林分析仪。 analyzers have access to the syntax tree describing how the code was written, without having to reverse engineer the structure from the IL & metadata. 分析人员可以访问描述代码编写方式的语法树,而无需对IL和元数据进行结构反向工程。

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

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