StackExchange.Precompilation - 如何对预编译诊断进行单元测试?

Jam*_*aix 6 c# unit-testing precompile roslyn

背景

我正在使用StackExchange.Precompilation在C#中实现面向方面的编程. 在GitHub上查看我的存储库.

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

if (Object.Equals(p, null)) throw new ArgumentNullException(nameof(p));
Run Code Online (Sandbox Code Playgroud)

在方法体的开头.


诊断很棒......

我想让错误地使用这些属性变得困难.我发现的最好的方法(除了直观设计)是Diagnostic为了无效或不合逻辑地使用属性创建编译时间.

例如,NonNullAttribute对值类型成员使用没有意义.(即使对于可以为空的值类型,因为如果你想保证它们不是null,那么应该使用非可空类型.)创建Diagnostic一个很好的方法来告知用户这个错误,而不会像这样构建崩溃一个例外.


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

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

public class TestClass {
    public void ShouldCreateDiagnostic([NonNull] int n) { }       
}
Run Code Online (Sandbox Code Playgroud)

并确认已创建正确的诊断(或在某些情况下未创建诊断).

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


解:

@ m0sa给出的答案非常有帮助.实现有很多细节,所以这里的单元测试看起来很像(使用NUnit 3).注意using staticfor SyntaxFactory,这消除了语法树构造中的大量混乱.

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)
            };
    }
}
Run Code Online (Sandbox Code Playgroud)

m0s*_*0sa 2

我认为您想在实际发出/编译之前添加诊断,因此步骤是:

  1. 创建您的CSharpCompilation,在进一步操作之前确保它没有诊断错误
  2. 创建一个BeforeCompileContext,并用编译和一个空的填充它List<Diagnostic>
  3. 创建您的实例ICompileModuleICompileModule.BeforeCompile使用步骤 2 中的上下文进行调用
  4. 检查它是否包含所需的Diagnostic