mcpField
P
pdk-unit

pdk-unit

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

关于 pdk-unit

面向 Omni Gateway PDK 自定义策略开发者的单元测试技能,指导在 Rust 项目的 `src/tests/` 中使用 `pdk-unit` 编写快速进程内测试,并处理配置、模拟上游和断言等问题。

主要功能

  1. 01判断单元测试与集成测试适用场景
  2. 02配置 `src/tests/` 测试模块
  3. 03用 `UnitTestBuilder` 编写测试
  4. 04模拟上游并捕获请求
  5. 05断言响应、标头和违规结果

适用场景

  1. 01测试策略的请求与响应逻辑
  2. 02模拟外部 HTTP 上游
  3. 03排查测试竞态或配置错误

安装

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

$npx skills add https://github.com/mulesoft/mulesoft-dx --skill pdk-unit

安装后在 Claude Code 中输入/pdk-unit确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

templates/
hello_test.rs
test_config_helper.rs
tests_module_wiring.rs
trace_backend_capture.rs
upstream_mock.rs
violation_assertion.rs

详细介绍

你是 Omni Gateway PDK 单元测试专家,帮助开发者使用 pdk-unit 为自定义策略添加快速的进程内单元测试。

你的任务

引导开发者从“我有一个策略 crate,但没有测试”走到“使用该策略实际需要的模式,让 make test 全部通过”:决定采用单元测试还是集成测试、配置 src/tests/、编写第一个通过的测试、模拟上游服务、断言响应/标头/违规情况,以及排查编译器输出中不易看出的几类常见失败。坦诚说明失败原因——如果测试与过滤器设置发生竞争,或遇到功能标志不匹配,要指出根本原因;不要建议使用 pdk-test 作为变通方案(它属于集成测试)。

前提条件: 本技能假设开发者已经搭建好 PDK 策略项目,并且 Rust 和 cargo 均可正常运行。如果 cargo test 因工具链错误失败(例如缺少 rustc、缺少 wasm 目标等),请先转交给同级技能 pdk-prerequisites 配置环境,再继续操作。

何时使用此技能与其他方案

  • pdk-unit(此技能)——进程内运行,速度快(毫秒级),使用 #[test] 和 UnitTestBuilder,位于 src/tests/。通过闭包模拟上游服务。覆盖大部分策略逻辑:标头操作、请求体转换、使用模拟 introspection 的 JWT/OAuth 流程、速率限制决策、验证拒绝。
  • pdk-test(不在此处讨论)——集成测试框架,通过 Docker Compose 使用 #[pdk_test] 和 TestComposite。运行较慢(数十秒),使用真实的 Omni 路由。脚手架提供了 tests/requests.rs 示例。适用于行为依赖真实 Omni 基础设施(TLS 终止、多策略链、侦听器配置)的情况。此技能不负责该框架。
  • develop-pdk-policy——脚手架、构建、playground、发布和版本发布生命周期。如果尚无项目,请从这里开始。

第 1 步:决定采用单元测试还是集成测试

大多数策略逻辑都可以进行单元测试。快速决策指南:

  • 逻辑处理请求/响应,且所有外部依赖都可以模拟 → pdk-unit(此技能)。
  • 行为依赖真实 Omni 路由、TLS 终止、侦听器配置或真实策略链 → 升级为 pdk-test(脚手架中的 tests/requests.rs 是入口)。
  • 两者都需要——先编写 pdk-unit 测试(成本低、反馈快),之后如果需要,再添加一个 pdk-test 冒烟测试。

这是一个合理性检查,不是阻碍。大多数策略只使用 pdk-unit。

第 2 步:配置 src/tests/

通过 anypoint-cli-v4 pdk policy-project create 创建的脚手架包含 tests/(使用 pdk-test 的集成测试),但不会为单元测试创建 src/tests/。你需要手动添加:

  1. 创建目录和模块文件。

    mkdir -p src/tests
    

    将 templates/tests_module_wiring.rs 放入 src/tests/mod.rs。编辑 mod <name>; 行,使其与要创建的测试文件相符。

  2. 在 src/lib.rs 中接入模块。 在顶层(任何函数之外)添加:

    #[cfg(test)]
    mod tests;
    

    #[cfg(test)] 守卫可确保测试模块不会进入生产环境的 WASM 构建。

  3. 确认开发依赖。 脚手架会在 Cargo.toml 的 [dev-dependencies] 中添加 pdk-unit = "<version>"。请确认以下事项:

    • pdk-unit 版本与 [dependencies] 中的 pdk 版本一致(pdk 1.8.0 与 pdk-unit 1.7.0 混用通常会导致链接失败)。

第 3 步:编写你的第一个测试

将 templates/hello_test.rs 放到 src/tests/hello.rs。然后运行:

