GFramework/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs
GeWuYou 92eb365dc7 feat(config): 添加配置系统集成测试和文档
- 添加 ArchitectureConfigIntegrationTests 验证架构初始化流程中配置加载
- 添加 GeneratedConfigConsumerIntegrationTests 测试消费者项目配置绑定功能
- 添加完整的游戏内容配置系统中文文档
- 添加生成配置目录和注册选项支持批量表注册与筛选
- 实现配置架构集成模板和热重载功能
- 添加跨表引用校验和运行时诊断功能
- 实现 VS Code 工具支持配置浏览和表单编辑
- 添加查询辅助方法支持按字段快速检索配置数据
2026-04-06 16:38:42 +08:00

161 lines
5.3 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using GFramework.Core.Architectures;
using GFramework.Game.Config;
using GFramework.Game.Config.Generated;
using NUnit.Framework;
namespace GFramework.Game.Tests.Config;
/// <summary>
/// 验证在 <see cref="Architecture" /> 初始化流程中可以通过聚合注册入口加载生成配置表,并通过表访问器读取数据。
/// </summary>
[TestFixture]
public class ArchitectureConfigIntegrationTests
{
/// <summary>
/// 架构初始化期间,通过 <see cref="YamlConfigLoader" /> 的聚合注册入口注册生成表,
/// 并将 <see cref="ConfigRegistry" /> 作为 utility 暴露给架构上下文读取。
/// </summary>
[Test]
public async Task ConfigLoaderCanRunDuringArchitectureInitialization()
{
var rootPath = CreateTempConfigRoot();
ConsumerArchitecture? architecture = null;
var initialized = false;
try
{
architecture = new ConsumerArchitecture(rootPath);
await architecture.InitializeAsync();
initialized = true;
var table = architecture.MonsterTable;
Assert.Multiple(() =>
{
Assert.That(table.Get(1).Name, Is.EqualTo("Slime"));
Assert.That(table.Get(2).Hp, Is.EqualTo(30));
Assert.That(table.FindByFaction("dungeon").Select(static config => config.Name),
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
Assert.That(architecture.Registry.TryGetMonsterTable(out var retrieved), Is.True);
Assert.That(retrieved, Is.Not.Null);
Assert.That(retrieved!.Get(1).Name, Is.EqualTo("Slime"));
Assert.That(architecture.Registry.TryGetItemTable(out _), Is.False);
Assert.That(architecture.Context.GetUtility<ConfigRegistry>(), Is.SameAs(architecture.Registry));
});
}
finally
{
if (architecture is not null && initialized)
{
await architecture.DestroyAsync();
}
DeleteDirectoryIfExists(rootPath);
}
}
private static string CreateTempConfigRoot()
{
var rootPath = Path.Combine(Path.GetTempPath(), "GFramework.ConfigArchitecture", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(rootPath);
Directory.CreateDirectory(Path.Combine(rootPath, "schemas"));
Directory.CreateDirectory(Path.Combine(rootPath, "monster"));
File.WriteAllText(Path.Combine(rootPath, "schemas", "monster.schema.json"), MonsterSchemaJson);
File.WriteAllText(Path.Combine(rootPath, "monster", "slime.yaml"), MonsterSlimeYaml);
File.WriteAllText(Path.Combine(rootPath, "monster", "goblin.yaml"), MonsterGoblinYaml);
return rootPath;
}
/// <summary>
/// 最佳努力尝试删除临时目录。
/// </summary>
private static void DeleteDirectoryIfExists(string path)
{
if (!Directory.Exists(path))
{
return;
}
try
{
Directory.Delete(path, true);
}
catch (IOException)
{
// Ignored: cleanup is best effort and should not fail the test.
}
catch (UnauthorizedAccessException)
{
// Ignored: cleanup is best effort and can transiently fail when files are still being released.
}
}
private const string MonsterSchemaJson = @"{
""title"": ""Monster Config"",
""description"": ""Defines one monster entry for the generated consumer integration test."",
""type"": ""object"",
""required"": [
""id"",
""name"",
""hp"",
""faction""
],
""properties"": {
""id"": {
""type"": ""integer"",
""description"": ""Monster identifier.""
},
""name"": {
""type"": ""string"",
""description"": ""Monster display name.""
},
""hp"": {
""type"": ""integer"",
""description"": ""Monster base health.""
},
""faction"": {
""type"": ""string"",
""description"": ""Used by the integration test to validate generated non-unique queries.""
}
}
}";
private const string MonsterSlimeYaml =
"id: 1\nname: Slime\nhp: 10\nfaction: dungeon\n";
private const string MonsterGoblinYaml =
"id: 2\nname: Goblin\nhp: 30\nfaction: dungeon\n";
private sealed class ConsumerArchitecture : Architecture
{
private readonly string _configRoot;
public ConfigRegistry Registry { get; }
public MonsterTable MonsterTable { get; private set; } = null!;
public ConsumerArchitecture(string configRoot)
{
_configRoot = configRoot ?? throw new ArgumentNullException(nameof(configRoot));
Registry = new ConfigRegistry();
}
protected override void OnInitialize()
{
RegisterUtility(Registry);
var loader = new YamlConfigLoader(_configRoot)
.RegisterAllGeneratedConfigTables(
new GeneratedConfigRegistrationOptions
{
IncludedConfigDomains = new[] { MonsterConfigBindings.ConfigDomain }
});
loader.LoadAsync(Registry).GetAwaiter().GetResult();
MonsterTable = Registry.GetMonsterTable();
}
}
}