Skip to content

娴嬭瘯鍜岃皟璇曟簮鐢熸垚鍣?

瀛︿範濡備綍娴嬭瘯鍜岃皟璇?C# 婧愮敓鎴愬櫒锛岀‘淇濅唬鐮佽川閲忓拰鍙淮鎶ゆ€с€?

涓轰粈涔堥渶瑕佹祴璇曪紵

婧愮敓鎴愬櫒鏄紪璇戞椂杩愯鐨勪唬鐮侊紝閿欒浼氱洿鎺ュ奖鍝嶇敤鎴风殑缂栬瘧杩囩▼銆傚畬鍠勭殑娴嬭瘯鍙互锛?

  • **纭繚姝g‘鎬?*: 楠岃瘉鐢熸垚鐨勪唬鐮佺鍚堥鏈?
  • 闃叉鍥炲綊: 纭繚淇敼涓嶄細鐮村潖鐜版湁鍔熻兘
  • **鎻愰珮鍙淮鎶ゆ€?*: 娓呮櫚鐨勬祴璇曠敤渚嬩綔涓烘枃妗?
  • 澧炲己淇″績: 鍦ㄥ彂甯冨墠鍙戠幇闂

娴嬭瘯绛栫暐

1. 蹇収娴嬭瘯锛圫napshot Testing锛?

楠岃瘉鐢熸垚鐨勪唬鐮佸唴瀹逛笌棰勬湡瀹屽叏鍖归厤銆?

csharp
[Fact]
public async Task Generator_Produces_Expected_Code()
{
    var source = @"
[GenerateInfo]
public partial class Person
{
    public string Name { get; set; }
}";

    var expectedGenerated = @"
partial class Person
{
    public string GetInfo()
    {
        return ""Person { Name = "" + Name + "" }"";
    }
}";

    await new CSharpSourceGeneratorTest<MyGenerator, XUnitVerifier>
    {
        TestState =
        {
            Sources = { source },
            GeneratedSources = { (typeof(MyGenerator), "Person.g.cs", expectedGenerated) }
        }
    }.RunAsync();
}

2. 缂栬瘧娴嬭瘯锛圕ompilation Testing锛?

楠岃瘉鐢熸垚鐨勪唬鐮佸彲浠ユ垚鍔熺紪璇戙€?

csharp
[Fact]
public void Generated_Code_Compiles_Successfully()
{
    var compilation = CreateCompilation(source);
    var generator = new MyGenerator();
    
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    driver = driver.RunGeneratorsAndUpdateCompilation(
        compilation,
        out var outputCompilation,
        out var diagnostics);
    
    Assert.Empty(outputCompilation.GetDiagnostics()
        .Where(d => d.Severity == DiagnosticSeverity.Error));
}

3. 璇婃柇娴嬭瘯锛圖iagnostic Testing锛?

楠岃瘉鐢熸垚鍣ㄦ纭姤鍛婇敊璇拰璀﹀憡銆?

csharp
[Fact]
public async Task Generator_Reports_Error_For_Non_Partial_Class()
{
    var source = @"
[GenerateInfo]
public class {|#0:NonPartialClass|}
{
}";

    var expected = DiagnosticResult
        .CompilerError("TEST0001")
        .WithLocation(0)
        .WithArguments("NonPartialClass");

    await new CSharpSourceGeneratorTest<MyGenerator, XUnitVerifier>
    {
        TestState =
        {
            Sources = { source },
            ExpectedDiagnostics = { expected }
        }
    }.RunAsync();
}

4. 鎵嬪姩娴嬭瘯锛圡anual Testing锛?

浣跨敤 GeneratorDriver 鐩存帴杩愯鐢熸垚鍣ㄣ€?

csharp
[Fact]
public void Manual_Test_Generator()
{
    var compilation = CreateCompilation(source);
    var generator = new MyGenerator();
    
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    driver = driver.RunGeneratorsAndUpdateCompilation(
        compilation,
        out var outputCompilation,
        out var diagnostics);
    
    var runResult = driver.GetRunResult();
    Assert.NotEmpty(runResult.GeneratedTrees);
}

璋冭瘯婧愮敓鎴愬櫒

鏂规硶 1: 浣跨敤 Debugger.Launch()

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    #if DEBUG
    if (!System.Diagnostics.Debugger.IsAttached)
    {
        System.Diagnostics.Debugger.Launch();
    }
    #endif
    
    // 鐢熸垚鍣ㄩ€昏緫...
}

