Skip to content
bitzorcas
中EN

Reference

测试夹具与种子数据

选择纯对象、WebApplicationFactory、Testcontainers 和模块自有 CSV 种子,并建立确定、隔离、可重放的测试数据。

Last updated

测试夹具负责建立边界,种子数据负责建立可重复的初始事实。不要让所有测试共享一个“万能数据库”:纯领域规则使用对象工厂,HTTP 管线使用 WebApplicationFactory,真实持久化与消息语义使用 Testcontainers,平台初始化才使用模块自有的 ISeedStep。

夹具选择矩阵

要验证的事实推荐夹具不推荐
聚合状态转换直接构造对象/测试 Builder启动 Host
Handler 与纯应用策略明确的 fake port、固定时钟Mock 私有方法
中间件、认证、Problem DetailsTestApiFactory / WebApplicationFactory只调用 Handler
SQL 翻译、事务、并发Testcontainers SQL ServerEF InMemory
CAP/Outbox 与 brokerSQL Server + RabbitMQ 容器内存事件列表
ORM parity同一合同分别驱动 SqlSugar/EF Core比较生成 SQL 字符串
包消费临时目录 + 隔离 NuGet Feed/cache产品仓库 ProjectReference

测试类型决定成本与证据,不是“集成测试越多越好”。在最低成本层枚举分支,在高层保留跨边界的关键路径。

当前集成夹具形态

无 Docker API 测试通过 TestApiFactory 启动 Shell 组合;Golden Use Case 的 GoldenUseCaseApiFactory 同时启动 SQL Server、RabbitMQ 与真实 API Host。Store、Repository、Website、CAP、审计和 Seeder 合同通常直接实现 IAsyncLifetime 管理容器。

tests/BitzOrcas.Integration.Tests/AssemblyInfo.cs 关闭测试并行,避免多个容器竞争 Docker 资源产生瞬态失败。不要在单个类中重新开启并行来追求表面速度。

Terminal window
# 无 Docker:验证 API Shell 与跨层安全降级。
dotnet test tests/BitzOrcas.Integration.Tests \
--configuration Release --filter 'Category!=Docker'
# 有 Docker:只运行 Seeder 合同,避免启动无关容器。
dotnet test tests/BitzOrcas.Integration.Tests \
--configuration Release \
--filter 'Category=Docker&FullyQualifiedName~Seeders.'

种子资产归属

种子 CSV 跟随 owner 模块的 Infrastructure 程序集。当前资产分布在 Identity、MasterData、Authorization、Menu、PlatformBilling、Catalog、Numbering 与少量 Framework 兼容位置;不能再用一个集中 SqlSugar 目录表示全部事实。

Terminal window
# 物理资产清单;执行集合仍取决于目标 Host 的 DI 组合。
find src/Platform src/Framework \
-path '*/Seeders/Assets/*.csv' -print | sort
# 查看步骤身份、顺序、范围和显式依赖。
rg -n 'SeedId =>|Order =>|SeedScope =>|DependsOn =>' \
src/Platform src/Framework -g '*SeedStep.cs'

文件名前缀用于阅读和对齐 Order,但 SeedRunner 最终以已注册 ISeedStep.Order 调度。SeedId 或 Order 重复、DependsOn 缺失都会在执行前 fail loud。

ORM 中立种子基类

标准实现是 EntitySetCsvSeedStepBase<TEntity>,不是旧文档中的 CsvSeedStepBase<T>。它从步骤所属程序集读取嵌入 CSV,通过 IEntitySet<TEntity> 按稳定业务键查询,并新增或复制 owner 管理字段。

