我正在构建一个查询其运行环境的库,以便将值返回给ask程序。有时候就像
一样简单pub fn func_name() -> Option<String> {
match env::var("ENVIRONMENT_VARIABLE") {
Ok(s) => Some(s),
Err(e) => None
}
}
但有时更复杂,甚至有一个由各种环境变量组成的结果。如何测试这些方法是否按预期运行?
答案 0 :(得分:8)
“我如何测试X”几乎总是以“通过控制X”来回答。在这种情况下,您需要控制环境变量:
use std::env;
fn env_is_set() -> bool {
match env::var("ENVIRONMENT_VARIABLE") {
Ok(s) => s == "yes",
_ => false
}
}
#[test]
fn when_set_yes() {
env::set_var("ENVIRONMENT_VARIABLE", "yes");
assert!(env_is_set());
}
#[test]
fn when_set_no() {
env::set_var("ENVIRONMENT_VARIABLE", "no");
assert!(!env_is_set());
}
#[test]
fn when_unset() {
env::remove_var("ENVIRONMENT_VARIABLE");
assert!(!env_is_set());
}
但是,您需要注意环境变量是共享资源。来自the docs for set_var
,强调我的:
将当前正在运行的流程的环境变量
k
设置为值v
。
您可能还需要注意,Rust测试运行器默认情况下会并行运行测试,因此可能会有另一个测试clobber。
此外,您可能希望在测试后将环境变量“重置”为已知良好状态。
答案 1 :(得分:5)
你的另一个选择(如果你不想搞乱实际设置环境变量)是抽象出来的。我只是在学习Rust,所以我不确定这是不是“Rust way(tm)”这样做...但这肯定是我会在另一种语言/环境中做到的:
use std::env;
pub trait QueryEnvironment {
fn get_var(&self, var: &'static str) -> Result<String, std::env::VarError>;
}
struct MockQuery;
struct ActualQuery;
impl QueryEnvironment for MockQuery {
#[allow(unused_variables)]
fn get_var(&self, var: &'static str) -> Result<String, std::env::VarError> {
Ok("Some Mocked Result".to_string()) // Returns a mocked response
}
}
impl QueryEnvironment for ActualQuery {
fn get_var(&self, var: &'static str) -> Result<String, std::env::VarError> {
env::var(var) // Returns an actual response
}
}
fn main() {
let mocked_query = MockQuery;
let actual_query = ActualQuery;
println!("The mocked environment value is: {}", func_name(mocked_query).unwrap());
println!("The actual environment value is: {}", func_name(actual_query).unwrap());
}
pub fn func_name<T: QueryEnvironment>(query: T) -> Option<String> {
match query.get_var("ENVIRONMENT_VARIABLE") {
Ok(s) => Some(s),
Err(_) => None
}
}
围栏的示例:http://is.gd/QhUlDW
注意实际调用是如何发生恐慌的。这是您将在实际代码中使用的实现。对于您的测试,您将使用模拟的。
答案 2 :(得分:0)
第三个选项,我认为更好的选择是传递现有类型 - 而不是创建一个每个人都必须强制要求的新抽象。
pub fn new<I>(vars: I)
where I: Iterator<Item = (String, String)>
{
for (x, y) in vars {
println!("{}: {}", x, y)
}
}
#[test]
fn trivial_call() {
let vars = [("fred".to_string(), "jones".to_string())];
new(vars.iter().cloned());
}
感谢#rust上的qrlpz帮助我为我的程序排序,只需分享结果以帮助其他人:)
答案 3 :(得分:0)
我有同样的需求,并实现了一些小型测试助手,它们处理了@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
将负责:
帮手:
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();
}
/// Sets environment variables to the given value for the duration of the closure.
/// Restores the previous values when the closure completes or panics, before unwinding the panic.
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);
}
}