鏂规硶 2: 闄勫姞鍒拌繘绋?

  1. 鍦?Visual Studio 涓紝閫夋嫨 "璋冭瘯" 鈫?"闄勫姞鍒拌繘绋?
  2. 鎵惧埌 csc.exe 鎴?VBCSCompiler.exe 杩涚▼
  3. 闄勫姞璋冭瘯鍣?
  4. 鍦ㄧ敓鎴愬櫒浠g爜涓缃柇鐐?
  5. 閲嶆柊缂栬瘧浣跨敤鑰呴」鐩?

鏂规硶 3: 浣跨敤鍗曞厓娴嬭瘯璋冭瘯

鍦ㄦ祴璇曚腑璁剧疆鏂偣锛岀劧鍚庤皟璇曟祴璇曪細

csharp
[Fact]
public void Debug_Generator()
{
    var generator = new MyGenerator();
    // 鍦ㄨ繖閲岃缃柇鐐?
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    // ...
}

娴嬭瘯妗嗘灦璁剧疆

浣跨敤 Microsoft.CodeAnalysis.Testing 妗嗘灦锛?

xml
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.2" />
<PackageReference Include="xunit" Version="2.9.2" />

娴嬭瘯鏈€浣冲疄璺?

1. 娴嬭瘯瑕嗙洊鐜?

纭繚娴嬭瘯瑕嗙洊锛?

  • 鉁?姝e父鎯呭喌锛坔appy path锛?
  • 鉁?杈圭晫鎯呭喌锛坋dge cases锛?
  • 鉁?閿欒鎯呭喌锛坋rror cases锛?
  • 鉁?璇婃柇杈撳嚭
  • 鉁?鐢熸垚鐨勪唬鐮佸彲浠ョ紪璇?

2. 娴嬭瘯缁勭粐

鎸夊姛鑳界粍缁囨祴璇曪細

  • 蹇収娴嬭瘯锛氶獙璇佺敓鎴愮殑浠g爜鍐呭
  • 缂栬瘧娴嬭瘯锛氶獙璇佷唬鐮佸彲浠ョ紪璇?
  • 璇婃柇娴嬭瘯锛氶獙璇侀敊璇姤鍛?
  • 鎵嬪姩娴嬭瘯锛氱伒娲荤殑鑷畾涔夐獙璇?

3. 娴嬭瘯鍛藉悕

浣跨敤鎻忚堪鎬х殑娴嬭瘯鍚嶇О锛?

  • Generator_Produces_GetInfo_For_Class_With_Properties
  • Generator_Reports_Error_For_Non_Partial_Class
  • Generated_Code_Compiles_Successfully

4. 娴嬭瘯闅旂

姣忎釜娴嬭瘯搴旇鐙珛锛?

  • 涓嶄緷璧栧叾浠栨祴璇曠殑缁撴灉
  • 鍙互鍗曠嫭杩愯
  • 鍙互骞惰杩愯

甯歌闂

Q: 娴嬭瘯杩愯寰堟參鎬庝箞鍔烇紵

A: 浣跨敤澧為噺鐢熸垚鍣ㄥ噺灏戦噸澶嶈绠楋紝骞惰杩愯娴嬭瘯锛屽彧娴嬭瘯鍏抽敭璺緞銆?

*Q: 濡備綍娴嬭瘯鐢熸垚鐨勪唬鐮佺殑鍔熻兘锛?

