Mockito多测试复用端口报错及BASE_URL配置优化咨询
Rust Mockito测试端口冲突解决与BASE_URL配置优化
1. 解决测试用例批量执行的端口冲突问题
问题根源
两个测试用例同时绑定固定端口8089,即使添加串行测试属性,也可能因端口释放不及时或Mockito内部资源未清理导致冲突。最彻底的解决方案是让Mockito自动分配随机可用端口,从根源避免端口争抢。
具体实现步骤
第一步:修改BASE_URL为动态获取的函数
将原有的编译期常量改为函数,测试环境下动态获取Mockito服务器的随机端口:
// 生产环境返回固定地址 #[cfg(not(test))] pub fn base_url() -> String { "http://localhost:8765".to_string() } // 测试环境返回Mockito自动分配的服务器地址 #[cfg(test)] pub fn base_url() -> String { format!("http://{}", mockito::server_address()) }
第二步:修改测试用例,使用随机端口创建Mock服务器
移除new_with_port(8089),改用Server::new()自动分配端口:
#[tokio::test] async fn existing_deck_new_note() { initialize(); // 自动分配随机端口,无需指定固定值 let mut server = mockito::Server::new(); let deck_exists_mock = get_deck_exists_mock(&mut server); let find_notes_mock = get_find_notes_mock(&mut server, EXISTING_DECK); let add_notes_mock = get_add_notes_mock(&mut server, EXISTING_DECK); let add_note = AddNotes::new(String::from(EXISTING_DECK), String::from("application"), String::from("εφαρμογή")); create_notes(add_note).await; deck_exists_mock.assert(); find_notes_mock.assert(); add_notes_mock.assert(); } #[tokio::test] async fn new_deck_new_note() { initialize(); let mut server = mockito::Server::new(); let deck_exists_mock = get_deck_exists_mock(&mut server); let create_deck_mock = get_create_deck_mock(&mut server, NEW_DECK); let find_notes_mock = get_find_notes_mock(&mut server, NEW_DECK); let add_notes_mock = get_add_notes_mock(&mut server, NEW_DECK); let add_note = AddNotes::new(String::from(NEW_DECK), String::from("application"), String::from("εφαρμογή")); create_notes(add_note).await; deck_exists_mock.assert(); create_deck_mock.assert(); find_notes_mock.assert(); add_notes_mock.assert(); }
额外说明
这种方式无需依赖串行测试,测试用例可以并行执行,既解决了端口冲突,又提升了测试效率。之前尝试的drop(server)等方法无效,是因为固定端口的抢占问题无法通过资源释放完全避免,随机端口从根源消除了冲突可能。
2. 优化BASE_URL配置方式
从Java背景出发,原有的#[cfg]宏直接标记常量的方式可读性较差,推荐以下几种更友好的方案:
方案一:函数封装+条件编译(推荐用于简单场景)
如前文所示,将不同环境的URL逻辑封装到函数中,业务代码只需调用base_url()即可,无需感知环境差异:
#[cfg(not(test))] pub fn base_url() -> String { "http://localhost:8765".to_string() } #[cfg(test)] pub fn base_url() -> String { format!("http://{}", mockito::server_address()) }
优点:代码简洁,无额外依赖,适合配置项少的场景。
方案二:环境变量+懒加载(适合需要动态调整的场景)
通过环境变量配置URL,测试时动态设置,生产使用默认值:
use lazy_static::lazy_static; use std::env; lazy_static! { static ref BASE_URL: String = env::var("BASE_URL") .unwrap_or_else(|_| "http://localhost:8765".to_string()); } #[cfg(test)] mod tests { use super::*; use std::sync::Once; static INIT: Once = Once::new(); pub fn initialize() { INIT.call_once(|| { env_logger::init(); // 测试时设置环境变量为Mockito随机端口地址 env::set_var("BASE_URL", format!("http://{}", mockito::server_address())); }); } }
注意:并行测试时环境变量会全局共享,若使用随机端口,仍推荐方案一的动态函数方式。
方案三:配置文件+专业配置库(适合多配置项场景)
使用config crate加载不同环境的配置文件,类似Java的配置文件管理方式:
- 在
Cargo.toml添加依赖:
[dependencies] config = "0.13" serde = { version = "1.0", features = ["derive"] } lazy_static = "1.4"
- 创建配置文件:
config/default.toml(生产默认配置):
base_url = "http://localhost:8765"
config/test.toml(测试配置,若使用Mockito随机端口可忽略,直接用动态函数):
base_url = "http://localhost:8089"
- 加载配置的代码:
use config::Config; use serde::Deserialize; use lazy_static::lazy_static; #[derive(Debug, Deserialize)] struct AppConfig { base_url: String, } lazy_static! { static ref CONFIG: AppConfig = { let mut cfg = Config::new(); // 加载基础配置 cfg.merge(config::File::with_name("config/default")).unwrap(); // 测试环境加载测试配置 #[cfg(test)] { cfg.merge(config::File::with_name("config/test")).unwrap(); } cfg.try_into().unwrap() }; } // 使用方式:CONFIG.base_url
优点:配置集中管理,扩展性强,适合复杂项目的多环境配置需求。
内容的提问来源于stack exchange,提问作者Dimitris Karamanis
相关产品推荐
相关产品推荐

