mcpField
A
akka-net-testing-patterns

akka-net-testing-patterns

github.com/aaronontheweb测试与质量1.2k421 次安装今天更新
代码仓库

关于 akka-net-testing-patterns

面向使用 Akka.NET 构建应用的 .NET 团队的测试指南,涵盖 Actor、持久化、交互和本地分片,并建议多数现代应用使用 Akka.Hosting.TestKit。

主要功能

  1. 01为 Actor 注入伪造依赖进行测试
  2. 02用 TestProbe 验证消息
  3. 03测试持久化与恢复
  4. 04在本地测试分片
  5. 05使用异步断言和场景测试

适用场景

  1. 01检查 Actor 消息流
  2. 02验证事件溯源 Actor 恢复
  3. 03无需集群测试分片
  4. 04端到端测试 Actor 工作流

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/aaronontheweb/dotnet-skills --skill akka-net-testing-patterns

安装后在 Claude Code 中输入/akka-net-testing-patterns确认已加载。

附带文件anti-patterns-and-reference.mdexamples.md

详细介绍

Akka.NET 测试模式

何时使用此技能

在以下情况下使用此技能:

  • 为 Akka.NET Actor 编写单元测试
  • 测试使用事件溯源的持久化 Actor
  • 验证 Actor 交互和消息流
  • 测试 Actor 监督和生命周期
  • 在 Actor 测试中模拟外部依赖项
  • 在本地测试集群分片行为
  • 验证 Actor 状态恢复和持久化

参考文件

选择测试方法

使用 Akka.Hosting.TestKit(推荐用于 95% 的场景)

适用场景:

  • 使用 Microsoft.Extensions.DependencyInjection 构建现代 .NET 应用
  • 在生产环境中使用 Akka.Hosting 配置 Actor
  • 需要向 Actor 注入服务(IOptions、DbContext、ILogger、HTTP 客户端等)
  • 测试使用 ASP.NET Core、Worker Services 或 .NET Aspire 的应用
  • 使用现代 Akka.NET 项目(Akka.NET v1.5+)

优势:

  • 原生支持依赖注入——在测试中用伪对象替换服务
  • 配置与生产环境保持一致(相同的扩展方法可用于测试)
  • 清晰分离 Actor 逻辑和基础设施
  • 类型安全的 Actor 注册表,用于检索 Actor
使用传统 Akka.TestKit

适用场景:

  • 参与 Akka.NET 核心库开发
  • 在没有 Microsoft.Extensions 的环境中工作(控制台应用、旧版系统)
  • 使用手动创建 Props 且不使用 DI 的旧版代码库

有关传统 TestKit 模式,请参阅 anti-patterns-and-reference.md。


核心原则(Akka.Hosting.TestKit)

  1. 继承 Akka.Hosting.TestKit.TestKit —— 这是框架基类,而非用户定义的类
  2. 重写 ConfigureServices() —— 将真实服务替换为伪对象/模拟对象
  3. 重写 ConfigureAkka() —— 使用与生产环境相同的扩展方法配置 Actor
  4. 使用 ActorRegistry —— 以类型安全的方式检索 Actor 引用
  5. 组合优于继承 —— 将伪服务作为字段,而非基类
  6. 不创建自定义基类 —— 使用方法重写,而非继承层次结构
  7. 一次测试一个 Actor —— 使用 TestProbe 作为依赖项
  8. 匹配生产模式 —— 使用相同的扩展方法,但采用不同的 AkkaExecutionMode

必需的 NuGet 包

<ItemGroup>
  
  <PackageReference Include="Akka.Hosting.TestKit" Version="*" />

  
  <PackageReference Include="xunit" Version="*" />
  <PackageReference Include="xunit.runner.visualstudio" Version="*" />
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" />

  
  <PackageReference Include="FluentAssertions" Version="*" />

  
  <PackageReference Include="Akka.Persistence.Hosting" Version="*" />

  
  <PackageReference Include="Akka.Cluster.Hosting" Version="*" />
</ItemGroup>

重要:修复测试项目的文件监视器问题

Akka.Hosting.TestKit 会启动真实的 IHost 实例,这些实例默认会启用用于重新加载配置的文件监视器。运行大量测试时,这会耗尽 Linux 上的文件描述符限制(inotify 监视限制)。

将以下内容添加到测试项目中——它会在任何测试运行前执行:

// 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");
    }
}

此设置的重要性:

  • [ModuleInitializer] 会在任何测试代码运行前自动执行
  • 为所有 IHost 实例全局设置环境变量
  • 防止运行 100 个以上测试时出现难以理解的 inotify 错误
  • 同样适用于使用 IHost 的 Aspire 集成测试

测试模式概览

以下每种模式都有简要说明。完整代码示例请参阅 examples.md。

模式 1:基础 Actor 测试