A: 浣跨敤缂栬瘧娴嬭瘯纭繚浠g爜鍙互缂栬瘧锛屽垱寤洪泦鎴愭祴璇曞疄闄呰繍琛岀敓鎴愮殑浠g爜銆?

Q: 蹇収娴嬭瘯鎬绘槸澶辫触鎬庝箞鍔烇紵

A: 妫€鏌ョ敓鎴愮殑浠g爜鏍煎紡鏄惁涓€鑷达紝浣跨敤瀛楃涓叉瘮杈冨伐鍏锋煡鐪嬪樊寮傘€?

瀛﹀埌鐨勭煡璇嗙偣

  • 娴嬭瘯妗嗘灦鐨勪娇鐢?
  • GeneratorDriver API
  • 澶氱娴嬭瘯绛栫暐鐨勫簲鐢?
  • 璋冭瘯鎶€宸?
  • 娴嬭瘯鏈€浣冲疄璺?

鐩稿叧璧勬簮

瀹屾暣椤圭洰缁撴瀯

Testing/
鈹溾攢鈹€ Testing.Generator/
鈹?  鈹溾攢鈹€ Testing.Generator.csproj
鈹?  鈹溾攢鈹€ InfoGenerator.cs              # 婧愮敓鎴愬櫒瀹炵幇
鈹?  鈹斺攢鈹€ InfoAttribute.cs              # 鏍囪鐗规€?
鈹溾攢鈹€ Testing.Consumer/
鈹?  鈹溾攢鈹€ Testing.Consumer.csproj
鈹?  鈹溾攢鈹€ Program.cs                    # 浣跨敤鐢熸垚鍣ㄧ殑浠g爜
鈹?  鈹斺攢鈹€ Person.cs                     # 鏍囪鐨勭被
鈹斺攢鈹€ Testing.Tests/
    鈹溾攢鈹€ Testing.Tests.csproj
    鈹溾攢鈹€ GeneratorTests.cs             # 涓绘祴璇曠被
    鈹溾攢鈹€ SnapshotTests.cs              # 蹇収娴嬭瘯
    鈹溾攢鈹€ DiagnosticTests.cs            # 璇婃柇娴嬭瘯
    鈹溾攢鈹€ CompilationTests.cs           # 缂栬瘧娴嬭瘯
    鈹溾攢鈹€ PropertyTests.cs              # 灞炴€ф祴璇?
    鈹溾攢鈹€ IntegrationTests.cs           # 闆嗘垚娴嬭瘯
    鈹溾攢鈹€ PerformanceTests.cs           # 鎬ц兘娴嬭瘯
    鈹斺攢鈹€ TestHelpers/
        鈹溾攢鈹€ CompilationHelper.cs      # 缂栬瘧杈呭姪绫?
        鈹溾攢鈹€ GeneratorTestHelper.cs    # 鐢熸垚鍣ㄦ祴璇曡緟鍔╃被
        鈹斺攢鈹€ VerifyHelper.cs           # 楠岃瘉杈呭姪绫?

娴嬭瘯杈呭姪绫?

CompilationHelper - 缂栬瘧杈呭姪绫?

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Immutable;
using System.Reflection;

namespace Testing.Tests.TestHelpers;

