2 个不稳定版本
使用旧的 Rust 2015
0.2.0 | 2016 年 7 月 18 日 |
---|---|
0.1.0 | 2016 年 7 月 5 日 |
#1623 in 网页编程
160KB
3K SLoC
noir
基于 Rust,类似 DSL 且以请求驱动的 HTTP API 黑盒测试库。
- 文档 在 crates.io 的最新版本。
请注意:noir 仍在开发中,可能有很多类型,很多错误,以及可能的一些 API 更改。
屏幕截图
功能
-
描述您的 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 以下三件事
- 在测试期间,您的应用程序将监听的域名
- 在测试期间,您的应用程序将监听的端口号
- 哪个 阻塞 函数将启动您的 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
});
}
许可
根据以下任一许可
- Apache License,版本 2.0(LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证(LICENSE-MIT 或 http://opensource.org/licenses/MIT)任选。
贡献
除非您明确声明,否则您有意提交以包含在作品中并由您提交的任何贡献,均应按上述方式双许可,不附加任何额外条款或条件。
依赖项
~5–14MB
~168K SLoC