Sparkdo.Messaging.Testing 3.0.0-preview.1

This is a prerelease version of Sparkdo.Messaging.Testing.
dotnet add package Sparkdo.Messaging.Testing --version 3.0.0-preview.1
                    
NuGet\Install-Package Sparkdo.Messaging.Testing -Version 3.0.0-preview.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sparkdo.Messaging.Testing" Version="3.0.0-preview.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Sparkdo.Messaging.Testing" Version="3.0.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="Sparkdo.Messaging.Testing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Sparkdo.Messaging.Testing --version 3.0.0-preview.1
                    
#r "nuget: Sparkdo.Messaging.Testing, 3.0.0-preview.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Sparkdo.Messaging.Testing@3.0.0-preview.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Sparkdo.Messaging.Testing&version=3.0.0-preview.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Sparkdo.Messaging.Testing&version=3.0.0-preview.1&prerelease
                    
Install as a Cake Tool

Sparkdo.Messaging.Testing

传输契约测试基座:12 条可复用用例 + 一个运行器。任何传输实现跑过它才算出证—— "我实现了 ITransportPort"不是证据,"这 12 条在我身上真跑过"才是。

项 值
包 Sparkdo.Messaging.Testing
交付框架 net11.0
外部 NuGet 依赖 无
谁引用 只该被测试工程引用,不进生产依赖图
公开面 ITransportFactory、TransportConformance、TransportConformanceRunner、ConformanceCase/Context/Result/Report/Outcome

三步出证

internal sealed class MyTransportFactory : ITransportFactory
{
    public string Name => "my-transport";

    public TransportCapability Capabilities => TransportCapability.Durable;      // 与传输自报一致

    public ValueTask<ITransportPort> CreateAsync(CancellationToken ct)
        => ValueTask.FromResult<ITransportPort>(new MyTransport(new MyOptions { ... }));
}

var report = await TransportConformanceRunner.RunAsync(
    new MyTransportFactory(), TransportConformance.Cases);

Assert.True(report.Passed, report.Describe());

传输合法地缺某项能力时(例如进程内传输没有持久化),上面那条断言本来就是 false。 这时出证要写死该跳过哪几条,而不是断言"没有失败":

Assert.Equal(["durable-survives-restart"], report.SkippedCaseIds);
Assert.True(report.FailedCaseIds.Count == 0, report.Describe());

写死而不是从能力声明派生:派生出来的期望值会让"传输悄悄少声明一项能力"自证清白。

ITransportFactory 的三个约定

成员 约定
Name 进报告的标识
Capabilities 要与 ITransportPort.Capabilities 一致。基座按它决定跑或跳;两边不一致就是少验或多吹
CreateAsync(ct) 创建一个尚未启动的实例。运行器自己调 StartAsync,每条用例一个新实例

运行器的行为(写传输前要知道)

事 口径
每条用例新建一个传输实例 共享实例会让一条用例的残留污染下一条,而这种失败最难归因
只调 StartAsync,不调 InitializeAsync 也就是说契约轮里没有 TransportContext(宿主的服务提供者)。别把"必须的依赖"全押在 InitializeAsync 上——配置校验要在 StartAsync 也能跑一遍(RabbitMQTransport / PostgresTransport 就是两处各调一次 Validate())
用例地址 运行器把 ConformanceContext.Address 填成 {transport.Scheme}://conformance/{caseId}。今天没有用例用它——每条用例自己按标准文法造发布与订阅地址。留这一格是给"需要一条现成唯一地址"的新用例,不是要传输认得 conformance 这一段
没声明能力就跳过,且不执行 能力是声明不是证据:声明了才验,验不过算失败。跳过原因串是 skipped-conformance-case:{Id}:missing-{Required}
用例异常收敛成失败,不中断整轮 一条炸掉不该让后面没有结论——出证报告要的是全集
默认超时 30 秒 RunAsync(factory, cases, timeout?, ct) 的 timeout 覆盖它。超时后先取消再给 1 秒收尾窗口(unwind grace),仍未结束就判失败
每条用例结束 DisposeAsync 传输实例 传输的异步释放要真的能散——IAsyncDisposable 在这条路上被问责