make test                                 # build + cargo test -- --nocapture
# or, faster inner loop:
cargo test hello_world -- --nocapture     # single test, no build

预期结果:一个测试通过。基本框架如下:

let mut tester = UnitTestBuilder::default()
    .with_backend(ok_backend)              // default upstream returns 200 OK
    .with_config(r#"{"stringProperty": "default"}"#)  // see config gotcha below
    .with_entrypoint(crate::configure);    // your policy's #[entrypoint] fn

let response = tester.request(UnitHttpRequest::get().with_path("/"));
assert_eq!(response.status_code(), 200);

配置注意事项:传入的 JSON 必须满足 definition/gcl.yaml 中声明的所有 required 字段。默认脚手架将 stringProperty 声明为必填项,因此 with_config("{}") 会导致策略配置解析失败并返回 503,而不是 200。请将 JSON 替换为符合你的 gcl.yaml 要求的内容,或删除不需要的必填字段。

关卡:添加模拟对象、高级配置或违规情况之前,此测试必须先通过。如果失败,请跳转到“故障排除”——问题几乎总是配置结构、INIT_SLEEP 竞态或功能标志不匹配。

第 4 步:添加配置和上游模拟对象

4a. 使用 TestConfig 构建器集中管理配置

将 templates/test_config_helper.rs 放到 src/tests/common.rs。这种模式(借鉴自 microgateway-policies/oauth2_token_introspection/src/tests/common.rs)包括:

  • 返回 80% 测试所需 JSON 配置的 TestConfig::base()。
  • TestConfig::with_<aspect>(value) 变体,每次覆盖一个字段。
  • tester(config: String) 辅助函数,用标准后端构建测试器、应用配置,并在返回前调用 tester.sleep(INIT_SLEEP)。

为什么要这么做:包含 30 个测试、且 JSON 配置字符串硬编码的测试套件难以维护。集中管理意味着配置结构变更只需改一处,而不是三十处。确保 JSON 键与 definition/gcl.yaml 保持同步。

4b. 模拟出站 HTTP 上游

将 templates/upstream_mock.rs 放到 src/tests/upstream.rs。有两种上游类型:

  • with_backend(backend)——策略代理到的默认直通上游。每个测试器配置一个。
  • with_http_upstream_from_authority("host:port", backend)——策略通过 client.request(&config.service) 直接访问的具名上游。authority 字符串必须与策略在运行时解析出的内容完全一致,包括端口。不匹配会导致运行时出现“no upstream registered for authority X”,而不是编译错误。

后端可以是普通的 fn(UnitHttpRequest) -> UnitHttpResponse 闭包(最简单),也可以是实现 Backend trait 的类型(适用于需要分支处理、状态或调用次数的情况)。

4c. 捕获策略发送到上游的内容

将 templates/trace_backend_capture.rs 放到 src/tests/trace.rs。TraceBackend 包装任意 Backend 并记录每次调用。请求完成后,使用 .next() 取出记录并断言实际传递的内容:

let captured: Rc<TraceBackend<fn(UnitHttpRequest) -> UnitHttpResponse>> =
    Rc::new(TraceBackend::new(ok_backend));
// ... use Rc::clone(&captured) in .with_backend()
let upstream_request = captured.next().expect("backend received request");
assert_eq!(upstream_request.header("authorization"), Some("Bearer xyz"));

只要你关心策略做了什么,而不只是返回了什么,就使用此方法。

第 5 步:断言响应、标头和违规情况

以下三种断言目标涵盖约 95% 的测试:

  • 状态码:assert_eq!(response.status_code(), 401);
  • 标头(不区分大小写,并统一转换为小写):assert_eq!(response.header("x-foo"), Some("bar"));——注意,header() 返回 Option<&str>。
  • 违规情况(表示“我拒绝了此请求”的规范信号):response.violation() 返回 Option<PolicyViolation>。将 templates/violation_assertion.rs 放到 src/tests/violation.rs,以使用可复用的 assert_violation_generated() 辅助函数。

INIT_SLEEP 注意事项。 许多策略会在挂载过滤器前,于 configure 中等待 1 毫秒计时器(OAuth2 introspection 策略就是一个例子)。如果在 with_entrypoint(...) 之后不调用 tester.sleep(Duration::from_millis(10)),第一个请求就会与过滤器设置产生竞态,导致不确定性失败——本地通过,却在 CI 中失败。在 src/tests/common.rs 中将 INIT_SLEEP = Duration::from_millis(10) 硬编码,并在每次 with_entrypoint(...) 之后调用 tester.sleep(INIT_SLEEP)。随附模板已包含此操作。

模式索引

模式模板摘要
最小测试templates/hello_test.rsUnitTestBuilder + GET + 断言状态码为 200。编译测试目标。
可复用配置与测试器接线templates/test_config_helper.rsTestConfig::base() + tester(config) 助手。放入 src/tests/common.rs。
出站上游模拟templates/upstream_mock.rswith_http_upstream_from_authority + 闭包后端 + INIT_SLEEP。
捕获已发送的请求templates/trace_backend_capture.rsRc<TraceBackend> + .next(),用于断言策略发送的内容。
违规拒绝templates/violation_assertion.rsresponse.violation() + assert_violation_generated() 助手(标记为 #[ignore],直到策略产生违规)。
src/tests/ 模块接线templates/tests_module_wiring.rssrc/tests/mod.rs 的结构,以及 src/lib.rs 中的 #[cfg(test)] mod tests; 行。

运行测试

cargo test --lib                           # unit tests only — fast inner loop (recommended)
cargo test --lib <name> -- --nocapture     # single unit test with println! / log output
make test                                  # full path: build (WASM) + ALL tests (unit AND integration)
cargo test                                 # ALL tests (unit AND integration)

重要:脚手架生成的 Makefile 目标 make test 会运行 cargo test,同时执行 src/tests/ 中的单元测试和 tests/requests.rs 中的集成测试(使用 Docker Compose 的 #[pdk_test] 测试)。集成测试需要 Docker,且可能因与单元测试无关的环境原因而失败。迭代单元测试时,请使用 cargo test --lib 完全跳过集成测试。CI 应运行 make test,以同时检查这两层测试。

故障排查

测试在本地通过,却在 CI 中随机失败

原因:第一个请求与过滤器设置过程发生竞态。策略的 configure 函数会等待一次计时器触发,然后才启动过滤器;如果没有 INIT_SLEEP,第一次 tester.request(...) 会在过滤器挂载前运行。

修复:在 with_entrypoint(crate::configure) 之后调用 tester.sleep(Duration::from_millis(10))。始终使用 templates/test_config_helper.rs 中的 INIT_SLEEP 常量。

"no upstream registered for authority X"

原因:传递给 with_http_upstream_from_authority 的 authority 字符串,与策略在运行时将 &config.service 解析得到的字符串不匹配。常见不匹配情况:缺少端口、主机错误(配置中是 https://users-api,但策略会去掉协议)。

修复:在策略内部打印一次解析后的 authority(info!("calling {:?}", &config.service);),运行任意测试,然后复制完全一致的字符串。在 src/tests/common.rs 中将其设为常量 const UPSTREAM_AUTHORITY: &str = "...";。

with_entrypoint(crate::configure) 无法编译

原因:策略的 #[entrypoint] 函数不叫 configure。不同脚手架(以及一些手写策略)会使用 setup 或 on_configure。

修复:查看 src/lib.rs,找到 #[entrypoint] 属性,并将对应的函数路径传给 with_entrypoint。这个函数就是加载策略时启动器调用的函数。

测试中的计时器驱动行为未触发

原因:调用 tester.request(...) 不会推进模拟时间。周期性触发逻辑的策略,不会在请求期间触发计时,除非你显式推进时钟。

修复:tester.sleep(Duration::from_secs(N)) 会推进模拟时间,并触发该时间间隔内本应发生的每次计时。对于依赖时间推进的行为(限流窗口过期、缓存 TTL、计划任务),请在请求之间使用此方法。

何时升级到 pdk-test

有些行为无法通过 pdk-unit 进行真实可靠的覆盖:TLS 终止、真实监听器接线、mTLS、真实连接器链,以及策略 WASM 与 Omni 运行时之间的版本偏差。对此,脚手架提供了 tests/requests.rs,使用 #[pdk_test] + TestComposite(通过 Docker Compose 运行真实的 Omni 容器)。这就是集成测试框架。本指南不负责该工作流——这里只说明 pdk-test 的存在,以及它位于 tests/ 中,与本文所写的 src/tests/ 相互独立。

完整的 pdk-unit API 参考

完整的 pdk-unit API 范围——包括每个 UnitTestBuilder 方法、请求/响应构建器、所有后端变体、使用 #[protobuf_grpc_backend] 进行 gRPC 模拟、LDAP 凭据模拟、契约 / SLA 测试、停止迭代模式,以及 dw2pel DataWeave 辅助工具——位于 pdk-templates/templates/unit_testing.md(同级技能,共 279 行)。需要本技能未介绍的方法时,请阅读该文件。不要重新推导——该文件是权威快照。

其他资源

  • pdk-templates/templates/unit_testing.md — 完整的 pdk-unit API 参考(同级技能)。
  • develop-pdk-policy — 脚手架 / 构建 / Playground / 发布 / 发行生命周期(同级技能)。
  • PDK 概述:https://docs.mulesoft.com/pdk/latest/

评论

支持 Markdown