/// <summary>
/// 缂栬瘧杈呭姪绫伙紝鐢ㄤ簬鍒涘缓娴嬭瘯鐢ㄧ殑 Compilation 瀵硅薄
/// </summary>
public static class CompilationHelper
{
    /// <summary>
    /// 鍒涘缓鍩虹鐨?CSharpCompilation
    /// </summary>
    public static CSharpCompilation CreateCompilation(string source)
    {
        var syntaxTree = CSharpSyntaxTree.ParseText(source);
        
        // 娣诲姞蹇呰鐨勫紩鐢?
        var references = new[]
        {
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
            MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
            MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location),
            MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location),
        };
        
        return CSharpCompilation.Create(
            assemblyName: "TestAssembly",
            syntaxTrees: new[] { syntaxTree },
            references: references,
            options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
    }
    
    /// <summary>
    /// 鍒涘缓鍖呭惈澶氫釜婧愭枃浠剁殑 Compilation
    /// </summary>
    public static CSharpCompilation CreateCompilation(params string[] sources)
    {
        var syntaxTrees = sources.Select(s => CSharpSyntaxTree.ParseText(s)).ToArray();
        
        var references = new[]
        {
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
            MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
            MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location),
            MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location),
        };
        
        return CSharpCompilation.Create(
            assemblyName: "TestAssembly",
            syntaxTrees: syntaxTrees,
            references: references,
            options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
    }
    
    /// <summary>
    /// 鑾峰彇缂栬瘧璇婃柇淇℃伅
    /// </summary>
    public static ImmutableArray<Diagnostic> GetDiagnostics(Compilation compilation)
    {
        return compilation.GetDiagnostics();
    }
    
    /// <summary>
    /// 妫€鏌ユ槸鍚︽湁缂栬瘧閿欒
    /// </summary>
    public static bool HasErrors(Compilation compilation)
    {
        return compilation.GetDiagnostics()
            .Any(d => d.Severity == DiagnosticSeverity.Error);
    }
    
    /// <summary>
    /// 鑾峰彇閿欒娑堟伅鍒楄〃
    /// </summary>
    public static List<string> GetErrorMessages(Compilation compilation)
    {
        return compilation.GetDiagnostics()
            .Where(d => d.Severity == DiagnosticSeverity.Error)
            .Select(d => d.GetMessage())
            .ToList();
    }
}

GeneratorTestHelper - 鐢熸垚鍣ㄦ祴璇曡緟鍔╃被

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Testing.Tests.TestHelpers;

/// <summary>
/// 鐢熸垚鍣ㄦ祴璇曡緟鍔╃被锛岀畝鍖栫敓鎴愬櫒娴嬭瘯浠g爜
/// </summary>
public static class GeneratorTestHelper
{
    /// <summary>
    /// 杩愯鐢熸垚鍣ㄥ苟杩斿洖缁撴灉
    /// </summary>
    public static GeneratorDriverRunResult RunGenerator<TGenerator>(
        string source,
        params MetadataReference[] additionalReferences)
        where TGenerator : IIncrementalGenerator, new()
    {
        var compilation = CompilationHelper.CreateCompilation(source);
        
        // 濡傛灉鏈夐澶栫殑寮曠敤锛屾坊鍔犲畠浠?
        if (additionalReferences.Length > 0)
        {
            compilation = compilation.AddReferences(additionalReferences);
        }
        
        var generator = new TGenerator();
        GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
        
        driver = driver.RunGeneratorsAndUpdateCompilation(
            compilation,
            out var outputCompilation,
            out var diagnostics);
        
        return driver.GetRunResult();
    }
    
    /// <summary>
    /// 杩愯鐢熸垚鍣ㄥ苟鑾峰彇鐢熸垚鐨勬簮浠g爜
    /// </summary>
    public static string[] GetGeneratedSources<TGenerator>(string source)
        where TGenerator : IIncrementalGenerator, new()
    {
        var result = RunGenerator<TGenerator>(source);
        
        return result.GeneratedTrees
            .Select(tree => tree.ToString())
            .ToArray();
    }
    
    /// <summary>
    /// 杩愯鐢熸垚鍣ㄥ苟鑾峰彇璇婃柇淇℃伅
    /// </summary>
    public static ImmutableArray<Diagnostic> GetGeneratorDiagnostics<TGenerator>(string source)
        where TGenerator : IIncrementalGenerator, new()
    {
        var result = RunGenerator<TGenerator>(source);
        return result.Diagnostics;
    }
    
    /// <summary>
    /// 楠岃瘉鐢熸垚鍣ㄦ槸鍚︾敓鎴愪簡鎸囧畾鏁伴噺鐨勬枃浠?
    /// </summary>
    public static bool VerifyGeneratedFileCount<TGenerator>(string source, int expectedCount)
        where TGenerator : IIncrementalGenerator, new()
    {
        var result = RunGenerator<TGenerator>(source);
        return result.GeneratedTrees.Length == expectedCount;
    }
    
