#http-api #testing-http #api-testing #black-box-testing #api #api-request #http

noir

基于 Rust,类似 DSL 且以请求驱动的 HTTP API 黑盒测试库

2 个不稳定版本

使用旧的 Rust 2015

0.2.0 2016 年 7 月 18 日
0.1.0 2016 年 7 月 5 日

#1623 in 网页编程

MIT/Apache

160KB
3K SLoC

noir 构建状态 构建状态 依赖状态 Crates.io 许可

基于 Rust,类似 DSL 且以请求驱动的 HTTP API 黑盒测试库。

  • 文档 在 crates.io 的最新版本。

请注意:noir 仍在开发中,可能有很多类型,很多错误,以及可能的一些 API 更改。

屏幕截图

noir

功能

  • 描述您的 API 和它访问的外部资源

  • 设置和配置针对您的 API 的 HTTP 请求

    • 执行带有特定头信息、查询字符串和主体的请求
    • 设置对响应头和主体的期望
  • 设置和提供外部、模拟的 HTTP 响应以供您的 API 使用

    • 这些仅适用于特定请求
    • 您的 API 在测试期间做出的任何意外外部调用都将被捕获
    • 提供的响应的请求顺序也将得到验证
    • 为您的 API 执行的请求的头和主体设置期望
  • 详细的彩色测试输出,有助于您快速找出到底出了什么问题

  • 强大的 JSON 支持,提供深入、详细的对象和数组差异比较,显示路径、类型和值

  • 使用 hyper 进行所有 HTTP 相关接口

开发中 / 不稳定

  • 宏,用于在测试中轻松定义 HTTP 多部分表单
  • API,用于在测试请求期间提供自定义/外部模拟

使用 noir 测试您的 API

由于 noir 提供了在测试中模拟应用程序某些部分的能力,因此您需要将测试作为 测试运行,以便在测试期间启用模拟。

一个完整的示例项目,集成了 noir,可以在 examples/api 文件夹中找到。

以下是对测试所需设置步骤的简要概述。

描述您的API

noir 提供了对基于 HTTP 的 API 的直接支持,要描述自己的基于 HTTP 的 API,请创建一个实现了 HttpApi 特质的结构。

use noir::HttpApi;

#[derive(Copy, Clone, Default)]
pub struct Api;
impl HttpApi for Api {

    fn hostname(&self) -> &'static str {
        "localhost"
    }

    fn port(&self) -> u16 {
        4000
    }

    fn start(&self) {
        application::server::run(self.host().as_str());
    }

}

您只需告诉 noir 以下三件事

  1. 在测试期间,您的应用程序将监听的域名
  2. 在测试期间,您的应用程序将监听的端口号
  3. 哪个 阻塞 函数将启动您的 web 服务器

当执行测试时,noir 将等待您的 web 服务器启动,然后逐个运行每个测试。

不并行运行的原因是,一旦您开始使用 noir 提供的宏(如 hyper_client!()(这些宏使您能够模拟应用程序对发出的 HTTP 请求的响应)),就没有简单的方法(即,不需要向应用程序添加额外的逻辑)将请求与每个测试提供的响应匹配,并且在您的应用程序在测试期间执行额外的、意外的 HTTP 请求时,也不清楚哪个具体的测试应该失败。

描述外部资源

use noir::HttpEndpoint;
#[derive(Copy, Clone)]
pub struct ExternalResource;
impl HttpEndpoint for ExternalResource {

    fn hostname(&self) -> &'static str {
        "external-resouce.com"
    }

    fn port(&self) -> u16 {
        443
    }

}

测试请求

每个 noir 测试都以对您定义的 API 结构的 HTTP 方法调用开始,所有这些调用都返回一个 HttpRequest 实例。

HttpRequest 实例允许您设置测试请求的数据和预期。

您还可以提供外部资源响应,这些响应将在请求运行时对您的应用程序可用。

一旦 HttpRequest 实例超出作用域,其构建的请求将自动发送,并且对其任何预期都将进行验证。

下面是一个相当牵强的示例,说明可以做到什么程度。有关详细信息,请参阅 文档

#[macro_use]
extern crate noir;

#[test]
fn test_get_resource_with_missing_optional_data() {

    // Perform a request against our API
    Api::get("/")
        
        // Set up our query string
        .with_query(query! {
            "page" => 2,
            "sort" => "asc",
            "detailed" => true
        })

        // Set the headers of the request
        .with_headers(headers![
            Accept(vec![
                qitem(Mime(TopLevel::Application, SubLevel::Json, vec![]))
            ])
        ])

        // Provide some mocked, external resource responses during the api request
        .provide(responses![

            // Provide a resource for "/data/base.json" that responds with a
            // "200 OK" and a json body and expects a JSON Accept header.
            ExternalResource.get("/data/base.json")
                            .with_status(StatusCode::Ok)
                            .with_body(!object {
                                "key" => "value"
                            })
                            .expected_header(Accept(vec![
                                qitem(Mime(TopLevel::Application, SubLevel::Json, vec![]))
                            ])),

            // Provide another resource with responds with "500"
            ExternalResource.get("/data/optional.json")
                            .with_status(StatusCode::InternalServerError)
                            .expected_header(Accept(vec![
                                qitem(Mime(TopLevel::Application, SubLevel::Json, vec![]))
                            ]))
        ])

        // Expect a "200 OK" response from our API
        .expected_status(StatusCode::Ok)

        // Expect a JSON Content-Type header on our response
        .expected_header(ContentType(
            Mime(TopLevel::Application, SubLevel::Json, vec![])
        ))

        // And finally expect a JSON body
        .expected_body(object!{
            "resource" => object! {
                "key" => "value"
            },
            "optional" => JsonValue::Null
        });
}

许可

根据以下任一许可

贡献

除非您明确声明,否则您有意提交以包含在作品中并由您提交的任何贡献,均应按上述方式双许可,不附加任何额外条款或条件。

依赖项

~5–14MB
~168K SLoC