当前位置:嗨网首页>书籍在线阅读

11-宏用例-编写测试

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

9.9 宏用例——编写测试

在为单元测试编写测试用例时,经常会用到宏。我们假设你正在编写HTTP客户端程序库,并且希望在各种HTTP谓词(如GET/POST)和不同的URL上测试该客户端程序。编写测试的通常做法是为每种类型的请求和URL创建函数。但是,有一种更好的方法可以做到这一点。通过宏,你可以构建一个小型DSL来执行测试以减少测试时间,这些测试是可读的,也可以在编译期进行类型检查。为了证明这一点,我们运行cargo new http_tester --lib命令创建一个新的程序库项目,并在其中包含我们的宏定义。该宏实现了一种小型语言,旨在描述对URL的简单HTTP GET/POST测试。以下是该语言的示例:

http://duckduckgo.com GET => 200
http://httpbin.org/post POST => 200, "key" => "value"

第1行代码表示向duckduckgo.com发送了一个GET请求,并期望返回代码为200(请求成功)。第2行代码表示向httpbin.org发送一个POST请求,以及带有自定义语法的表单参数"key"=>"value",它还期望返回代码为200。上述示例非常简单,但是足以满足我们演示上述宏的需要。

我们假定已经实现了该程序库,并将使用名为reqwest的HTTP请求库。我们将在Cargo.toml文件中添加对reqwest库的依赖:

# http_tester/Cargo.toml
[dependencies]
reqwest = "0.9.5"

以下是lib.rs的代码:

// http_tester/src/lib.rs
#[macro_export]
macro_rules! http_test {
    ($url:tt GET => $code:expr) => {
        let request = reqwest::get($url).unwrap();
        println!("Testing GET {} => {}", $url, $code);
        assert_eq!(request.status().as_u16(), $code);
    };
    ($url:tt POST => $code:expr, $($k:expr => $v:expr),*) => {
        let params = [$(($k, $v),)*];
        let client = reqwest::Client::new();
        let res = client.post($url)
            .form(&params)
            .send().unwrap();
        println!("Testing POST {} => {}", $url, $code);
        assert_eq!(res.status().as_u16(), $code);
    };
}
#[cfg(test)]
mod tests {
    #[test]
    fn test_http_verbs() {
        http_test!("http://duckduckgo.com" GET => 200);
        http_test!("http://httpbin.org/post" POST => 200, "hello" =>
"world", "foo" => "bar");
    }
}

在宏定义中,我们只是匹配规则,GET和POST被视为字面标记。在匹配臂中,我们创建了请求客户端,并对输入返回的状态代码进行断言,该状态代码是提供给宏的。POST测试用例还有一个自定义语法,用于提供查询参数,例如"key"=>"value",它在params变量中作为数组进行收集。然后将其传递给reqwest::post构造器方法的form方法。我们将在第13章深入介绍Web请求程序库的详细内容。

让我们运行cargo test命令,得到以下输出结果:

running 1 test
test tests::test_http_verbs ... ok

请花一点时间思考一下在这里使用宏的好处是什么。这也可以作为带#[test]注释的函数调用来实现,但是宏有一些额外的优点,即使最基本的形式也是如此。优点之一是在编译期检查HTTP谓词,使我们的测试更具说明性。如果我们尝试使用不在预期范围之内的测试用例(例如HTTP DELETE)调用宏,那么将得到以下错误提示信息:

error: no rules expected the token `DELETE`

除了使用它们进行枚举测试用例之外,宏还用于根据某些外部环境状态(例如数据库表、时间及日期等)生成Rust代码。它们可以用于装饰具有自定义属性的结构体,在编译期为它们生成任意代码,或者创建新的linter插件以进行Rust编译器本身并不支持的其他静态分析。一个很好的例子是我们已经用过的clippy lint工具。宏还用于生成调用原生C程序库的代码。我们将会在第10章对这一主题进行详细介绍。