    /// <summary>
    /// 楠岃瘉鐢熸垚鐨勪唬鐮佹槸鍚﹀寘鍚寚瀹氭枃鏈?
    /// </summary>
    public static bool VerifyGeneratedCodeContains<TGenerator>(
        string source,
        string expectedText)
        where TGenerator : IIncrementalGenerator, new()
    {
        var sources = GetGeneratedSources<TGenerator>(source);
        return sources.Any(s => s.Contains(expectedText));
    }
}

VerifyHelper - 楠岃瘉杈呭姪绫?

csharp
using Microsoft.CodeAnalysis;
using System.Text.RegularExpressions;

namespace Testing.Tests.TestHelpers;

/// <summary>
/// 楠岃瘉杈呭姪绫伙紝鎻愪緵鍚勭楠岃瘉鏂规硶
/// </summary>
public static class VerifyHelper
{
    /// <summary>
    /// 楠岃瘉鐢熸垚鐨勪唬鐮佹牸寮忔槸鍚︽纭?
    /// </summary>
    public static bool VerifyCodeFormat(string code)
    {
        // 妫€鏌ユ槸鍚︽湁姝g‘鐨勭缉杩?
        var lines = code.Split('\n');
        foreach (var line in lines)
        {
            if (line.TrimStart().StartsWith("//"))
                continue;
                
            // 妫€鏌ョ缉杩涙槸鍚︽槸 4 鐨勫€嶆暟
            var leadingSpaces = line.Length - line.TrimStart().Length;
            if (leadingSpaces % 4 != 0)
                return false;
        }
        
        return true;
    }
    
    /// <summary>
    /// 楠岃瘉鐢熸垚鐨勪唬鐮佹槸鍚﹀寘鍚繀瑕佺殑鍛藉悕绌洪棿
    /// </summary>
    public static bool VerifyNamespaces(string code, params string[] requiredNamespaces)
    {
        foreach (var ns in requiredNamespaces)
        {
            if (!code.Contains($"using {ns};"))
                return false;
        }
        return true;
    }
    
    /// <summary>
    /// 楠岃瘉鐢熸垚鐨勪唬鐮佹槸鍚﹀寘鍚寚瀹氱殑鏂规硶
    /// </summary>
    public static bool VerifyMethodExists(string code, string methodName)
    {
        var pattern = $@"\b{Regex.Escape(methodName)}\s*\(";
        return Regex.IsMatch(code, pattern);
    }
    
    /// <summary>
    /// 楠岃瘉鐢熸垚鐨勪唬鐮佹槸鍚﹀寘鍚寚瀹氱殑绫?
    /// </summary>
    public static bool VerifyClassExists(string code, string className)
    {
        var pattern = $@"\bclass\s+{Regex.Escape(className)}\b";
        return Regex.IsMatch(code, pattern);
    }
    
    /// <summary>
    /// 楠岃瘉璇婃柇淇℃伅鐨勪弗閲嶇骇鍒?
    /// </summary>
    public static bool VerifyDiagnosticSeverity(
        Diagnostic diagnostic,
        DiagnosticSeverity expectedSeverity)
    {
        return diagnostic.Severity == expectedSeverity;
    }
    
    /// <summary>
    /// 楠岃瘉璇婃柇淇℃伅鐨?ID
    /// </summary>
    public static bool VerifyDiagnosticId(
        Diagnostic diagnostic,
        string expectedId)
    {
        return diagnostic.Id == expectedId;
    }
    
    /// <summary>
    /// 姣旇緝涓や釜浠g爜瀛楃涓诧紙蹇界暐绌虹櫧宸紓锛?
    /// </summary>
    public static bool CompareCode(string actual, string expected)
    {
        var normalizedActual = NormalizeWhitespace(actual);
        var normalizedExpected = NormalizeWhitespace(expected);
        return normalizedActual == normalizedExpected;
    }
    
