用途
该技能用于使用 .NET Aspire 的测试设施配合 xUnit 编写集成测试。适用场景包括:为 .NET Aspire 应用编写集成测试;测试带真实数据库连接的 ASP.NET Core 应用;验证分布式应用中的服务间通信;在容器中使用真实基础设施(SQL Server、Redis、消息队列)进行测试;将 Playwright UI 测试与 Aspire 编排的服务结合;测试具备服务发现与网络能力的微服务。
核心原则
- 真实依赖:通过 Aspire 使用实际基础设施(数据库、缓存),而非模拟。
- 动态端口绑定:让 Aspire 动态分配端口(127.0.0.1:0)以避免冲突。
- 夹具生命周期:使用 IAsyncLifetime 正确设置和清理测试夹具。
- 端点发现:绝不硬编码 URL,在运行时从 Aspire 发现端点。
- 并行隔离:使用 xUnit 集合控制测试并行化。
- 健康检查:运行测试前始终等待服务健康。
所需 NuGet 包
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="$(AspireVersion)" />
<PackageReference Include="xunit" Version="*" />
<PackageReference Include="xunit.runner.visualstudio" Version="*" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" />
</ItemGroup>
关键:集成测试的文件监视器修复
当运行大量各自启动 IHost 的集成测试时,默认 .NET 主机生成器会启用配置文件监视以支持配置重载,这会耗尽 Linux 上的文件描述符限制。在任何测试运行前,将以下内容添加到测试项目:
// TestEnvironmentInitializer.cs
using System.Runtime.CompilerServices;
namespace YourApp.Tests;
internal static class TestEnvironmentInitializer
{
[ModuleInitializer]
internal static void Initialize()
{
// Disable config file watching in test hosts
// Prevents file descriptor exhaustion (inotify watch limit) on Linux
Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE", "false");
}
}
模式 1:基础 Aspire 测试夹具(现代 API)
using Aspire.Hosting;
using Aspire.Hosting.Testing;
public sealed class AspireAppFixture : IAsyncLifetime
{
private DistributedApplication? _app;
public DistributedApplication App => _app
?? throw new InvalidOperationException("App not initialized");
public async Task InitializeAsync()
{
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.YourApp_AppHost>([
"YourApp:UseVolumes=false",
"YourApp:Environment=IntegrationTest",
"YourApp:Replicas=1"
]);
_app = await builder.BuildAsync();
using var startupCts = new CancellationTokenSource(TimeSpan.FromMinutes(10));
await _app.StartAsync(startupCts.Token);
using var healthCts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
await _app.ResourceNotifications.WaitForResourceHealthyAsync("api", healthCts.Token);
}
public Uri GetEndpoint(string resourceName, string scheme = "https")
{
return _app?.GetEndpoint(resourceName, scheme)
?? throw new InvalidOperationException($"Endpoint for '{resourceName}' not found");
}
public async Task DisposeAsync()
{
if (_app is not null)
{
await _app.DisposeAsync();
}
}
}
模式 2:在测试中使用夹具
[CollectionDefinition("Aspire collection")]
public class AspireCollection : ICollectionFixture<AspireAppFixture> { }
[Collection("Aspire collection")]
public class IntegrationTests
{
private readonly AspireAppFixture _fixture;
public IntegrationTests(AspireAppFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task Application_ShouldStart()
{
var httpClient = _fixture.App.CreateHttpClient("yourapp");
var response = await httpClient.GetAsync("/");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
更多模式(端点发现、数据库测试、Playwright UI 测试、条件资源配置、Respawn 数据库重置、服务间通信、消息队列测试)见 advanced-patterns.md。
常见模式摘要
| 模式 | 用例 |
|---|---|
| 基础夹具 | 简单 HTTP 端点测试 |
| 端点发现 | 避免硬编码 URL |
| 数据库测试 | 验证数据访问层 |
| Playwright 集成 | 带真实后端的完整 UI 测试 |
| 配置覆盖 | 测试专用设置 |
| 健康检查 | 确保服务就绪 |
| 服务通信 | 测试分布式系统交互 |
| 消息队列测试 | 验证异步消息传递 |
棘手/不明显的提示
| 问题 | 解决方案 |
|---|---|
| 测试立即超时 | 调用 await _app.StartAsync() 并等待服务健康 |
| 测试间端口冲突 | 使用 xUnit CollectionDefinition 共享夹具 |
| 时序导致的测试不稳定 | 实现正确的健康检查轮询,而非 Task.Delay() |
| 无法连接 SQL Server | 通过 GetConnectionStringAsync() 动态获取连接字符串 |
| 并行测试相互干扰 | 使用 [Collection] 特性让相关测试顺序运行 |
| Aspire 仪表板冲突 | 同一时间只能运行一个仪表板;测试复用同一实例 |
最佳实践
- 使用 IAsyncLifetime:确保正确的异步初始化与清理。
- 通过集合共享夹具:复用应用实例以减少测试执行时间。
- 动态发现端点:绝不硬编码 localhost:5000 之类地址。
- 等待健康检查:不要假设服务立即就绪。
- 使用真实依赖测试:Aspire 让使用真实 SQL、Redis 等变得容易。
- 清理资源:始终正确实现 DisposeAsync。
- 使用有意义的测试数据:用真实测试数据填充数据库。
- 测试失败场景:验证错误处理与弹性。
- 保持测试隔离:每个测试应独立且与顺序无关。
- 监控测试执行时间:若测试缓慢,考虑并行化。
GitHub Actions 设置、自定义资源等待器以及 Aspire CLI/MCP 集成见 ci-and-tooling.md。
调试提示
- 运行 Aspire 仪表板:测试失败时,检查 http://localhost:15888 的仪表板。
- 使用带 MCP 的 Aspire CLI:让 AI 助手查询真实应用状态。
- 启用详细日志:设置 ASPIRE_ALLOW_UNSECURED_TRANSPORT=true 以获得更详细输出。
- 检查容器日志:使用 docker logs 查看容器输出。
- 在夹具中使用断点:调试夹具初始化以捕获启动问题。
- 验证资源名称:确保 AppHost 与测试之间的资源名称一致。