娴嬭瘯鍜岃皟璇曟簮鐢熸垚鍣?
瀛︿範濡備綍娴嬭瘯鍜岃皟璇?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: 闄勫姞鍒拌繘绋?
- 鍦?Visual Studio 涓紝閫夋嫨 "璋冭瘯" 鈫?"闄勫姞鍒拌繘绋?
- 鎵惧埌
csc.exe鎴?VBCSCompiler.exe杩涚▼ - 闄勫姞璋冭瘯鍣?
- 鍦ㄧ敓鎴愬櫒浠g爜涓缃柇鐐?
- 閲嶆柊缂栬瘧浣跨敤鑰呴」鐩?
鏂规硶 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_PropertiesGenerator_Reports_Error_For_Non_Partial_ClassGenerated_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();
}
}