    private static string NormalizeWhitespace(string code)
    {
        // 绉婚櫎鎵€鏈夌┖鐧藉瓧绗︼紝鍙繚鐣欏崟涓┖鏍?
        return Regex.Replace(code.Trim(), @"\s+", " ");
    }
}

鍗曞厓娴嬭瘯绀轰緥

鍩虹鐢熸垚鍣ㄦ祴璇?

csharp
using Xunit;
using Testing.Generator;
using Testing.Tests.TestHelpers;

namespace Testing.Tests;

public class GeneratorTests
{
    [Fact]
    public void Generator_Generates_GetInfo_Method()
    {
        // Arrange
        var source = @"
using Testing.Generator;

namespace TestNamespace
{
    [GenerateInfo]
    public partial class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}";

        // Act
        var generatedSources = GeneratorTestHelper
            .GetGeneratedSources<InfoGenerator>(source);

        // Assert
        Assert.NotEmpty(generatedSources);
        Assert.Contains("GetInfo", generatedSources[0]);
        Assert.Contains("public string GetInfo()", generatedSources[0]);
    }
    
    [Fact]
    public void Generator_Includes_All_Properties()
    {
        // Arrange
        var source = @"
using Testing.Generator;

[GenerateInfo]
public partial class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int Stock { get; set; }
}";

        // Act
        var generatedSources = GeneratorTestHelper
            .GetGeneratedSources<InfoGenerator>(source);
        var generatedCode = generatedSources[0];

        // Assert
        Assert.Contains("Name", generatedCode);
        Assert.Contains("Price", generatedCode);
        Assert.Contains("Stock", generatedCode);
    }
    
    [Fact]
    public void Generator_Handles_Multiple_Classes()
    {
        // Arrange
        var source = @"
using Testing.Generator;

[GenerateInfo]
public partial class Person
{
    public string Name { get; set; }
}

[GenerateInfo]
public partial class Product
{
    public string Title { get; set; }
}";

        // Act
        var result = GeneratorTestHelper.RunGenerator<InfoGenerator>(source);

        // Assert
        Assert.Equal(2, result.GeneratedTrees.Length);
    }
    
    [Fact]
    public void Generator_Preserves_Namespace()
    {
        // Arrange
        var source = @"
using Testing.Generator;

namespace MyCompany.MyProject
{
    [GenerateInfo]
    public partial class Employee
    {
        public string Name { get; set; }
    }
}";

        // Act
        var generatedSources = GeneratorTestHelper
            .GetGeneratedSources<InfoGenerator>(source);

        // Assert
        Assert.Contains("namespace MyCompany.MyProject", generatedSources[0]);
    }
}

灞炴€ф祴璇?

灞炴€ф祴璇曪紙Property-Based Testing锛夐獙璇佺敓鎴愬櫒鍦ㄥ悇绉嶈緭鍏ヤ笅鐨勮涓虹壒鎬с€?

csharp
using Xunit;
using FsCheck;
using FsCheck.Xunit;

namespace Testing.Tests;

public class PropertyTests
{
    /// <summary>
    /// 灞炴€э細鐢熸垚鍣ㄦ€绘槸涓烘爣璁扮殑绫荤敓鎴愪唬鐮?
    /// </summary>
    [Property]
    public Property Generator_Always_Generates_Code_For_Marked_Classes()
    {
        return Prop.ForAll<string, string>((className, propertyName) =>
        {
            // 纭繚鍚嶇О鏈夋晥
            if (string.IsNullOrWhiteSpace(className) || 
                string.IsNullOrWhiteSpace(propertyName))
                return true;
            
            var source = $@"
using Testing.Generator;

[GenerateInfo]
public partial class {className}
{{
    public string {propertyName} {{ get; set; }}
}}";

            try
            {
                var result = GeneratorTestHelper
                    .RunGenerator<InfoGenerator>(source);
                return result.GeneratedTrees.Length > 0;
            }
            catch
            {
                // 濡傛灉婧愪唬鐮佹棤鏁堬紝璺宠繃姝ゆ祴璇?
                return true;
            }
        });
    }
    