基础模式。重写 ConfigureServices() 以注入伪对象,重写 ConfigureAkka(),使用与生产环境相同的扩展方法注册 Actor。

public class OrderActorTests : TestKit
{
    private readonly FakeOrderRepository _fakeRepository = new();

    protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
    {
        services.AddSingleton<IOrderRepository>(_fakeRepository);
    }

    protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
    {
        builder.WithInMemoryJournal().WithInMemorySnapshotStore();
        builder.WithActors((system, registry, resolver) =>
        {
            registry.Register<OrderActor>(system.ActorOf(resolver.Props<OrderActor>(), "order-actor"));
        });
    }

    [Fact]
    public async Task CreateOrder_Success_SavesToRepository()
    {
        var orderActor = ActorRegistry.Get<OrderActor>();
        var response = await orderActor.Ask<OrderCommandResult>(
            new CreateOrder("ORDER-123", "CUST-456", 99.99m), RemainingOrDefault);
        response.Status.Should().Be(CommandStatus.Success);
        _fakeRepository.SaveCallCount.Should().Be(1);
    }
}
模式 2:使用 TestProbe 测试 Actor 交互

在 ActorRegistry 中注册一个 TestProbe,作为依赖 Actor 的替身。使用 ExpectMsgAsync<T>() 验证消息已发送。

模式 3:自动响应的 TestProbe

当被测 Actor 使用 Ask 与依赖项通信时,创建一个自动响应 Actor,将消息转发给探针并进行回复,以避免超时。

模式 4:测试持久化 Actor

使用 WithInMemoryJournal() 和 WithInMemorySnapshotStore()。通过 PoisonPill 终止 Actor,然后发送查询以强制从日志中恢复,从而测试恢复。

模式 5:复用生产配置

在测试中始终复用生产扩展方法,不要重复编写 HOCON 配置。这样可确保测试使用与生产环境完全相同的配置。

protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
{
    builder
        .AddDraftSerializer()                                    // Same as production
        .AddOrderDomainActors(AkkaExecutionMode.LocalTest)      // Same, but local mode
        .WithInMemoryJournal().WithInMemorySnapshotStore();      // Test-specific overrides
}
模式 6:在本地进行集群分片

使用 AkkaExecutionMode.LocalTest 和 GenericChildPerEntityParent,无需实际集群即可测试分片行为。扩展方法相同,模式不同。

模式 7:对异步操作使用 AwaitAssertAsync

当 Actor 执行异步操作时,使用 AwaitAssertAsync。它会重试断言,直到断言通过或超时,从而避免测试不稳定。

await AwaitAssertAsync(() =>
{
    _fakeReadModelService.SyncCallCount.Should().BeGreaterOrEqualTo(1);
}, TimeSpan.FromSeconds(3));
模式 8:基于场景的集成测试

通过多个 Actor 和状态转换,对完整业务工作流进行端到端测试。注册所有领域 Actor,并验证每个步骤的状态。


常见模式汇总

模式使用场景
基本 Actor 测试使用注入服务的单个 Actor
TestProbe验证 Actor 是否向依赖项发送消息
自动响应器避免测试时 Ask 超时
持久化 Actor测试事件溯源和恢复
集群分片在本地测试分片行为
AwaitAssertAsync处理 Actor 中的异步操作
场景测试端到端业务工作流

最佳实践

  1. 每个 Actor 对应一个测试类 - 让测试保持专注
  2. 重写 ConfigureServices/ConfigureAkka - 不要创建基类
  3. 使用伪实现,而非模拟对象 - 更简单,也更易维护
  4. 一次测试一个 Actor - 使用 TestProbe 处理依赖项
  5. 匹配生产环境模式 - 使用相同的扩展方法和不同的 AkkaExecutionMode
  6. 对异步操作使用 AwaitAssertAsync - 避免测试不稳定
  7. 测试恢复功能 - 终止并重启 Actor,以验证持久化
  8. 对工作流使用场景测试 - 对完整业务流程进行端到端测试
  9. 保持测试快速 - 使用内存持久化,不连接真实数据库
  10. 使用有意义的名称 - Scenario_FirstTimePurchase_SuccessfulPayment

调试提示

  1. 启用调试日志 - 向 TestKit 构造函数传入 LogLevel.Debug
  2. 使用 ITestOutputHelper - 在测试输出中查看 Actor 系统日志
  3. 检查 TestProbe - 查看 probe.Messages,了解发送了哪些内容
  4. 查询 Actor 状态 - 添加状态查询消息以便调试
  5. 使用带日志的 AwaitAssertAsync - 查看断言失败的原因
  6. 检查 ActorRegistry - 验证 Actor 是否已正确注册
// Constructor with debug logging
public OrderActorTests(ITestOutputHelper output)
    : base(output: output, logLevel: LogLevel.Debug)
{
}

评论

支持 Markdown