关于 rust-api-test-harness
这项技能帮助 Rust 开发者通过公开 HTTP 行为,为 API 编写可复现、适用于 CI 的测试。内容涉及测试启动结构、依赖隔离,以及成功和失败场景的验证。
主要功能
- 01黑盒 HTTP 集成测试
- 02可复用的应用启动与测试夹具
- 03数据库和外部 API 隔离
- 04错误与副作用断言
- 05Cargo 预检验证
适用场景
- 01为 Rust 服务添加 HTTP 集成测试
- 02隔离状态以测试数据库行为
- 03模拟第三方 HTTP 依赖
- 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.yamlreferences/database-test-isolation.mdexternal-http-mocks.mdscripts/check-rust-service.sh详细介绍
Rust API 测试框架
使用此技能以测试优先、可复现且适用于 CI 的方式开发 Rust API。 优先编写黑盒测试,通过 HTTP 调用服务并验证可观察到的行为,而不是深入访问框架内部实现。
核心工作流
- 首先检查项目:
Cargo.toml、工作区布局、现有的src/main.rs、src/lib.rs、路由器/启动模块、tests/、迁移文件、Docker Compose 和 CI 工作流。 - 找到应用边界。如有需要,将启动代码提取为可复用函数,以便测试可以绑定随机本地端口并传入显式状态。
- 在更改行为之前,先编写或更新集成测试。除非项目已有明确约定,否则将 API 测试放在
tests/下。 - 创建
spawn_app辅助函数,为每个测试用例启动一次服务,捕获基础 URL,并管理所有临时测试资源。 - 使用
reqwest测试公开的 HTTP 契约,而非特定于框架的请求对象,除非项目有意采用进程内测试。 - 隔离状态。当行为取决于依赖项的语义时,使用真实依赖:SQL 行为使用 Postgres,外部 API 使用模拟 HTTP 服务,并为每项资源使用唯一名称。
- 将测试辅助代码写得达到生产级质量。为失败提供有用的信息,用简短的辅助方法隐藏样板代码,并在可以观察就绪状态时避免使用 sleep。
- 完成前运行本地验证脚本或等效的 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 工作流的模式。