    /// <summary>
    /// 灞炴€э細鐢熸垚鐨勪唬鐮佹€绘槸鍙互缂栬瘧
    /// </summary>
    [Property]
    public Property Generated_Code_Always_Compiles()
    {
        return Prop.ForAll<NonEmptyString>(className =>
        {
            var source = $@"
using Testing.Generator;

[GenerateInfo]
public partial class {className.Get}
{{
    public string Name {{ get; set; }}
}}";

            try
            {
                var compilation = CompilationHelper.CreateCompilation(source);
                var generator = new InfoGenerator();
                
                GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
                driver = driver.RunGeneratorsAndUpdateCompilation(
                    compilation,
                    out var outputCompilation,
                    out _);
                
                return !CompilationHelper.HasErrors(outputCompilation);
            }
            catch
            {
                return true;
            }
        });
    }
}

闆嗘垚娴嬭瘯

闆嗘垚娴嬭瘯楠岃瘉鐢熸垚鍣ㄥ湪鐪熷疄椤圭洰涓殑琛屼负銆?

csharp
using Xunit;
using System.Reflection;

namespace Testing.Tests;

public class IntegrationTests
{
    [Fact]
    public void Generated_Code_Can_Be_Executed()
    {
        // Arrange
        var source = @"
using Testing.Generator;

[GenerateInfo]
public partial class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}";

        var compilation = CompilationHelper.CreateCompilation(source);
        var generator = new InfoGenerator();
        
        GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
        driver = driver.RunGeneratorsAndUpdateCompilation(
            compilation,
            out var outputCompilation,
            out _);
        
        // Act - 缂栬瘧骞跺姞杞界▼搴忛泦
        using var ms = new MemoryStream();
        var emitResult = outputCompilation.Emit(ms);
        
        Assert.True(emitResult.Success);
        
        ms.Seek(0, SeekOrigin.Begin);
        var assembly = Assembly.Load(ms.ToArray());
        
        // 鍒涘缓瀹炰緥骞惰皟鐢ㄧ敓鎴愮殑鏂规硶
        var personType = assembly.GetType("Person");
        var person = Activator.CreateInstance(personType);
        
        personType.GetProperty("Name").SetValue(person, "Alice");
        personType.GetProperty("Age").SetValue(person, 30);
        
        var getInfoMethod = personType.GetMethod("GetInfo");
        var result = getInfoMethod.Invoke(person, null) as string;
        
        // Assert
        Assert.NotNull(result);
        Assert.Contains("Alice", result);
        Assert.Contains("30", result);
    }
    
    [Fact]
    public void Generator_Works_With_Multiple_Assemblies()
    {
        // 娴嬭瘯鐢熸垚鍣ㄥ湪澶氱▼搴忛泦鍦烘櫙涓嬬殑琛屼负
        var source1 = @"
using Testing.Generator;

namespace Assembly1
{
    [GenerateInfo]
    public partial class ClassA
    {
        public string PropertyA { get; set; }
    }
}";

        var source2 = @"
using Testing.Generator;

namespace Assembly2
{
    [GenerateInfo]
    public partial class ClassB
    {
        public string PropertyB { get; set; }
    }
}";

        var compilation1 = CompilationHelper.CreateCompilation(source1);
        var compilation2 = CompilationHelper.CreateCompilation(source2);
        
        var generator = new InfoGenerator();
        
        var driver1 = CSharpGeneratorDriver.Create(generator);
        driver1.RunGeneratorsAndUpdateCompilation(
            compilation1,
            out var output1,
            out _);
        
        var driver2 = CSharpGeneratorDriver.Create(generator);
        driver2.RunGeneratorsAndUpdateCompilation(
            compilation2,
            out var output2,
            out _);
        
        Assert.False(CompilationHelper.HasErrors(output1));
        Assert.False(CompilationHelper.HasErrors(output2));
    }
}

