mcpField
R
rust-api-test-harness

rust-api-test-harness

github.com/lvtd-llc测试与质量163 次安装7 周前更新
代码仓库

关于 rust-api-test-harness

这项技能帮助 Rust 开发者通过公开 HTTP 行为,为 API 编写可复现、适用于 CI 的测试。内容涉及测试启动结构、依赖隔离,以及成功和失败场景的验证。

主要功能

  1. 01黑盒 HTTP 集成测试
  2. 02可复用的应用启动与测试夹具
  3. 03数据库和外部 API 隔离
  4. 04错误与副作用断言
  5. 05Cargo 预检验证

适用场景

  1. 01为 Rust 服务添加 HTTP 集成测试
  2. 02隔离状态以测试数据库行为
  3. 03模拟第三方 HTTP 依赖
  4. 04检查 API 错误与重试相关结果

安装

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

$npx skills add https://github.com/lvtd-llc/skills --skill rust-api-test-harness

安装后在 Claude Code 中输入/rust-api-test-harness确认已加载。

包含的文件

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

agents/
openai.yaml
references/
database-test-isolation.md
external-http-mocks.md
scripts/
check-rust-service.sh

详细介绍

Rust API 测试框架

使用此技能以测试优先、可复现且适用于 CI 的方式开发 Rust API。 优先编写黑盒测试,通过 HTTP 调用服务并验证可观察到的行为,而不是深入访问框架内部实现。

核心工作流

  1. 首先检查项目:Cargo.toml、工作区布局、现有的 src/main.rs、 src/lib.rs、路由器/启动模块、tests/、迁移文件、Docker Compose 和 CI 工作流。
  2. 找到应用边界。如有需要,将启动代码提取为可复用函数,以便测试可以绑定随机本地端口并传入显式状态。
  3. 在更改行为之前,先编写或更新集成测试。除非项目已有明确约定,否则将 API 测试放在 tests/ 下。
  4. 创建 spawn_app 辅助函数,为每个测试用例启动一次服务,捕获基础 URL,并管理所有临时测试资源。
  5. 使用 reqwest 测试公开的 HTTP 契约,而非特定于框架的请求对象,除非项目有意采用进程内测试。
  6. 隔离状态。当行为取决于依赖项的语义时,使用真实依赖:SQL 行为使用 Postgres,外部 API 使用模拟 HTTP 服务,并为每项资源使用唯一名称。
  7. 将测试辅助代码写得达到生产级质量。为失败提供有用的信息,用简短的辅助方法隐藏样板代码,并在可以观察就绪状态时避免使用 sleep。
  8. 完成前运行本地验证脚本或等效的 cargo 命令。

启动结构

优先使用接收基础设施依赖的启动函数,而不是在处理程序模块内部创建依赖。Actix 可以监听同步的 std::net::TcpListener:

pub fn run(
    listener: std::net::TcpListener,
    app_state: AppState,
) -> Result<actix_web::dev::Server, std::io::Error> {
    let state = actix_web::web::Data::new(app_state);
    let server = actix_web::HttpServer::new(move || {
        actix_web::App::new()
            .app_data(state.clone())
            .route("/health_check", actix_web::web::get().to(health_check))
    })
    .listen(listener)?
    .run();

    Ok(server)
}

对于 Axum,优先采用相同的边界,但使用 Tokio 的异步监听器类型:

pub fn router(app_state: AppState) -> axum::Router {
    axum::Router::new()
        .route("/health_check", axum::routing::get(health_check))
        .with_state(app_state)
}

pub async fn spawn_axum_app() -> TestApp {
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
        .await
        .expect("failed to bind random port");
    let port = listener.local_addr().unwrap().port();
    let server = axum::serve(listener, router(test_state().await));
    let server_handle = tokio::spawn(async move { server.await });

    TestApp {
        address: format!("http://127.0.0.1:{port}"),
        http_client: reqwest::Client::new(),
        server_handle,
    }
}

测试框架模式

使用辅助对象,避免在每个测试中重复设置步骤。

pub struct TestApp {
    pub address: String,
    pub http_client: reqwest::Client,
    pub server_handle: tokio::task::JoinHandle<std::io::Result<()>>,
}

pub async fn spawn_app() -> TestApp {
    let listener = std::net::TcpListener::bind("127.0.0.1:0")
        .expect("failed to bind random port");
    let port = listener.local_addr().unwrap().port();

    let server = my_app::startup::run(listener, test_state().await)
        .expect("failed to start test server");
    let server_handle = tokio::spawn(server);

    TestApp {
        address: format!("http://127.0.0.1:{port}"),
        http_client: reqwest::Client::new(),
        server_handle,
    }
}

当多个测试中出现多步骤用户流程时,添加简短的辅助方法。方法名应体现行为,例如 post_subscription、login、create_newsletter 或 confirm_subscription。

不要丢弃 tokio::spawn 返回的 JoinHandle。将它保存在测试夹具中,这样测试就能检测到意外结束的服务任务,并在清理时中止它,而不是让服务端 panic 被后续的“连接被拒绝”错误掩盖。

断言

  • 首先断言状态码和响应头。
  • 尽可能通过公开 API 断言持久化的副作用;只有在公开 API 尚无可观察途径时,才直接查询数据库。
  • 将负面情况作为一等行为进行断言:缺少字段、格式错误的负载、重复提交、未授权用户、上游故障和超时。
  • 除非 API 契约确实包含那些精确字节,否则不要对嘈杂的响应正文创建快照。

状态与依赖隔离

如果测试涉及数据库,请阅读 references/database-test-isolation.md。 如果测试调用第三方 HTTP API,请阅读 references/external-http-mocks.md。

默认偏好:

  • 对于由 Postgres 提供支持的行为,使用真实 Postgres,而非 SQLite 或内存替代方案。
  • 当测试可能写入共享状态时,为每个测试使用一个逻辑数据库、模式或唯一命名空间。
  • 在应用启动前,于设置阶段运行迁移。
  • 对外部 HTTP API 使用 wiremock 或等效的本地模拟服务。
  • 在测试中积极配置超时,避免失败导致测试挂起。

验证

适用标准 cargo 工作流时,请在 Rust 项目根目录运行捆绑的预检脚本:

path/to/rust-api-test-harness/scripts/check-rust-service.sh

默认情况下,脚本会运行:

cargo fmt --all --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features

设置 RUST_API_TEST_HARNESS_SKIP_CLIPPY=1 可仅跳过 clippy 步骤。脚本仍会运行格式检查和测试。当项目在 CI 中单独配置了 lint,或已知 --all-targets --all-features 不适用于本地预检时,可使用此绕过选项。

如果项目在 CI 中不会使用所有 features,请改为参照仓库现有的 CI 命令,并说明这一差异。

参考文件

  • references/database-test-isolation.md:Postgres 支持的测试、迁移、每个测试使用独立数据库以及清理方面的权衡模式。
  • references/external-http-mocks.md:使用本地 mock 测试出站 HTTP 客户端和 API 工作流的模式。

评论

支持 Markdown