编辑:
以下测试辅助程序现在可以在专用的创建中使用。
免责声明:我是共同作者之一。
我有同样的需求,并实现了一些小的测试辅助程序,可以处理 @Shepmaster 提到的注意事项。
这些测试辅助程序使得测试能够像这样进行:
#[test]
fn test_default_log_level_is_info() {
with_env_vars(
vec![
("LOGLEVEL", None),
("SOME_OTHER_VAR", Some("foo"))
],
|| {
let actual = Config::new();
assert_eq!("INFO", actual.log_level);
},
);
}
with_env_vars
能够自动处理以下事项:
- 在并行运行测试时避免副作用。
- 在测试闭包完成后将环境变量重置为其原始值。
- 支持在测试闭包期间取消设置环境变量。
- 即使测试闭包发生 panic,也能够执行以上所有操作。
该辅助函数:
use lazy_static::lazy_static;
use std::env::VarError;
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::sync::Mutex;
use std::{env, panic};
lazy_static! {
static ref SERIAL_TEST: Mutex<()> = Default::default();
}
pub fn with_env_vars<F>(kvs: Vec<(&str, Option<&str>)>, closure: F)
where
F: Fn() + UnwindSafe + RefUnwindSafe,
{
let guard = SERIAL_TEST.lock().unwrap();
let mut old_kvs: Vec<(&str, Result<String, VarError>)> = Vec::new();
for (k, v) in kvs {
let old_v = env::var(k);
old_kvs.push((k, old_v));
match v {
None => env::remove_var(k),
Some(v) => env::set_var(k, v),
}
}
match panic::catch_unwind(|| {
closure();
}) {
Ok(_) => {
for (k, v) in old_kvs {
reset_env(k, v);
}
}
Err(err) => {
for (k, v) in old_kvs {
reset_env(k, v);
}
drop(guard);
panic::resume_unwind(err);
}
};
}
fn reset_env(k: &str, old: Result<String, VarError>) {
if let Ok(v) = old {
env::set_var(k, v);
} else {
env::remove_var(k);
}
}
env::var
可能会产生错误,如果环境变量不是有效的Unicode。或者这是一个有意的决定,因为非Unicode环境变量应该被视为“未设置”,以便正确处理它? - hyperupcallenv::var_os
。 - Shepmastercargo test
的示例。它成功运行了896次,失败了104次。可能是因为测试是并行运行的,存在在设置变量和测试之间的竞态条件。我认为,目前的答案实际上没有回答这个问题,因为它没有提供一种实际测试环境变量的方法。看到这个答案的人可能会忽略这一点,并最终遇到很多烦恼的不稳定测试。 - Neil Robertsset_env
可以秘密引入 内存不安全!)。相反,我努力在程序开头一次性获取所有环境变量,然后构建一个配置数据结构。那个结构就是我用于测试的。 - Shepmaster