You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试依赖GeneratorExecutionContext的Source Generator扩展方法?

如何为Source Generator扩展方法编写测试(无法实例化GeneratorExecutionContext)

我正在编写一个Source Generator,需要读取.csproj文件。通过GeneratorExecutionContext.AnalyzerConfigOptions.GlobalOptions中的build_property.ProjectDir获取文件路径,并写了两个扩展方法:

public static class GeneratorInitializationContextExtensions
{
    public static string GetProjectDirectory(this GeneratorExecutionContext context)
    {
        context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.ProjectDir", out string? projectDirectory);
        if (projectDirectory is null) throw new("Can't find project directory");
        return projectDirectory;
    }

    public static string GetProjectFile(this GeneratorExecutionContext context)
    {
        string   projectDirectory = context.GetProjectDirectory();
        string[] files            = Directory.GetFiles(projectDirectory);
        string   projectFile      = files.Single(f => f.EndsWith(".csproj"));
        return projectFile;
    }
}

这些方法运行正常,但我想为它们添加测试覆盖。遗憾的是,我无法创建用于测试的GeneratorExecutionContext实例——它的唯一构造函数是内部的,且AnalyzerConfigOptionsProvider属性没有setter。请问该如何解决此问题?


解决方案

1. 拆分核心逻辑,解耦对GeneratorExecutionContext的依赖

最直接的方式是把业务逻辑从扩展方法中抽离,让核心功能不依赖GeneratorExecutionContext,这样就能单独测试:

public static class GeneratorInitializationContextExtensions
{
    public static string GetProjectDirectory(this GeneratorExecutionContext context)
    {
        return GetProjectDirectoryFromGlobalOptions(context.AnalyzerConfigOptions.GlobalOptions);
    }

    public static string GetProjectFile(this GeneratorExecutionContext context)
    {
        string projectDirectory = context.GetProjectDirectory();
        return FindProjectFileInDirectory(projectDirectory);
    }

    // 抽离出可直接测试的核心方法
    internal static string GetProjectDirectoryFromGlobalOptions(IReadOnlyDictionary<string, string> globalOptions)
    {
        globalOptions.TryGetValue("build_property.ProjectDir", out string? projectDirectory);
        if (projectDirectory is null) throw new("Can't find project directory");
        return projectDirectory;
    }

    internal static string FindProjectFileInDirectory(string projectDirectory)
    {
        string[] files = Directory.GetFiles(projectDirectory);
        string projectFile = files.Single(f => f.EndsWith(".csproj"));
        return projectFile;
    }
}

测试时直接调用GetProjectDirectoryFromGlobalOptions和FindProjectFileInDirectory:

  • 给前者传入模拟的键值对字典,验证路径提取逻辑
  • 给后者传入预先创建好测试文件的目录,验证项目文件查找逻辑

完全不需要依赖GeneratorExecutionContext实例。

2. 使用Moq模拟上下文依赖

如果一定要测试完整的扩展方法,可以用Moq模拟GeneratorExecutionContext的AnalyzerConfigOptions属性:

先安装Moq包,然后编写测试代码:

[TestClass]
public class GeneratorInitializationContextExtensionsTests
{
    [TestMethod]
    public void GetProjectDirectory_ValidOptions_ReturnsCorrectPath()
    {
        // 模拟GlobalOptions字典
        var mockGlobalOptions = new Dictionary<string, string>
        {
            {"build_property.ProjectDir", "C:\\TestProject\\"}
        };

        // 模拟AnalyzerConfigOptionsProvider
        var mockConfigProvider = new Mock<AnalyzerConfigOptionsProvider>();
        mockConfigProvider.Setup(p => p.GlobalOptions).Returns(mockGlobalOptions);

        // 模拟GeneratorExecutionContext实例
        var mockContext = Mock.Of<GeneratorExecutionContext>(c => 
            c.AnalyzerConfigOptions == mockConfigProvider.Object);

        // 调用扩展方法并断言结果
        string result = mockContext.GetProjectDirectory();
        Assert.AreEqual("C:\\TestProject\\", result);
    }
}

注意:部分Roslyn版本可能对GeneratorExecutionContext的模拟有限制,如果此方法失效,优先使用第一种拆分逻辑的方案。

3. 使用Roslyn测试工具包模拟完整生成环境

如果需要测试完整的Source Generator流程,可以使用Microsoft.CodeAnalysis.Testing包,它能模拟真实的生成上下文:

[TestClass]
public class GeneratorIntegrationTests
{
    [TestMethod]
    public async Task GetProjectFile_ReturnsCorrectCsprojPath()
    {
        // 创建测试项目,指定目录和.csproj文件
        var testProject = new TestProject
        {
            ProjectDir = "C:\\TestProject\\",
            Files = { ["TestProject.csproj"] = "<Project Sdk=\"Microsoft.NET.Sdk\"></Project>" }
        };

        // 配置生成器测试
        var test = new SourceGeneratorTest<YourGenerator, DefaultVerifier>
        {
            TestProject = testProject
        };

        // 运行测试,生成器内可正常调用你的扩展方法
        await test.RunAsync();

        // 这里可以断言生成器的输出结果,或验证扩展方法的执行逻辑
    }
}

这种方法适合端到端测试生成器整体流程,但如果只是测试扩展方法的逻辑,第一种拆分方案更轻量高效。


内容的提问来源于stack exchange,提问作者ScottishTapWater

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 10:43:37