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爜銆?
姝ラ锛?
- 鍦?Visual Studio 涓墦寮€鐢熸垚鍣ㄩ」鐩?
- 鍦ㄧ敓鎴愬櫒浠g爜涓缃柇鐐?
- 閫夋嫨 "璋冭瘯" 鈫?"闄勫姞鍒拌繘绋?锛圕trl+Alt+P锛?
- 鍦ㄨ繘绋嬪垪琛ㄤ腑鎵惧埌浠ヤ笅杩涚▼涔嬩竴锛?
csc.exe- C# 缂栬瘧鍣?VBCSCompiler.exe- Roslyn 缂栬瘧鍣ㄦ湇鍔″櫒dotnet.exe- .NET CLI
- 闄勫姞璋冭瘯鍣?
- 鍦ㄤ娇鐢ㄨ€呴」鐩腑瑙﹀彂閲嶆柊缂栬瘧
鎻愮ず锛?
- 浣跨敤杩涚▼鍚嶇О杩囨护蹇€熸壘鍒扮洰鏍囪繘绋?
- 鍙互闄勫姞鍒板涓繘绋?
- 濡傛灉鎵句笉鍒拌繘绋嬶紝鍏堢紪璇戜竴娆′娇鐢ㄨ€呴」鐩?
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: 鍑犱釜浼樺寲寤鸿锛?
- 骞惰杩愯娴嬭瘯锛?
csharp
[Collection("Generator Tests")] // 浣跨敤闆嗗悎鍒嗙粍
public class FastTests { }- 浣跨敤娴嬭瘯鍒嗙被锛?
csharp
[Trait("Category", "Fast")]
public void QuickTest() { }
[Trait("Category", "Slow")]
public void SlowTest() { }- 缂撳瓨缂栬瘧瀵硅薄锛?
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: 浣跨敤闄勫姞鍒拌繘绋嬬殑鏂规硶锛?
- 鍦ㄧ敓鎴愬櫒浠g爜涓缃柇鐐?
- 闄勫姞鍒?
VBCSCompiler.exe鎴?csc.exe - 鍦ㄤ娇鐢ㄨ€呴」鐩腑瑙﹀彂閲嶆柊缂栬瘧
- 鏂偣浼氳鍛戒腑
鎴栬€呬娇鐢?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);
}
}涓嬩竴姝?
瀹屾垚娴嬭瘯鍜岃皟璇曠殑瀛︿範鍚庯紝浣犲彲浠ワ細
- 瀛︿範 .NET 10 宓屽叆寮忕敓鎴愬櫒 - .NET 10 宓屽叆寮忕敓鎴愬櫒绀轰緥
- *娣卞叆 API 鍙傝€? - 璇箟妯″瀷 API
- *浜嗚В鏈€浣冲疄璺? - [鏈€浣冲疄璺垫寚鍗梋(../api/best-practices.md)
- 鎺㈢储楂樼骇妯″紡 - 楂樼骇妯″紡
- 鏌ョ湅鏇村绀轰緥 - 绀轰緥绱㈠紩
鐩稿叧鏂囨。
- [蹇€熷紑濮媇(../guide/getting-started.md) - 婧愮敓鎴愬櫒鍏ラ棬
- [澧為噺鐢熸垚鍣╙(./incremental-generator.md) - 鎬ц兘浼樺寲
- 璇婃柇鎶ュ憡 - 閿欒鎶ュ憡
- 璋冭瘯鍜岃瘖鏂?API - 璋冭瘯鎶€鏈瑙?