public sealed class TestCatalogSeedStep(
IEntitySet<TestCatalogRecord> rows,
CsvSeedReader reader,
ILogger<TestCatalogSeedStep> logger)
: EntitySetCsvSeedStepBase<TestCatalogRecord>(rows, reader, logger)
{
public override int Order => 960;
public override string SeedId => "test_catalog";
public override SeedScope SeedScope => SeedScope.Demo;
protected override string CsvFileName => "960-test_catalog.csv";
// Code 是跨重复运行稳定的业务键,数据库生成 Id 不能承担匹配职责。
protected override string[] WhereColumns => [nameof(TestCatalogRecord.Code)];
protected override Expression<Func<TestCatalogRecord, bool>> Match(TestCatalogRecord source)
=> row => row.Code == source.Code;
protected override void Copy(TestCatalogRecord source, TestCatalogRecord target)
{
// 只复制 CSV 拥有的字段;保留持久 Id、租户和运行时审计字段。
target.Name = source.Name;
target.IsEnabled = source.IsEnabled;
}
}

示例 Order 必须在实际目标组合中重新检查全局唯一性。业务模块应选择自己的区间并通过架构/集成测试守护,而不是照抄 960。

Scope 与环境

ScopeDevelopment/DemoStaging/Production用途
Global执行执行语言、币种、国家/地区等平台级基础数据
Tenant执行执行默认租户组织、设置覆盖等租户级数据
ProductionSafe执行执行必需平台定义、主数据
Demo执行自动跳过示例目录、非生产账号/内容

BitzOrcasSeedOptions 还可以整体禁用 Runner、设置 SkipSeedIds 或配置 CSV override root。测试若使用 override,必须把覆盖目录放在临时空间,并在结束时删除;生产测试证据不能依赖开发机上的未提交 CSV。

确定性数据规则

  • 使用测试自有 TenantId、业务键和时间戳,不依赖另一个用例先运行;
  • 时间从 IAppClock 或显式参数进入,断言不要读取瞬时 UtcNow;
  • 随机数据必须记录 seed,失败可重放;
  • 每个测试创建并清理自己的数据库事实,或在事务/独立容器中隔离;
  • 断言业务键和可观察结果,不依赖数据库自增值的具体数字;
  • 并发测试用同步屏障制造冲突,不用 Task.Delay 猜时序;
  • CSV 不包含真实密码、Token、License、客户数据或本机绝对路径。

Testcontainers 生命周期

public sealed class CatalogSqlContractTests : IAsyncLifetime
{
private readonly MsSqlContainer database = new MsSqlBuilder().Build();
public async Task InitializeAsync()
{
// StartAsync 完成后仍执行查询级 readiness,避免“端口已开但 SQL 未就绪”。
await database.StartAsync();
await MsSqlContainerReadiness.WaitForQueryAsync(database.GetConnectionString());
}
// 即使断言失败,xUnit 仍通过异步释放回收容器资源。
public Task DisposeAsync() => database.DisposeAsync().AsTask();
}

发生失败时保留容器日志、连接就绪阶段和首个业务断言。不要只把 Testcontainers 超时扩大到几十分钟;先区分镜像拉取、daemon、资源、readiness 与 schema 问题。

幂等与部分失败测试

一个种子步骤至少验证:空库首次执行、重复执行、CSV 管理字段更新、业务键重复被拒绝、缺失依赖被拒绝、Demo 在生产环境跳过。有关联父子步骤时,还要验证 DependsOn 和外键顺序。

“执行两次没有异常”不够:第二次后行数不增加,owner 管理字段收敛,运行时字段不被覆盖,报告状态和退出码都应正确。

夹具评审清单

  • 夹具层级与要证明的事实匹配;
  • Docker 用例标记 Category=Docker 并进入正确 CI shard;
  • 测试不依赖顺序、固定端口、共享用户或历史数据库;
  • 容器有 readiness、取消和确定清理;
  • SeedId/Order/DependsOn 在目标组合中唯一且完整;
  • Global/Tenant/Demo/ProductionSafe 范围符合数据性质;
  • 重放、部分失败和并发路径有断言;
  • 日志与失败消息不暴露连接串或 Secret。

继续阅读演示数据与种子参考、集成测试和运行测试。

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%