12 条用例

用例 Id 问的是什么 需要的能力
settlement-exactly-once 同一租约第二次结算必须失败 —
backpressure-blocks-when-full 无人消费时写入最终会阻塞(只看有没有界;已故障/已取消不算通过) Backpressure
stop-rejects-traffic 停止后发送返回未受理(停止是终态) —
fan-out-reaches-every-subscriber 三个不同名订阅者都收到同一条投递 —
publish-without-subscriber-accepted 无人订阅时发布仍被受理(不等于晚起的订阅收得到) —
payload-round-trips-byte-identical 载荷逐字节往返,不得"顺手规范化" —
headers-survive-the-round-trip 消息头原样带回(含 null 值条目、键的大小写) —
dead-letter-remains-readable 结算成死信后仍可从 {scheme}://dead/{身份}/{订阅者} 读回同一条(同 MessageId、同字节) —
delivery-count-advances-on-redelivery 重投后 DeliveryCount 递增 —
released-port-leaves-no-trace DisposeAsync 后读循环在宽限内散尽、同名重新打开取得到投递(放两次验幂等) —
durable-survives-restart 旧实例整个释放后新实例仍能取到未结算投递 Durable
fault-injection-degrades-explicitly 带已取消令牌的写入要么响亮拒绝、要么如实送达,之后一笔正常写入必须照常送达 —

受能力门管的只有两条:durable-survives-restart(Durable)与 backpressure-blocks-when-full(Backpressure),都由这个运行器问责;NativeSchedule 在基座里 没有用例,这条缺席被 CapabilityCaseTests 钉着。所以基座抓的是"声明了但跑不过", 抓不到"声明了却没实现"。

报告面

report.Passed            // 全部用例真跑且通过才为 true:任一条跳过即 false
report.Results           // 每条一个 ConformanceResult(CaseId, Outcome, Failure?, SkipReason?)
report.PassedCaseIds / FailedCaseIds / SkippedCaseIds
report.Describe()        // 给人读的全集,断言失败信息就带它
失效方式 防线
把"环境缺能力"读成"验过了" Passed 在有任何跳过时为 false;要合法跳过就写死 SkippedCaseIds
只有 xunit 层的绿 xunit 的 0 跳过 ≠ 契约轮的 0 跳过:按声明跳过的那条要在传输的用例里显式断言出来
用例写错了(把"至少一次"当"恰好一次") 才改用例,且必须在提交消息里写清理由。实现不合契约就修实现

加一条用例

用例类型是包内的 internal static class(Cases/ 下),注册在 TransportConformance.Cases 里, 一条一个 ConformanceCase(Id, Title, Required, Run)。加它意味着:

步 内容
1 想清这条问责的是端口契约的哪一句——不是某个 broker 的性质
2 定 Id(稳定,写进各传输的跳过断言里)与 Required
3 三个传输都要跑得过或按声明合法跳过;跑不过先改传输
4 加一条能力档的自检:CapabilityCaseTests 钉着"哪些用例受哪个能力门管"

出证记录

传输 读数
InMemory 11 过 + 1 跳(durable-survives-restart)
RabbitMQ 11 过 + 1 跳(backpressure-blocks-when-full),2026-10-19 全工程 82/82
Postgres 11 过 + 1 跳(backpressure-blocks-when-full:库里没有"无人消费就堵住写入"这回事),2026-10-19 全工程 20/20
dotnet test src/messaging/test/Sparkdo.Messaging.Testing.Tests          # 基座自身的用例
dotnet test src/messaging/test/Sparkdo.Messaging.Transport.InMemory.Tests

broker 两档要真服务(SPARKDO_MESSAGING_RABBITMQ / SPARKDO_MESSAGING_POSTGRES),没服务就跳过; 跳过不是通过,跑法与逐条读数见那两个传输工程的 README。

Product Compatible and additional computed target framework versions.
.NET net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0-preview.1 59 10/30/2026