背景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 static
为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)
};
}
}
非常感谢。我现在正在尝试此操作并将回报。 – JamesFaix