StackExchange.Precompilation - 如何对预编译诊断进行单元测试?
StackExchange.Precompilation - How can I unit test precompilation diagnostics?
背景
我正在使用 StackExchange.Precompilation 在 C# 中实现面向方面的编程。 See my repository on GitHub.
基本思想是客户端代码将能够在成员上放置自定义属性,预编译器将对具有这些属性的任何成员执行语法转换。一个简单的例子是我创建的 NonNullAttribute
。当 NonNullAttribute
放在参数 p
上时,预编译器将插入
if (Object.Equals(p, null)) throw new ArgumentNullException(nameof(p));
在方法体的开头。
诊断很棒...
我想让错误使用这些属性变得困难。我发现的最好方法(除了直观的设计)是为无效或不合逻辑的属性使用创建编译时 Diagnostic
s。
例如,NonNullAttribute
对值类型成员没有意义。 (即使对于可为空的值类型,因为如果你想保证它们不为空,那么应该使用不可为空的类型。)创建 Diagnostic
是通知用户此错误的好方法,不会像异常一样使构建崩溃。
...但是我该如何测试它们呢?
诊断是突出显示错误的好方法,但我也想确保我的诊断创建代码没有错误。我希望能够设置一个单元测试来预编译这样的代码示例
public class TestClass {
public void ShouldCreateDiagnostic([NonNull] int n) { }
}
并确认创建了正确的诊断(或者在某些情况下没有创建诊断)。
熟悉 StackExchange.Precompilation 的人可以给我一些指导吗?
解法:
@m0sa 给出的答案非常有帮助。实现有很多细节,所以这里是单元测试的实际样子(使用 NUnit 3)。请注意 SyntaxFactory
的 using static
,这消除了语法树构造中的许多混乱情况。
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)
};
}
}
我想你想在实际发出/编译之前添加你的诊断,所以步骤是:
- 创建您的
CSharpCompilation
,确保它没有诊断错误,然后再继续
- 创建一个
BeforeCompileContext
,并用编译和空 List<Diagnostic>
填充它
- 创建您的
ICompileModule
的实例并使用步骤 2 中的上下文调用 ICompileModule.BeforeCompile
- 检查它是否包含所需的
Diagnostic
背景
我正在使用 StackExchange.Precompilation 在 C# 中实现面向方面的编程。 See my repository on GitHub.
基本思想是客户端代码将能够在成员上放置自定义属性,预编译器将对具有这些属性的任何成员执行语法转换。一个简单的例子是我创建的 NonNullAttribute
。当 NonNullAttribute
放在参数 p
上时,预编译器将插入
if (Object.Equals(p, null)) throw new ArgumentNullException(nameof(p));
在方法体的开头。
诊断很棒...
我想让错误使用这些属性变得困难。我发现的最好方法(除了直观的设计)是为无效或不合逻辑的属性使用创建编译时 Diagnostic
s。
例如,NonNullAttribute
对值类型成员没有意义。 (即使对于可为空的值类型,因为如果你想保证它们不为空,那么应该使用不可为空的类型。)创建 Diagnostic
是通知用户此错误的好方法,不会像异常一样使构建崩溃。
...但是我该如何测试它们呢?
诊断是突出显示错误的好方法,但我也想确保我的诊断创建代码没有错误。我希望能够设置一个单元测试来预编译这样的代码示例
public class TestClass {
public void ShouldCreateDiagnostic([NonNull] int n) { }
}
并确认创建了正确的诊断(或者在某些情况下没有创建诊断)。
熟悉 StackExchange.Precompilation 的人可以给我一些指导吗?
解法:
@m0sa 给出的答案非常有帮助。实现有很多细节,所以这里是单元测试的实际样子(使用 NUnit 3)。请注意 SyntaxFactory
的 using static
,这消除了语法树构造中的许多混乱情况。
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)
};
}
}
我想你想在实际发出/编译之前添加你的诊断,所以步骤是:
- 创建您的
CSharpCompilation
,确保它没有诊断错误,然后再继续 - 创建一个
BeforeCompileContext
,并用编译和空List<Diagnostic>
填充它
- 创建您的
ICompileModule
的实例并使用步骤 2 中的上下文调用 - 检查它是否包含所需的
Diagnostic
ICompileModule.BeforeCompile