#cloud-native #integration-tests #test-framework #buildpack #cnb #container-image

libcnb-test

使用libcnb.rs编写的buildpack集成测试框架

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测试

Download history 290/week @ 2024-04-28 22/week @ 2024-05-05 18/week @ 2024-05-12 20/week @ 2024-05-19 32/week @ 2024-05-26 49/week @ 2024-06-02 128/week @ 2024-06-09 322/week @ 2024-06-16 40/week @ 2024-06-23 390/week @ 2024-06-30 100/week @ 2024-07-07 23/week @ 2024-07-14 114/week @ 2024-07-21 96/week @ 2024-07-28 50/week @ 2024-08-11

每月261次 下载
用于 2 crates

BSD-3-Clause

245KB
5K SLoC

libcnb-test   文档 最新版本 MSRV

使用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