首页 > 解决方案 > 用于测试的抽象操作系统环境变量

问题描述

我创建了一个Server结构,它由 2 个端口、一个 IP 地址和一个端口组成。无法指定分配给端口的值,但它是从系统的环境变量中读取的。当未找到环境变量或它不是有效值时,它默认为标准端口。

struct Server {
    ip: String,
    port: u16,
}

impl Server {
    const STD_PORT: u16 = 4333;
    const PORT_ENV_VAR: &'static str = "MEMDB_PORT";

    fn new(ip: String) -> Server {
        Server {
            ip: ip,
            port: Server::get_port(),
        }
    }

    fn get_port() -> u16 {
        match env::var(Server::PORT_ENV_VAR) {
            Ok(val) => match val.parse::<u16>() {
                Ok(val) => val,
                Err(_) => Server::STD_PORT,
            },
            Err(_) => Server::STD_PORT,
        }
    }
}

当我想为它创建测试时,可以通过以下方式完成:

#[cfg(test)]
mod tests {
    use crate::Server;

    #[test]
    fn test_create_server_assigns_correct_ip() {
        // WHEN:
        let server = crate::Server::new("127.0.0.1".to_owned());

        // THEN:
        assert_eq!(server.ip, "127.0.0.1");
    }

    #[test]
    fn test_create_server_assigns_correct_port() {
        // WHEN:
        let server = crate::Server::new("".to_owned());

        // THEN:
        assert_eq!(server.port, 4333);
    }
}

当然,当环境变量MEMDB_PORT被赋值时,这个测试会产生不同的结果。

我如何能够通过设置正确的环境变量来验证分配的端口是否可以控制?

我正在考虑创建一个特征来定义 API 以访问系统的环境变量。

trait EnvironmentReader {
    fn get(key: &str) -> Option<String>;
}

struct OSEnvironmentReader {}

impl EnvironmentReader for OSEnvironmentReader {
    fn get(key: &str) -> Option<String> {
        return match env::var(key) {
            Ok(val) => Some(val),
            Err(_) => None,
        };
    }
}

这是如何在 Rust 中做到这一点的正确方法吗?如果是这样,我应该如何修改我的结构来使用这个特性?将它作为一个字段添加到结构本身,我如何使用新的“dyn”东西来做到这一点?

标签: rust

解决方案


这里不需要dyn或特征。只需创建一个配置结构:

pub struct ServerConfig {
    pub port: u16,
}

impl ServerConfig {
    const STD_PORT: u16 = 4333;
    const PORT_ENV_VAR: &'static str = "MEMDB_PORT";

    fn from_environment() -> ServerConfig {
        ServerConfig {
            port: Self::port_from_env(),
        }
    }

    fn port_from_env() -> u16 {
        // Also shortened the `match` a bit here. Could make this generic too.
        env::var(ServerConfig::PORT_ENV_VAR)
            .map(|val| val.parse::<u16>())
            .unwrap_or(ServerConfig::STD_PORT)
    }
}

Server然后在构建它时将其传递给您:

struct Server {
    config: Config,
    ip: String,
}

impl Server {
    fn new(config: Config, ip: String) -> Server {
        Server {
            config,
            ip,
        }
    }
}

在生产代码中,通过ServerConfig::from_environment(). 在您的测试代码中,传递一个自定义ServerConfig { port: 4333 }. 您仍然没有测试该端口是否正确地从环境中获取,但是在您提到的评论中您不想要它并且宁愿将其模拟出来。

如果它更适合您的设计,您也可以搬进ip去。ServerConfig


推荐阅读