关于 akka-net-testing-patterns
面向使用 Akka.NET 构建应用的 .NET 团队的测试指南,涵盖 Actor、持久化、交互和本地分片,并建议多数现代应用使用 Akka.Hosting.TestKit。
主要功能
- 01为 Actor 注入伪造依赖进行测试
- 02用 TestProbe 验证消息
- 03测试持久化与恢复
- 04在本地测试分片
- 05使用异步断言和场景测试
适用场景
- 01检查 Actor 消息流
- 02验证事件溯源 Actor 恢复
- 03无需集群测试分片
- 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 状态恢复和持久化
参考文件
- examples.md:所有测试模式的完整代码示例(模式 1-8 及提醒)
- anti-patterns-and-reference.md:反模式、传统 TestKit 和 CI/CD 集成
选择测试方法
使用 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)
- 继承
Akka.Hosting.TestKit.TestKit—— 这是框架基类,而非用户定义的类 - 重写
ConfigureServices()—— 将真实服务替换为伪对象/模拟对象 - 重写
ConfigureAkka()—— 使用与生产环境相同的扩展方法配置 Actor - 使用
ActorRegistry—— 以类型安全的方式检索 Actor 引用 - 组合优于继承 —— 将伪服务作为字段,而非基类
- 不创建自定义基类 —— 使用方法重写,而非继承层次结构
- 一次测试一个 Actor —— 使用 TestProbe 作为依赖项
- 匹配生产模式 —— 使用相同的扩展方法,但采用不同的
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 中的异步操作 |
| 场景测试 | 端到端业务工作流 |
最佳实践
- 每个 Actor 对应一个测试类 - 让测试保持专注
- 重写 ConfigureServices/ConfigureAkka - 不要创建基类
- 使用伪实现,而非模拟对象 - 更简单,也更易维护
- 一次测试一个 Actor - 使用 TestProbe 处理依赖项
- 匹配生产环境模式 - 使用相同的扩展方法和不同的
AkkaExecutionMode - 对异步操作使用 AwaitAssertAsync - 避免测试不稳定
- 测试恢复功能 - 终止并重启 Actor,以验证持久化
- 对工作流使用场景测试 - 对完整业务流程进行端到端测试
- 保持测试快速 - 使用内存持久化,不连接真实数据库
- 使用有意义的名称 -
Scenario_FirstTimePurchase_SuccessfulPayment
调试提示
- 启用调试日志 - 向 TestKit 构造函数传入
LogLevel.Debug - 使用 ITestOutputHelper - 在测试输出中查看 Actor 系统日志
- 检查 TestProbe - 查看
probe.Messages,了解发送了哪些内容 - 查询 Actor 状态 - 添加状态查询消息以便调试
- 使用带日志的 AwaitAssertAsync - 查看断言失败的原因
- 检查 ActorRegistry - 验证 Actor 是否已正确注册
// Constructor with debug logging
public OrderActorTests(ITestOutputHelper output)
: base(output: output, logLevel: LogLevel.Debug)
{
}