Skip to content
style I fill:#d4edda
style J fill:#f8d7da

## 璋冭瘯鎶€宸ц瑙?

### 1. 浣跨敤 Debugger.Launch() 璋冭瘯

杩欐槸鏈€鐩存帴鐨勮皟璇曟柟娉曪紝浼氬湪鐢熸垚鍣ㄦ墽琛屾椂寮瑰嚭璋冭瘯鍣ㄩ€夋嫨瀵硅瘽妗嗐€?

```csharp
[Generator]
public class InfoGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        #if DEBUG
        // 浠呭湪 DEBUG 妯″紡涓嬪惎鐢?
        if (!System.Diagnostics.Debugger.IsAttached)
        {
            // 寮瑰嚭璋冭瘯鍣ㄩ€夋嫨瀵硅瘽妗?
            System.Diagnostics.Debugger.Launch();
        }
        #endif
        
        // 鐢熸垚鍣ㄩ€昏緫...
        var classDeclarations = context.SyntaxProvider
            .CreateSyntaxProvider(/* ... */);
    }
}

浼樼偣锛?

  • 鉁?绠€鍗曠洿鎺?
  • 鉁?鍙互鍦ㄧ敓鎴愬櫒鍒濆鍖栨椂灏卞紑濮嬭皟璇?
  • 鉁?閫傚悎璋冭瘯澶嶆潅鐨勭敓鎴愰€昏緫

缂虹偣锛?

  • 鉂?姣忔缂栬瘧閮戒細寮瑰嚭瀵硅瘽妗?
  • 鉂?闇€瑕佹墜鍔ㄩ€夋嫨璋冭瘯鍣ㄥ疄渚?
  • 鉂?鍙兘褰卞搷缂栬瘧鎬ц兘

2. 闄勫姞鍒扮紪璇戝櫒杩涚▼

鏇寸伒娲荤殑璋冭瘯鏂规硶锛屼笉闇€瑕佷慨鏀圭敓鎴愬櫒浠g爜銆?

姝ラ锛?

  1. 鍦?Visual Studio 涓墦寮€鐢熸垚鍣ㄩ」鐩?
  2. 鍦ㄧ敓鎴愬櫒浠g爜涓缃柇鐐?
  3. 閫夋嫨 "璋冭瘯" 鈫?"闄勫姞鍒拌繘绋?锛圕trl+Alt+P锛?
  4. 鍦ㄨ繘绋嬪垪琛ㄤ腑鎵惧埌浠ヤ笅杩涚▼涔嬩竴锛?
    • csc.exe - C# 缂栬瘧鍣?
    • VBCSCompiler.exe - Roslyn 缂栬瘧鍣ㄦ湇鍔″櫒
    • dotnet.exe - .NET CLI
  5. 闄勫姞璋冭瘯鍣?
  6. 鍦ㄤ娇鐢ㄨ€呴」鐩腑瑙﹀彂閲嶆柊缂栬瘧

鎻愮ず锛?

  • 浣跨敤杩涚▼鍚嶇О杩囨护蹇€熸壘鍒扮洰鏍囪繘绋?
  • 鍙互闄勫姞鍒板涓繘绋?
  • 濡傛灉鎵句笉鍒拌繘绋嬶紝鍏堢紪璇戜竴娆′娇鐢ㄨ€呴」鐩?

3. 浣跨敤鍗曞厓娴嬭瘯璋冭瘯

鏈€鎺ㄨ崘鐨勮皟璇曟柟娉曪紝鍙互绮剧‘鎺у埗璋冭瘯鍦烘櫙銆?

csharp
[Fact]
public void Debug_Generator_With_Specific_Input()
{
    // 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);
    
    // 鎸?F11 姝ヨ繘鍒扮敓鎴愬櫒浠g爜
    driver = driver.RunGeneratorsAndUpdateCompilation(
        compilation,
        out var outputCompilation,
        out var diagnostics);
    
    // 妫€鏌ョ粨鏋?
    var result = driver.GetRunResult();
}

浼樼偣锛?

  • 鉁?鍙互绮剧‘鎺у埗杈撳叆
  • 鉁?鍙互閲嶅杩愯
  • 鉁?鍙互蹇€熻凯浠?
  • 鉁?涓嶅奖鍝嶆甯哥紪璇?

4. 浣跨敤鏃ュ織杈撳嚭璋冭瘯

褰撴棤娉曚娇鐢ㄨ皟璇曞櫒鏃讹紝浣跨敤鏃ュ織杈撳嚭銆?

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    context.RegisterSourceOutput(
        classDeclarations,
        (spc, classDecl) =>
        {
            // 杈撳嚭璋冭瘯淇℃伅鍒版枃浠?
            var logPath = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                "generator-debug.log");
            
            File.AppendAllText(logPath, 
                $"Processing class: {classDecl.Identifier}\n");
            
            // 鐢熸垚浠g爜...
        });
}

娉ㄦ剰锛?

  • 鈿狅笍 涓嶈鍦ㄧ敓浜т唬鐮佷腑淇濈暀鏃ュ織杈撳嚭
  • 鈿狅笍 鏃ュ織鏂囦欢鍙兘浼氬彉寰楀緢澶?
  • 鈿狅笍 鏂囦欢 I/O 浼氬奖鍝嶆€ц兘

5. 浣跨敤 #error 鎸囦护璋冭瘯

鍦ㄧ敓鎴愮殑浠g爜涓彃鍏?#error 鎸囦护鏉ユ鏌ョ敓鎴愮殑鍐呭銆?

csharp
var code = $@"
#error Generated code for {className}
partial class {className}
{{
    public string GetInfo() => ""{propertyList}"";
}}";

context.AddSource($"{className}.g.cs", code);

缂栬瘧鏃朵細鏄剧ず閿欒锛屽彲浠ョ湅鍒扮敓鎴愮殑鍐呭銆?

CI/CD 闆嗘垚

GitHub Actions 閰嶇疆

yaml
name: Test Source Generators

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: '8.0.x'
    
    - name: Restore dependencies
      run: dotnet restore
    
    - name: Build
      run: dotnet build --no-restore --configuration Release
    
    - name: Run tests
      run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage"
    
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        files: ./coverage.cobertura.xml
        flags: unittests
        name: codecov-umbrella
    
    - name: Run performance tests
      run: dotnet run --project Testing.Tests --configuration Release --filter "Category=Performance"
    
    - name: Publish test results
      uses: EnricoMi/publish-unit-test-result-action@v2
      if: always()
      with:
        files: '**/TestResults/*.xml'

Azure DevOps Pipeline

yaml
trigger:
  branches:
    include:
    - main
    - develop

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

steps:
- task: UseDotNet@2
  displayName: 'Install .NET SDK'
  inputs:
    version: '8.0.x'

- task: DotNetCoreCLI@2
  displayName: 'Restore packages'
  inputs:
    command: 'restore'
    projects: '**/*.csproj'

- task: DotNetCoreCLI@2
  displayName: 'Build solution'
  inputs:
    command: 'build'
    projects: '**/*.csproj'
    arguments: '--configuration $(buildConfiguration) --no-restore'

- task: DotNetCoreCLI@2
  displayName: 'Run unit tests'
  inputs:
    command: 'test'
    projects: '**/*Tests.csproj'
    arguments: '--configuration $(buildConfiguration) --no-build --collect:"XPlat Code Coverage"'

- task: PublishCodeCoverageResults@1
  displayName: 'Publish code coverage'
  inputs:
    codeCoverageTool: 'Cobertura'
    summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'

- task: PublishTestResults@2
  displayName: 'Publish test results'
  inputs:
    testResultsFormat: 'VSTest'
    testResultsFiles: '**/*.trx'
    mergeTestResults: true

鏈湴 CI 鑴氭湰

bash
#!/bin/bash
# test.sh - 鏈湴娴嬭瘯鑴氭湰

set -e

echo "馃Ч Cleaning..."
dotnet clean

echo "馃摝 Restoring packages..."
dotnet restore

echo "馃敤 Building..."
dotnet build --configuration Release --no-restore

echo "馃И Running unit tests..."
dotnet test --configuration Release --no-build --verbosity normal

echo "馃搳 Generating coverage report..."
dotnet test --configuration Release --no-build --collect:"XPlat Code Coverage"

echo "鈿?Running performance tests..."
dotnet test --configuration Release --no-build --filter "Category=Performance"

echo "鉁?All tests passed!"

鏈€浣冲疄璺?vs 鍙嶆ā寮?

鉁?鏈€浣冲疄璺?

1. 浣跨敤娴嬭瘯杈呭姪绫?

csharp
// 鉁?濂界殑鍋氭硶锛氫娇鐢ㄨ緟鍔╃被绠€鍖栨祴璇?
[Fact]
public void Test_With_Helper()
{
    var source = "...";
    var result = GeneratorTestHelper.RunGenerator<MyGenerator>(source);
    Assert.NotEmpty(result.GeneratedTrees);
}
csharp
// 鉂?涓嶅ソ鐨勫仛娉曪細姣忎釜娴嬭瘯閮介噸澶嶇浉鍚岀殑璁剧疆浠g爜
[Fact]
public void Test_Without_Helper()
{
    var source = "...";
    var syntaxTree = CSharpSyntaxTree.ParseText(source);
    var references = new[] { /* ... */ };
    var compilation = CSharpCompilation.Create(/* ... */);
    var generator = new MyGenerator();
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    // ... 澶ч噺閲嶅浠g爜
}

2. 娴嬭瘯闅旂

csharp
// 鉁?濂界殑鍋氭硶锛氭瘡涓祴璇曠嫭绔?
[Fact]
public void Test_Feature_A()
{
    var source = "...";
    var result = RunGenerator(source);
    Assert.True(result.Success);
}

[Fact]
public void Test_Feature_B()
{
    var source = "...";
    var result = RunGenerator(source);
    Assert.True(result.Success);
}
csharp
// 鉂?涓嶅ソ鐨勫仛娉曪細娴嬭瘯涔嬮棿鏈変緷璧?
private static GeneratorDriverRunResult _sharedResult;

[Fact]
public void Test_Step1()
{
    _sharedResult = RunGenerator("...");
}

[Fact]
public void Test_Step2()
{
    // 渚濊禆 Test_Step1 鐨勭粨鏋?
    Assert.NotNull(_sharedResult);
}

3. 浣跨敤鎻忚堪鎬х殑娴嬭瘯鍚嶇О

csharp
// 鉁?濂界殑鍋氭硶锛氭竻鏅版弿杩版祴璇曟剰鍥?
[Fact]
public void Generator_Creates_GetInfo_Method_For_Class_With_Properties()
{
    // ...
}

[Fact]
public void Generator_Reports_Error_When_Class_Is_Not_Partial()
{
    // ...
}
csharp
// 鉂?涓嶅ソ鐨勫仛娉曪細妯$硦鐨勬祴璇曞悕绉?
[Fact]
public void Test1()
{
    // ...
}

[Fact]
public void TestGenerator()
{
    // ...
}

4. 娴嬭瘯杈圭晫鎯呭喌

csharp
// 鉁?濂界殑鍋氭硶锛氭祴璇曞悇绉嶈竟鐣屾儏鍐?
[Theory]
[InlineData("")]                    // 绌哄瓧绗︿覆
[InlineData("   ")]                 // 鍙湁绌虹櫧
[InlineData("VeryLongClassName...")] // 寰堥暱鐨勫悕绉?
[InlineData("_")]                   // 鐗规畩瀛楃
[InlineData("123")]                 // 鏁板瓧寮€澶?
public void Generator_Handles_Edge_Cases(string className)
{
    // ...
}
csharp
// 鉂?涓嶅ソ鐨勫仛娉曪細鍙祴璇曟甯告儏鍐?
[Fact]
public void Generator_Works()
{
    var source = @"
public partial class NormalClass
{
    public string Name { get; set; }
}";
    // 鍙祴璇曠悊鎯虫儏鍐?
}

5. 楠岃瘉鐢熸垚鐨勪唬鐮佸彲浠ョ紪璇?

csharp
// 鉁?濂界殑鍋氭硶锛氱‘淇濈敓鎴愮殑浠g爜鍙互缂栬瘧
[Fact]
public void Generated_Code_Compiles_Successfully()
{
    var source = "...";
    var compilation = CompilationHelper.CreateCompilation(source);
    var generator = new MyGenerator();
    
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    driver = driver.RunGeneratorsAndUpdateCompilation(
        compilation,
        out var outputCompilation,
        out _);
    
    // 楠岃瘉娌℃湁缂栬瘧閿欒
    var errors = outputCompilation.GetDiagnostics()
        .Where(d => d.Severity == DiagnosticSeverity.Error);
    Assert.Empty(errors);
}
csharp
// 鉂?涓嶅ソ鐨勫仛娉曪細鍙鏌ョ敓鎴愮殑浠g爜鏂囨湰
[Fact]
public void Generated_Code_Contains_Method()
{
    var source = "...";
    var generated = GetGeneratedCode(source);
    
    // 鍙鏌ユ枃鏈紝涓嶉獙璇佹槸鍚﹀彲浠ョ紪璇?
    Assert.Contains("public string GetInfo()", generated);
}

6. 浣跨敤蹇収娴嬭瘯楠岃瘉瀹屾暣杈撳嚭

csharp
// 鉁?濂界殑鍋氭硶锛氫娇鐢ㄥ揩鐓ф祴璇?
[Fact]
public async Task Generator_Produces_Expected_Output()
{
    var source = "...";
    
    await new CSharpSourceGeneratorTest<MyGenerator, XUnitVerifier>
    {
        TestState =
        {
            Sources = { source },
            GeneratedSources = 
            { 
                (typeof(MyGenerator), "Output.g.cs", expectedCode) 
            }
        }
    }.RunAsync();
}
csharp
// 鉂?涓嶅ソ鐨勫仛娉曪細鎵嬪姩姣旇緝姣忎釜閮ㄥ垎
[Fact]
public void Generator_Output_Is_Correct()
{
    var generated = GetGeneratedCode("...");
    
    Assert.Contains("using System;", generated);
    Assert.Contains("namespace", generated);
    Assert.Contains("class", generated);
    Assert.Contains("method", generated);
    // ... 寰堝鏂█
}

鉂?甯歌鍙嶆ā寮?

1. 鍦ㄧ敓浜т唬鐮佷腑淇濈暀璋冭瘯浠g爜

csharp
// 鉂?鍙嶆ā寮忥細鍦ㄧ敓浜т唬鐮佷腑淇濈暀 Debugger.Launch()
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    System.Diagnostics.Debugger.Launch(); // 浼氬奖鍝嶆墍鏈夌敤鎴?
    // ...
}

瑙e喅鏂规锛氫娇鐢ㄦ潯浠剁紪璇?

csharp
// 鉁?姝g‘鍋氭硶
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    #if DEBUG
    if (!System.Diagnostics.Debugger.IsAttached)
    {
        System.Diagnostics.Debugger.Launch();
    }
    #endif
    // ...
}

2. 娴嬭瘯渚濊禆澶栭儴鐘舵€?

csharp
// 鉂?鍙嶆ā寮忥細渚濊禆鏂囦欢绯荤粺
[Fact]
public void Test_Reads_Config_File()
{
    // 渚濊禆澶栭儴鏂囦欢
    var config = File.ReadAllText("config.json");
    var result = RunGenerator(config);
    // ...
}

瑙e喅鏂规锛氬湪娴嬭瘯涓彁渚涙墍鏈夎緭鍏?

csharp
// 鉁?姝g‘鍋氭硶
[Fact]
public void Test_With_Inline_Config()
{
    var config = @"{ ""setting"": ""value"" }";
    var result = RunGenerator(config);
    // ...
}

3. 蹇界暐璇婃柇淇℃伅

csharp
// 鉂?鍙嶆ā寮忥細涓嶆鏌ヨ瘖鏂俊鎭?
[Fact]
public void Test_Generator()
{
    var result = RunGenerator("...");
    Assert.NotEmpty(result.GeneratedTrees);
    // 蹇界暐浜嗗彲鑳界殑璀﹀憡鍜岄敊璇?
}

瑙e喅鏂规锛氭€绘槸妫€鏌ヨ瘖鏂俊鎭?

csharp
// 鉁?姝g‘鍋氭硶
[Fact]
public void Test_Generator_Without_Diagnostics()
{
    var result = RunGenerator("...");
    Assert.NotEmpty(result.GeneratedTrees);
    Assert.Empty(result.Diagnostics); // 纭繚娌℃湁璇婃柇淇℃伅
}

4. 娴嬭瘯杩囦簬鑴嗗急

csharp
// 鉂?鍙嶆ā寮忥細渚濊禆绮剧‘鐨勭┖鐧藉拰鏍煎紡
[Fact]
public void Test_Exact_Output()
{
    var generated = GetGeneratedCode("...");
    var expected = "public    string GetInfo()  {  return \"...\"; }";
    Assert.Equal(expected, generated); // 绌虹櫧涓嶅悓灏变細澶辫触
}

瑙e喅鏂规锛氳鑼冨寲姣旇緝鎴栦娇鐢ㄨ涔夋瘮杈?

csharp
// 鉁?姝g‘鍋氭硶
[Fact]
public void Test_Normalized_Output()
{
    var generated = GetGeneratedCode("...");
    var expected = "public string GetInfo() { return \"...\"; }";
    Assert.True(VerifyHelper.CompareCode(generated, expected));
}

鐪熷疄浣跨敤鍦烘櫙

鍦烘櫙 1: 寮€鍙戞柊鍔熻兘鏃剁殑 TDD 娴佺▼

csharp
// 姝ラ 1: 鍏堝啓娴嬭瘯
[Fact]
public void Generator_Should_Support_Nullable_Properties()
{
    var source = @"
[GenerateInfo]
public partial class Person
{
    public string? Name { get; set; }
    public int? Age { get; set; }
}";

    var generated = GeneratorTestHelper
        .GetGeneratedSources<InfoGenerator>(source)[0];
    
    // 鏈熸湜鐢熸垚鐨勪唬鐮佸鐞?null 鍊?
    Assert.Contains("Name ?? \"null\"", generated);
    Assert.Contains("Age?.ToString() ?? \"null\"", generated);
}

// 姝ラ 2: 杩愯娴嬭瘯锛堝け璐ワ級
// 姝ラ 3: 瀹炵幇鍔熻兘
// 姝ラ 4: 杩愯娴嬭瘯锛堥€氳繃锛?

鍦烘櫙 2: 淇 Bug 鏃剁殑鍥炲綊娴嬭瘯

csharp
// 鐢ㄦ埛鎶ュ憡锛氱敓鎴愬櫒鍦ㄥ鐞嗗祵濂楃被鏃跺穿婧?
[Fact]
public void Bug_Fix_Generator_Handles_Nested_Classes()
{
    var source = @"
public class Outer
{
    [GenerateInfo]
    public partial class Inner
    {
        public string Name { get; set; }
    }
}";

    // 杩欎釜娴嬭瘯搴旇涓嶄細鎶涘嚭寮傚父
    var exception = Record.Exception(() =>
    {
        var result = GeneratorTestHelper
            .RunGenerator<InfoGenerator>(source);
    });
    
    Assert.Null(exception);
}

鍦烘櫙 3: 鎬ц兘浼樺寲楠岃瘉

csharp
[Fact]
public void Performance_Optimization_Reduces_Allocations()
{
    var source = GenerateLargeSource(1000);
    
    // 浼樺寲鍓嶇殑鍩哄噯
    var beforeMemory = GC.GetTotalMemory(true);
    var result1 = GeneratorTestHelper.RunGenerator<OldGenerator>(source);
    var afterMemory1 = GC.GetTotalMemory(true);
    var oldAllocations = afterMemory1 - beforeMemory;
    
    // 浼樺寲鍚?
    beforeMemory = GC.GetTotalMemory(true);
    var result2 = GeneratorTestHelper.RunGenerator<NewGenerator>(source);
    var afterMemory2 = GC.GetTotalMemory(true);
    var newAllocations = afterMemory2 - beforeMemory;
    
    // 楠岃瘉浼樺寲鏁堟灉
    Assert.True(newAllocations < oldAllocations * 0.8,
        $"New allocations ({newAllocations}) should be < 80% of old ({oldAllocations})");
}

鍦烘櫙 4: 澶氬钩鍙板吋瀹规€ф祴璇?

csharp
[Theory]
[InlineData("net6.0")]
[InlineData("net7.0")]
[InlineData("net8.0")]
public void Generator_Works_On_Multiple_Frameworks(string targetFramework)
{
    var source = "...";
    
    // 浣跨敤涓嶅悓鐨勭洰鏍囨鏋跺垱寤虹紪璇?
    var compilation = CreateCompilationForFramework(source, targetFramework);
    var generator = new InfoGenerator();
    
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    driver = driver.RunGeneratorsAndUpdateCompilation(
        compilation,
        out var outputCompilation,
        out _);
    
    Assert.False(CompilationHelper.HasErrors(outputCompilation));
}

甯歌闂瑙g瓟

Q1: 濡備綍娴嬭瘯澧為噺鐢熸垚鍣ㄧ殑缂撳瓨琛屼负锛?

A: 杩愯鐢熸垚鍣ㄤ袱娆★紝绗簩娆″簲璇ヤ娇鐢ㄧ紦瀛樼殑缁撴灉銆?

csharp
[Fact]
public void Incremental_Generator_Uses_Cache()
{
    var source = "...";
    var compilation = CompilationHelper.CreateCompilation(source);
    var generator = new InfoGenerator();
    
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    
    // 绗竴娆¤繍琛?
    driver = driver.RunGenerators(compilation);
    var result1 = driver.GetRunResult();
    
    // 绗簩娆¤繍琛岋紙娌℃湁淇敼锛?
    driver = driver.RunGenerators(compilation);
    var result2 = driver.GetRunResult();
    
    // 楠岃瘉浣跨敤浜嗙紦瀛橈紙缁撴灉鐩稿悓浣嗘病鏈夐噸鏂拌绠楋級
    Assert.Equal(
        result1.GeneratedTrees.Length,
        result2.GeneratedTrees.Length);
}

Q2: 濡備綍娴嬭瘯鐢熸垚鍣ㄦ姤鍛婄殑璇婃柇淇℃伅锛?

A: 浣跨敤 Microsoft.CodeAnalysis.Testing 妗嗘灦鐨勮瘖鏂獙璇佸姛鑳姐€?

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

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

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

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

A: 鍑犱釜浼樺寲寤鸿锛?

  1. 骞惰杩愯娴嬭瘯锛?
csharp
[Collection("Generator Tests")] // 浣跨敤闆嗗悎鍒嗙粍
public class FastTests { }
  1. 浣跨敤娴嬭瘯鍒嗙被锛?
csharp
[Trait("Category", "Fast")]
public void QuickTest() { }

[Trait("Category", "Slow")]
public void SlowTest() { }
  1. 缂撳瓨缂栬瘧瀵硅薄锛?
csharp
private static readonly Lazy<CSharpCompilation> _baseCompilation = 
    new(() => CreateBaseCompilation());

Q4: 濡備綍娴嬭瘯鐢熸垚鍣ㄥ湪澶у瀷椤圭洰涓殑琛ㄧ幇锛?

A: 鍒涘缓鎬ц兘娴嬭瘯锛岀敓鎴愬ぇ閲忔祴璇曟暟鎹€?

csharp
[Theory]
[InlineData(10)]
[InlineData(100)]
[InlineData(1000)]
public void Generator_Scales_With_Project_Size(int classCount)
{
    var source = GenerateLargeSource(classCount);
    var stopwatch = Stopwatch.StartNew();
    
    var result = GeneratorTestHelper.RunGenerator<InfoGenerator>(source);
    
    stopwatch.Stop();
    
    // 楠岃瘉鎬ц兘绾挎€у闀?
    var expectedMaxTime = classCount * 10; // 姣忎釜绫?10ms
    Assert.True(stopwatch.ElapsedMilliseconds < expectedMaxTime);
}

Q5: 濡備綍璋冭瘯鐢熸垚鍣ㄥ湪瀹為檯椤圭洰涓殑闂锛?

A: 浣跨敤闄勫姞鍒拌繘绋嬬殑鏂规硶锛?

  1. 鍦ㄧ敓鎴愬櫒浠g爜涓缃柇鐐?
  2. 闄勫姞鍒?VBCSCompiler.exe 鎴?csc.exe
  3. 鍦ㄤ娇鐢ㄨ€呴」鐩腑瑙﹀彂閲嶆柊缂栬瘧
  4. 鏂偣浼氳鍛戒腑

鎴栬€呬娇鐢?Debugger.Launch()锛堜粎鍦?DEBUG 妯″紡锛夈€?

Q6: 濡備綍楠岃瘉鐢熸垚鐨勪唬鐮佺鍚堢紪鐮佽鑼冿紵

A: 浣跨敤 Roslyn 鍒嗘瀽鍣ㄦ垨鑷畾涔夐獙璇併€?

csharp
[Fact]
public void Generated_Code_Follows_Naming_Conventions()
{
    var generated = GeneratorTestHelper
        .GetGeneratedSources<InfoGenerator>("...");
    
    // 楠岃瘉鏂规硶鍚嶄娇鐢?PascalCase
    Assert.Matches(@"\bpublic\s+\w+\s+[A-Z][a-zA-Z0-9]*\s*\(", generated[0]);
    
    // 楠岃瘉娌℃湁浣跨敤 var
    Assert.DoesNotContain("var ", generated[0]);
}

Q7: 濡備綍娴嬭瘯鐢熸垚鍣ㄧ殑閿欒澶勭悊锛?

A: 鎻愪緵鏃犳晥杈撳叆骞堕獙璇侀敊璇姤鍛娿€?

csharp
[Theory]
[InlineData("")]                          // 绌鸿緭鍏?
[InlineData("invalid syntax {{{")]       // 璇硶閿欒
[InlineData("class NotPartial { }")]     // 缂哄皯 partial
public void Generator_Handles_Invalid_Input(string source)
{
    var result = GeneratorTestHelper.RunGenerator<InfoGenerator>(source);
    
    // 搴旇鎶ュ憡璇婃柇淇℃伅鎴栦笉鐢熸垚浠g爜
    Assert.True(
        result.Diagnostics.Length > 0 || 
        result.GeneratedTrees.Length == 0);
}

Q8: 濡備綍鍦?CI/CD 涓繍琛岀敓鎴愬櫒娴嬭瘯锛?

A: 鍦?CI 閰嶇疆涓坊鍔犳祴璇曟楠ゃ€?

yaml
# GitHub Actions 绀轰緥
- name: Run generator tests
  run: dotnet test --configuration Release --logger "trx;LogFileName=test-results.trx"

- name: Publish test results
  uses: EnricoMi/publish-unit-test-result-action@v2
  if: always()
  with:
    files: '**/test-results.trx'

Q9: 濡備綍娴嬭瘯鐢熸垚鍣ㄤ笌鍏朵粬鐢熸垚鍣ㄧ殑鍏煎鎬э紵

A: 鍦ㄥ悓涓€涓紪璇戜腑杩愯澶氫釜鐢熸垚鍣ㄣ€?

csharp
[Fact]
public void Generator_Works_With_Other_Generators()
{
    var source = "...";
    var compilation = CompilationHelper.CreateCompilation(source);
    
    var generator1 = new InfoGenerator();
    var generator2 = new OtherGenerator();
    
    GeneratorDriver driver = CSharpGeneratorDriver.Create(
        generator1,
        generator2);
    
    driver = driver.RunGeneratorsAndUpdateCompilation(
        compilation,
        out var outputCompilation,
        out _);
    
    Assert.False(CompilationHelper.HasErrors(outputCompilation));
}

Q10: 濡備綍娴嬭瘯鐢熸垚鍣ㄧ殑澧為噺鏇存柊锛?

A: 淇敼杈撳叆骞堕獙璇佸彧閲嶆柊鐢熸垚鍙楀奖鍝嶇殑閮ㄥ垎銆?

csharp
[Fact]
public void Incremental_Generator_Only_Updates_Changed_Files()
{
    var source1 = "..."; // 鍘熷婧愪唬鐮?
    var source2 = "..."; // 淇敼鍚庣殑婧愪唬鐮?
    
    var compilation1 = CompilationHelper.CreateCompilation(source1);
    var compilation2 = CompilationHelper.CreateCompilation(source2);
    
    var generator = new InfoGenerator();
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    
    // 绗竴娆¤繍琛?
    driver = driver.RunGenerators(compilation1);
    var result1 = driver.GetRunResult();
    
    // 绗簩娆¤繍琛岋紙淇敼鍚庯級
    driver = driver.RunGenerators(compilation2);
    var result2 = driver.GetRunResult();
    
    // 楠岃瘉澧為噺鏇存柊
    Assert.NotEqual(
        result1.GeneratedTrees.Length,
        result2.GeneratedTrees.Length);
}

Q11: 濡備綍娴嬭瘯鐢熸垚鍣ㄧ殑绾跨▼瀹夊叏鎬э紵

A: 骞跺彂杩愯鐢熸垚鍣ㄣ€?

csharp
[Fact]
public void Generator_Is_Thread_Safe()
{
    var source = "...";
    var tasks = new List<Task>();
    
    for (int i = 0; i < 10; i++)
    {
        tasks.Add(Task.Run(() =>
        {
            var result = GeneratorTestHelper
                .RunGenerator<InfoGenerator>(source);
            Assert.NotEmpty(result.GeneratedTrees);
        }));
    }
    
    // 鎵€鏈変换鍔¢兘搴旇鎴愬姛瀹屾垚
    Task.WaitAll(tasks.ToArray());
}

Q12: 濡備綍娴嬭瘯鐢熸垚鍣ㄧ殑鍐呭瓨浣跨敤锛?

A: 浣跨敤 BenchmarkDotNet 鐨勫唴瀛樿瘖鏂櫒銆?

csharp
[MemoryDiagnoser]
public class MemoryTests
{
    [Benchmark]
    public void Generator_Memory_Usage()
    {
        var source = GenerateLargeSource(1000);
        GeneratorTestHelper.RunGenerator<InfoGenerator>(source);
    }
}

涓嬩竴姝?

瀹屾垚娴嬭瘯鍜岃皟璇曠殑瀛︿範鍚庯紝浣犲彲浠ワ細

  1. 瀛︿範 .NET 10 宓屽叆寮忕敓鎴愬櫒 - .NET 10 宓屽叆寮忕敓鎴愬櫒绀轰緥
  2. *娣卞叆 API 鍙傝€? - 璇箟妯″瀷 API
  3. *浜嗚В鏈€浣冲疄璺? - [鏈€浣冲疄璺垫寚鍗梋(../api/best-practices.md)
  4. 鎺㈢储楂樼骇妯″紡 - 楂樼骇妯″紡
  5. 鏌ョ湅鏇村绀轰緥 - 绀轰緥绱㈠紩

鐩稿叧鏂囨。

  • [蹇€熷紑濮媇(../guide/getting-started.md) - 婧愮敓鎴愬櫒鍏ラ棬
  • [澧為噺鐢熸垚鍣╙(./incremental-generator.md) - 鎬ц兘浼樺寲
  • 璇婃柇鎶ュ憡 - 閿欒鎶ュ憡
  • 璋冭瘯鍜岃瘖鏂?API - 璋冭瘯鎶€鏈瑙?

基于 MIT 许可发布