2016-12-14 59 views
6

背景StackExchange.Precompilation - 如何单元测试预编译诊断?

我正在使用StackExchange.Precompilation在C#中实现面向方面的编程。 See my repository on GitHub.

其基本思想是客户端代码将能够在成员上放置自定义属性,并且预编译器将对具有这些属性的任何成员执行语法转换。一个简单的例子是我创建的NonNullAttribute。当NonNullAttribute放置在参数p,预编译器将在方法本体的开头插入

if (Object.Equals(p, null)) throw new ArgumentNullException(nameof(p)); 


诊断是真棒...

我想使其难以错误地使用这些属性。我发现的最佳方式(除直观设计外)是为无效或不合逻辑的属性使用创建编译时间Diagnostic。例如,NonNullAttribute在值类型成员上使用没有意义。 (即使对于可为null的值类型,因为如果你想保证它们不是null,那么应该使用不可为空的类型。)创建一个Diagnostic是向用户通知此错误的好方法,而不会崩溃构建就像一个例外。


...但我该如何测试它们?

诊断是强调错误的好方法,但我也想确保我的诊断创建代码没有错误。我希望能够建立一个单元测试,可以预编译的代码示例这样

public class TestClass { 
    public void ShouldCreateDiagnostic([NonNull] int n) { }  
} 

,并确认正确的诊断是创建(或在某些情况下,没有诊断已创建)。

任何人都可以熟悉StackExchange.Precompilation给我一些指导呢?


解决方案:

通过@ m0sa给出的答案是难以置信的帮助。实现有很多细节,所以这里的单元测试看起来像(使用NUnit 3)。注意using staticSyntaxFactory,这消除了语法树构造中的大量混乱。

using System.Collections.Generic; 
using System.Linq; 
using Microsoft.CodeAnalysis; 
using Microsoft.CodeAnalysis.CSharp; 
using Microsoft.CodeAnalysis.CSharp.Syntax; 
using NUnit.Framework; 
using StackExchange.Precompilation; 
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 

namespace MyPrecompiler.Tests { 

    [TestFixture] 
    public class NonNull_CompilationDiagnosticsTest { 

     [Test] 
     public void NonNullAttribute_CreatesDiagnosticIfAppliedToValueTypeParameter() { 

      var context = new BeforeCompileContext { 
       Compilation = TestCompilation_NonNullOnValueTypeParameter(), 
       Diagnostics = new List<Diagnostic>() 
      }; 

      ICompileModule module = new MyPrecompiler.MyModule(); 

      module.BeforeCompile(context); 

      var diagnostic = context.Diagnostics.SingleOrDefault(); 

      Assert.NotNull(diagnostic); 
      Assert.AreEqual("MyPrecompiler: Invalid attribute usage", 
          diagnostic.Descriptor.Title.ToString()); //Must use ToString() because Title is a LocalizeableString 
     } 

     //Make sure there are spaces before the member name, parameter names, and parameter types. 
     private CSharpCompilation TestCompilation_NonNullOnValueTypeParameter() { 
      return CreateCompilation(
       MethodDeclaration(ParseTypeName("void"), Identifier(" TestMethod")) 
       .AddParameterListParameters(
        Parameter(Identifier(" param1")) 
         .WithType(ParseTypeName(" int")) 
         .AddAttributeLists(AttributeList() 
              .AddAttributes(Attribute(ParseName("NonNull")))))); 
     } 

     //Make sure to include Using directives 
     private CSharpCompilation CreateCompilation(params MemberDeclarationSyntax[] members) { 

      return CSharpCompilation.Create("TestAssembly") 
       .AddReferences(References) 
       .AddSyntaxTrees(CSharpSyntaxTree.Create(CompilationUnit() 
        .AddUsings(UsingDirective(ParseName(" Traction"))) 
        .AddMembers(ClassDeclaration(Identifier(" TestClass")) 
           .AddMembers(members)))); 
     } 

     private string runtimePath = @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\"; 

     private MetadataReference[] References => 
      new[] { 
       MetadataReference.CreateFromFile(runtimePath + "mscorlib.dll"), 
       MetadataReference.CreateFromFile(runtimePath + "System.dll"), 
       MetadataReference.CreateFromFile(runtimePath + "System.Core.dll"), 
       MetadataReference.CreateFromFile(typeof(NonNullAttribute).Assembly.Location) 
      }; 
    } 
} 

回答

2

我想你想的实际EMIT /编译之前加你诊断,这样的步骤将是:

  1. 创建CSharpCompilation,继续之前
  2. 确保它有没有诊断错误
  3. 创建一个BeforeCompileContext,并使用汇编填充它并创建一个空List<Diagnostic>
  4. 创建您的ICompileModule的实例并调用ICompileModule.BeforeCompile上下文f ROM步骤2
  5. 检查它是否包含所需的Diagnostic
+0

非常感谢。我现在正在尝试此操作并将回报。 – JamesFaix