SuperAPI AI 工具集

aspire-integration-testing

测试与质量

查看项目

更新于 10-15 10:27

技能介绍

用途

该技能用于使用 .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 与测试之间的资源名称一致。