26个版本 (重大更新)
0.22.0 | 2024年6月18日 |
---|---|
0.20.0 | 2024年4月12日 |
0.19.0 | 2024年2月23日 |
0.17.0 | 2023年12月6日 |
0.3.0 | 2022年3月8日 |
#54 在 测试
每月261次 下载
用于 2 crates
245KB
5K SLoC
libcnb-test
使用Rust语言和libcnb.rs编写的Cloud Native Buildpacks集成测试框架。
框架
- 自动交叉编译和打包测试的buildpack
- 使用
pack build
执行指定配置的构建 - 支持使用结果应用程序镜像启动容器
- 支持并发测试执行
- 处理测试容器和镜像的清理
- 提供额外的测试断言宏以简化常见的测试场景(例如,
assert_contains!
)
依赖项
集成测试需要在主机上提供以下内容
仅完全支持本地Docker守护进程。因此,如果您使用Circle CI,则必须使用machine
执行器而不是远程Docker功能。
示例
一个基本的测试,使用指定的构建器镜像和应用程序源固定装置进行构建,然后对生成的 pack build
日志输出进行断言
// In $CRATE_ROOT/tests/integration_test.rs
use libcnb_test::{assert_contains, assert_empty, BuildConfig, TestRunner};
// Note: In your code you'll want to uncomment the `#[test]` annotation here.
// It's commented out in these examples so that this documentation can be
// run as a `doctest` and so checked for correctness in CI.
// #[test]
fn basic() {
TestRunner::default().build(
BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
|context| {
assert_empty!(context.pack_stderr);
assert_contains!(context.pack_stdout, "Expected build output");
},
);
}
执行相同镜像的第二次构建以测试缓存处理,使用 TestContext::rebuild
use libcnb_test::{assert_contains, BuildConfig, TestRunner};
// #[test]
fn rebuild() {
TestRunner::default().build(
BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
|context| {
assert_contains!(context.pack_stdout, "Installing dependencies");
let config = context.config.clone();
context.rebuild(config, |rebuild_context| {
assert_contains!(rebuild_context.pack_stdout, "Using cached dependencies");
});
},
);
}
测试预期的构建包失败,使用 BuildConfig::expected_pack_result
use libcnb_test::{assert_contains, BuildConfig, PackResult, TestRunner};
// #[test]
fn expected_pack_failure() {
TestRunner::default().build(
BuildConfig::new("heroku/builder:22", "tests/fixtures/invalid-app")
.expected_pack_result(PackResult::Failure),
|context| {
assert_contains!(context.pack_stderr, "ERROR: Invalid Procfile!");
},
);
}
在构建的镜像上运行shell命令,使用 TestContext::run_shell_command
use libcnb_test::{assert_empty, BuildConfig, TestRunner};
// #[test]
fn run_shell_command() {
TestRunner::default().build(
BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
|context| {
// ...
let command_output = context.run_shell_command("python --version");
assert_empty!(command_output.stderr);
assert_eq!(command_output.stdout, "Python 3.10.4\n");
},
);
}
使用默认进程和公开的端口启动容器以测试Web服务器,使用 TestContext::start_container
use libcnb_test::{assert_contains, assert_empty, BuildConfig, ContainerConfig, TestRunner};
use std::thread;
use std::time::Duration;
const TEST_PORT: u16 = 12345;
// #[test]
fn starting_web_server_container() {
TestRunner::default().build(
BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
|context| {
// ...
context.start_container(
ContainerConfig::new()
.env("PORT", TEST_PORT.to_string())
.expose_port(TEST_PORT),
|container| {
let address_on_host = container.address_for_port(TEST_PORT);
let url = format!("http://{}:{}", address_on_host.ip(), address_on_host.port());
// Give the server time to start.
thread::sleep(Duration::from_secs(2));
let server_log_output = container.logs_now();
assert_empty!(server_log_output.stderr);
assert_contains!(
server_log_output.stdout,
&format!("Listening on port {TEST_PORT}")
);
let response = ureq::get(&url).call().unwrap();
let body = response.into_string().unwrap();
assert_contains!(body, "Expected response substring");
},
);
},
);
}
使用Docker Exec检查已运行的容器,使用 ContainerContext::shell_exec
use libcnb_test::{assert_contains, BuildConfig, ContainerConfig, TestRunner};
// #[test]
fn shell_exec() {
TestRunner::default().build(
BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
|context| {
// ...
context.start_container(ContainerConfig::new(), |container| {
// ...
let exec_log_output = container.shell_exec("ps");
assert_contains!(exec_log_output.stdout, "nginx");
});
},
);
}
在测试设置期间动态修改测试固定装置,使用 BuildConfig::app_dir_preprocessor
use libcnb_test::{BuildConfig, TestRunner};
use std::fs;
// #[test]
fn dynamic_fixture() {
TestRunner::default().build(
BuildConfig::new("heroku/builder:22", "tests/fixtures/app").app_dir_preprocessor(
|app_dir| {
fs::write(app_dir.join("runtime.txt"), "python-3.10").unwrap();
},
),
|context| {
// ...
},
);
}
使用多个构建包进行构建,使用 BuildConfig::buildpacks
use libcnb::data::buildpack_id;
use libcnb_test::{BuildConfig, BuildpackReference, TestRunner};
// #[test]
fn additional_buildpacks() {
TestRunner::default().build(
BuildConfig::new("heroku/builder:22", "tests/fixtures/app").buildpacks([
BuildpackReference::CurrentCrate,
BuildpackReference::WorkspaceBuildpack(buildpack_id!("my-project/buildpack")),
BuildpackReference::Other(String::from("heroku/another-buildpack")),
]),
|context| {
// ...
},
);
}
提示
- Rust测试会自动并行运行,但前提是它们位于同一个crate中。对于集成测试,Rust将每个文件编译为一个单独的crate。因此,请确保将所有集成测试包含在单个文件中(内联或通过包含额外的测试模块),以确保它们并行运行。
- 如果您想更轻松地单独运行单元测试和集成测试,请在每个集成测试上使用
#[ignore = "integration test"]
进行注释,这将导致cargo test
跳过它们(仅运行单元/文档测试)。然后可以使用cargo test -- --ignored
运行集成测试,或者可以使用cargo test -- --include-ignored
一次性运行所有测试。 - 如果您想对多行日志输出进行断言,请参阅indoc crate。
依赖项
~9–21MB
~293K SLoC