鎬ц兘娴嬭瘯

鎬ц兘娴嬭瘯纭繚鐢熸垚鍣ㄥ湪澶у瀷椤圭洰涓殑鎬ц兘琛ㄧ幇銆?

csharp
using Xunit;
using System.Diagnostics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace Testing.Tests;

public class PerformanceTests
{
    [Fact]
    public void Generator_Completes_Within_Time_Limit()
    {
        // Arrange
        var source = GenerateLargeSource(100); // 100 涓被
        var stopwatch = Stopwatch.StartNew();
        
        // Act
        var result = GeneratorTestHelper.RunGenerator<InfoGenerator>(source);
        stopwatch.Stop();
        
        // Assert
        Assert.True(stopwatch.ElapsedMilliseconds < 5000, 
            $"Generator took {stopwatch.ElapsedMilliseconds}ms, expected < 5000ms");
    }
    
    [Fact]
    public void Incremental_Generator_Caches_Results()
    {
        // Arrange
        var source = @"
[GenerateInfo]
public partial class Person
{
    public string Name { get; set; }
}";

        var compilation = CompilationHelper.CreateCompilation(source);
        var generator = new InfoGenerator();
        
        GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
        
        // 绗竴娆¤繍琛?
        var sw1 = Stopwatch.StartNew();
        driver = driver.RunGenerators(compilation);
        sw1.Stop();
        
        // 绗簩娆¤繍琛岋紙搴旇浣跨敤缂撳瓨锛?
        var sw2 = Stopwatch.StartNew();
        driver = driver.RunGenerators(compilation);
        sw2.Stop();
        
        // Assert - 绗簩娆″簲璇ユ洿蹇?
        Assert.True(sw2.ElapsedMilliseconds <= sw1.ElapsedMilliseconds,
            $"Second run ({sw2.ElapsedMilliseconds}ms) should be faster than first ({sw1.ElapsedMilliseconds}ms)");
    }
    
    private static string GenerateLargeSource(int classCount)
    {
        var sb = new StringBuilder();
        sb.AppendLine("using Testing.Generator;");
        
        for (int i = 0; i < classCount; i++)
        {
            sb.AppendLine($@"
[GenerateInfo]
public partial class Class{i}
{{
    public string Property1 {{ get; set; }}
    public string Property2 {{ get; set; }}
    public int Property3 {{ get; set; }}
}}");
        }
        
        return sb.ToString();
    }
}

/// <summary>
/// 浣跨敤 BenchmarkDotNet 杩涜璇︾粏鐨勬€ц兘娴嬭瘯
/// </summary>
[MemoryDiagnoser]
public class GeneratorBenchmarks
{
    private string _smallSource;
    private string _mediumSource;
    private string _largeSource;
    
    [GlobalSetup]
    public void Setup()
    {
        _smallSource = GenerateSource(10);
        _mediumSource = GenerateSource(100);
        _largeSource = GenerateSource(1000);
    }
    
    [Benchmark]
    public void SmallProject_10Classes()
    {
        GeneratorTestHelper.RunGenerator<InfoGenerator>(_smallSource);
    }
    
    [Benchmark]
    public void MediumProject_100Classes()
    {
        GeneratorTestHelper.RunGenerator<InfoGenerator>(_mediumSource);
    }
    
    [Benchmark]
    public void LargeProject_1000Classes()
    {
        GeneratorTestHelper.RunGenerator<InfoGenerator>(_largeSource);
    }
    
    private string GenerateSource(int count)
    {
        var sb = new StringBuilder();
        sb.AppendLine("using Testing.Generator;");
        
        for (int i = 0; i < count; i++)
        {
            sb.AppendLine($@"
[GenerateInfo]
public partial class TestClass{i}
{{
    public string Name {{ get; set; }}
}}");
        }
        
        return sb.ToString();
    }
}

基于 MIT 许可发布