Source Generator 单元测试

Intro

Source Generator 是 .NET 5.0 以后引入的一个在编译期间动态生成代码的一个机制,介绍可以参考 C# 强大的新特性 Source Generator

GetStarted

使用起来还算比较简单的,我平时一般用 xunit,所以下面的示例也是使用 xunit 来写单元测试,微软提供的测试组件也有针对 MsTest 和 NUnit 的,可以根据自己需要进行选择 https://www.nuget.org/packages?q=Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing

我的项目是 xunit , 所以首先需要在测试项目中引用 Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit 这个 NuGet 包,如果不是 xunit 选择对应的 NuGet 包即可

如果在还原包的时候有包版本的警告可以显式指定对应包的版本来消除警告

Sample1

首先来看一个最简单的 Source Generator 示例

[Generator]
public class HelloGenerator : ISourceGenerator
{public void Initialize(GeneratorInitializationContext context){// for debugging// if (!Debugger.IsAttached) Debugger.Launch();}public void Execute(GeneratorExecutionContext context){var code = @"namespace HelloGenerated
{public class HelloGenerator{public static void Test() => System.Console.WriteLine(""Hello Generator"");}
}";context.AddSource(nameof(HelloGenerator), code);}
}

这个 Source Generator 就是一个比较简单的生成一个 HelloGenerator 的类,这个类里只有一个 Test 的静态方法,单元测试方法如下:

[Fact]
public async Task HelloGeneratorTest()
{var code = string.Empty;var generatedCode = @"namespace HelloGenerated
{public class HelloGenerator{public static void Test() => System.Console.WriteLine(""Hello Generator"");}
}";var tester = new CSharpSourceGeneratorTest<HelloGenerator, XUnitVerifier>(){TestState ={Sources = { code },GeneratedSources ={(typeof(HelloGenerator), $"{nameof(HelloGenerator)}.cs", SourceText.From(generatedCode, Encoding.UTF8)),}},};await tester.RunAsync();
}

通常来说 Source Generator 的测试分为两部分,一部分是源代码,一部分 Generator 生成的代码

而这个示例比较简单,其实和源代码没有关系,可以没有源代码,上面是给了一个空,也可以不配置 Sources

而 Generated Sources 则是由我们的 Generator 生成的代码

首先我们需要创建一个 CSharpSourceGeneratorTest 有两个泛型类型,第一个是 Generator 类型,第二个是验证器,这和你使用哪个测试框架有关系,xunit 就固定是 XUnitVerifier ,在 test 中指定 TestState 中的源代码和生成的源代码,之后调用 RunAsync 方法就可以了

上面有一个生成的示例,

第一个参数是 Generator 的类型,会根据 Generator 的类型获取生成代码的位置,

第二个参数是在 Generator 里 AddSource 时指定的名称,但是这里需要注意的是,即使指定的名称不是 .cs 结尾的需要也需要在这里添加 .cs 后缀,这个地方感觉可以优化一下,自动加 .cs 后缀

第三个参数就是实际生成的代码了

Sample2

接着我们来看一个稍微复杂一些的,和源代码有关系并且有依赖项

Generator 定义如下:

[Generator]
public class ModelGenerator : ISourceGenerator
{public void Initialize(GeneratorInitializationContext context){// Debugger.Launch();context.RegisterForSyntaxNotifications(() => new CustomSyntaxReceiver());}public void Execute(GeneratorExecutionContext context){var codeBuilder = new StringBuilder(@"
using System;
using WeihanLi.Extensions;namespace Generated
{public class ModelGenerator{public static void Test(){Console.WriteLine(""-- ModelGenerator --"");
");if (context.SyntaxReceiver is CustomSyntaxReceiver syntaxReceiver){foreach (var model in syntaxReceiver.Models){codeBuilder.AppendLine($@"      ""{model.Identifier.ValueText} Generated"".Dump();");}}codeBuilder.AppendLine("    }");codeBuilder.AppendLine("  }");codeBuilder.AppendLine("}");var code = codeBuilder.ToString();context.AddSource(nameof(ModelGenerator), code);}
}internal class CustomSyntaxReceiver : ISyntaxReceiver
{public List<ClassDeclarationSyntax> Models { get; } = new();public void OnVisitSyntaxNode(SyntaxNode syntaxNode){if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax){Models.Add(classDeclarationSyntax);}}
}

单元测试方法如下:

[Fact]public async Task ModelGeneratorTest(){var code = @"
public class TestModel123{}
";var generatedCode = @"
using System;
using WeihanLi.Extensions;namespace Generated
{public class ModelGenerator{public static void Test(){Console.WriteLine(""-- ModelGenerator --"");""TestModel123 Generated"".Dump();}}
}
";var tester = new CSharpSourceGeneratorTest<ModelGenerator, XUnitVerifier>(){TestState ={Sources = { code },GeneratedSources ={(typeof(ModelGenerator), $"{nameof(ModelGenerator)}.cs", SourceText.From(generatedCode, Encoding.UTF8)),}},};// references// TestState.AdditionalReferencestester.TestState.AdditionalReferences.Add(typeof(DependencyResolver).Assembly);// ReferenceAssemblies//    WithAssemblies//tester.ReferenceAssemblies = tester.ReferenceAssemblies//    .WithAssemblies(ImmutableArray.Create(new[] { typeof(DependencyResolver).Assembly.Location.Replace(".dll", "", System.StringComparison.OrdinalIgnoreCase) }))//    ;//    WithPackages//tester.ReferenceAssemblies = tester.ReferenceAssemblies//    .WithPackages(ImmutableArray.Create(new PackageIdentity[] { new PackageIdentity("WeihanLi.Common", "1.0.46") }))//    ;await tester.RunAsync();}

大体上和前面的示例差不多,比较大的差异在于,这里需要处理依赖项,上面代码中提供的三种处理方式,其中 WithPackages 方式只支持 NuGet 包方式,如果是直接引用的 dll 可以使用前面两种方式来实现

More

在之前的介绍文章中我们推荐在代码里添加一句 Debugger.Launch() 来调试 Source Generator,而有了单元测试之后,我们就可以不需要这个了,debug 我们的测试用例也可以调试我们的 Generator,很多时候就会比较方便,也不需要编译的时候触发选择 Debugger 了会更加高效一些,代码里可以少一些神奇的 Debugger.Launch() 了,更加推荐使用单元测试的方式来测试 Generator

上面的第二个示例依赖项的处理,踩了好多坑,自己试了好多次都不行,Google/StackOverflow 大法好

除了上面的 WithXxx 方式,我们还可以用 AddXxx 方式,Add 是增量的方式,而 With 是完全的替换掉对应的依赖

如果你的项目里也有用到 Source Generator,不妨试一下,上面示例的代码可以从 Github 上获取:https://github.com/WeihanLi/SamplesInPractice/blob/master/SourceGeneratorSample/SourceGeneratorTest/GeneratorTest.cs

References

  • https://stackoverflow.com/questions/65550409/adding-reference-assemblies-to-roslyn-analyzer-code-fix-unit-tests

  • https://www.thinktecture.com/en/net/roslyn-source-generators-analyzers-code-fixes-testing/

  • https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md#unit-testing-of-generators

  • https://github.com/dotnet/roslyn-sdk/tree/main/src/Microsoft.CodeAnalysis.Testing

  • https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/SourceGeneratorSample/SourceGeneratorTest/GeneratorTest.cs

  • C# 强大的新特性 Source Generator

  • 使用 Source Generator 代替 T4 动态生成代码

  • 使用 Source Generator 自动生成 WEB API

Source Generator 单元测试相关推荐

  1. .NET 6 中的 Logging Source Generator

    .NET 6 中的 Logging Source Generator Intro Logging source generator 是 .NET 6 引入的一个新功能,借助 Source Genera ...

  2. 使用 Source Generator 自动生成 WEB API

    使用 Source Generator 自动生成 WEB API Intro 上次我们介绍了使用 Source Generator 的应用,有小伙伴留言说想要自动生成一套 ABP 相关的东西,我对 A ...

  3. 使用 Source Generator 代替 T4 动态生成代码

    使用 Source Generator 代替 T4 动态生成代码 Intro 在 Source Generator 出现之前有一些重复性的代码,我会使用 T4 去生成,这样就可以一定程度上避免复制粘贴 ...

  4. C# 强大的新特性 Source Generator

    C# 强大的新特性 Source Generator Intro 微软在 .NET 5 中引入了 Source Generator 的新特性,利用 Source Generator 我们可以在应用编译 ...

  5. Source Generator:C# 9 将迎来编译时元编程

    源码生成器(Source Generator)是 C#编译器的一个新特性,开发者可以使用编译器生成的元数据检查用户代码,并生成附加的源文件,与程序的其他部分一起编译. 受 F#类型提供程序的启发,C# ...

  6. javaweb项目Error:Android Source Generator: [example] Android SDK is not specified

    javaweb项目混入android@java javaweb项目Error:Android Source Generator: [example] Android SDK is not specif ...

  7. Error:Android Source Generator: [sdk] Android SDK is not specified.

    有时候使用intellij idea 带入android 项目,运行提示Error:Android Source Generator: [sdk] Android SDK is not specifi ...

  8. ERROR: Android Source Generator: [project] AndroidManifest.xml file not found

    you must open Project Structure modified something. example: Project Structure > Facets ,you can ...

  9. 在 C# 中生成代码的四种方式——包括.NET 5中的Source Generators

    Microsoft在最新的C#版本中引入了Source Generator.这是一项新功能,可以让我们在代码编译时生成源代码.在本文中,我将介绍四种C#中的代码生成方式,以简化我们的日常工作.然后,您 ...

最新文章

  1. 设计模式--访问器(Visitor)模式
  2. underscore.js 页面数据渲染
  3. Lync Server的环境搭建(五):Lync-Server的安装部署
  4. git 命令 clone分支的代码
  5. 10大iOS开发者最喜爱的类库
  6. 网页素材精品:一组五彩缤纷的免费矢量背景素材
  7. 偏微分方程的数值解(二): 一维状态空间的偏微分方程的 MATLAB 解法
  8. 数据结构与算法(总结)
  9. Biaofun讲解短视频营销对于2021年的大势所趋
  10. 关于excel 打开时提示 文件正在使用 正处于锁定状态 正在编辑 的不治本但简单可行的解决办法
  11. C#中Validating和Validated事件
  12. 红米K40重启的解决方案
  13. 基于STM32的智能快递箱(快递驿站)设计
  14. android rfid开发实例,Android NFC读卡 高频卡 RFID
  15. 一、自定义一个竖直Layout
  16. Python,pandas中DataFrame的选取总结
  17. ubuntu下如何设置PageUp/PageDown键调出使用过的历史命令
  18. 请求后台时对uri进行编码——即encodeURIComponent()的使用
  19. 【CF 比赛记录】Roye_ack的艰难上分日常(35)
  20. .NET基础篇:解决VS2017引用无效问题。

热门文章

  1. OCS2007R2升级LyncSrv2013 PART4:关联边缘
  2. C++语言基本概念(5)
  3. 2010年5月系统集成项目管理工程师上午试卷参考答案(讨论版)
  4. 批处理文件总结(三)
  5. linux隐写文件剥离,杂项的基本解题思路(1)——文件操作隐写、图片隐写
  6. omnipay支付--支付宝支付
  7. 业务id转密文短链的一种实现思路
  8. 整理ASP.NET MVC 5各种错误请求[401,403,404,500]的拦截及自定义页面处理实例
  9. python:软件目录结构规范
  10. 编写高质量代码:改善Java的151个建议四(基本类型)21-30