使用 Docker 进行 Rust 集成测试:高级水平

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

使用 Docker 进行 Rust 集成测试简介

\n

集成测试是使用 Rust 开发可靠服务器应用程序的关键阶段。与单元测试不同,集成测试检查组件与真实依赖项(如数据库、缓存、外部 API)的交互。使用 Docker 可以隔离测试环境并确保可重复性。在本文中,我们将分析高级技术:并行启动容器、状态管理、模拟服务以及通过 Tokio 处理异步操作。

\n\n

测试环境配置:Docker 和 testcontainers

\n

对于 Rust 中的集成测试,使用 testcontainers crate 很方便。它允许以编程方式启动 Docker 容器,等待它们准备就绪,并正确清理资源。我们从基本配置开始。

\n

安装依赖项

\n

添加到 Cargo.toml:

\n
[dev-dependencies]\ntestcontainers = "0.21"\ntokio = { version = "1", features = ["full"] }\nreqwest = { version = "0.12", features = ["json"] }\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"
\n

基本示例:PostgreSQL 容器

\n

让我们创建一个测试,启动 PostgreSQL,执行迁移并检查数据插入:

\n
use testcontainers::{clients, images::postgres::Postgres, Container};\nuse tokio_postgres::{NoTls, Client};\n\nasync fn setup_db() -> (Client, Container<'static, Postgres>) {\n    let docker = clients::Cli::default();\n    let pg_image = Postgres::default();\n    let container = docker.run(pg_image);\n    let host = container.get_host();\n    let port = container.get_host_port_ipv4(5432);\n    let connection_string = format!(\n        "host={host} port={port} user=postgres dbname=postgres",\n    );\n    let (client, connection) = tokio_postgres::connect(&connection_string, NoTls)\n        .await\n        .expect("Failed to connect");\n    tokio::spawn(async move {\n        if let Err(e) = connection.await {\n            eprintln!("Connection error: {e}");\n        }\n    });\n    (client, container)\n}\n\n#[tokio::test]\nasync fn test_insert_user() {\n    let (client, _container) = setup_db().await;\n    client\n        .simple_query("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT)")\n        .await\n        .unwrap();\n    client\n        .execute("INSERT INTO users (name) VALUES ($1)", &[&"Alice"])\n        .await\n        .unwrap();\n    let rows = client.query("SELECT name FROM users", &[]).await.unwrap();\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0].get::<_, String>(0), "Alice");\n}
\n

由于 Container 的析构函数,容器在测试完成后会自动删除。这省去了手动清理的麻烦。

\n\n

并行运行测试和资源管理

\n

默认情况下,Rust 在并行线程中执行测试。如果每个测试都启动自己的容器,可能会导致端口冲突和 Docker 过载。让我们考虑两种方法。

\n

通过动态分配使用不同端口

\n

testcontainers crate 会自动在主机上分配空闲端口。这解决了冲突问题,但增加了资源消耗。对于重量级容器(例如数据库),最好使用共享实例:

\n
use once_cell::sync::Lazy;\nuse testcontainers::clients;\n\nstatic DOCKER: Lazy<clients::Cli> = Lazy::new(|| clients::Cli::default());\n\nasync fn get_shared_postgres() -> (tokio_postgres::Client, Container<'static, Postgres>) {\n    let pg_image = Postgres::default();\n    let container = DOCKER.run(pg_image);\n    // ... 连接方式同前一个示例\n    (client, container)\n}
\n

然而,这种方法有风险:测试可能会通过共享状态相互影响。建议在单个容器内使用单独的数据库,或者重置状态

博客

书籍推荐