Rust 版 Cloud 客户端库提供了一种对实际客户端实现进行桩处理的方法,以便您注入模拟进行测试。您可以使用模拟来编写受控、可靠的单元测试,这些测试不会进行网络调用,也不会产生费用。
在本文档中,您将了解以下主题:
- 如何使用 Cloud Storage
Storage客户端编写可测试的接口。 - 如何模拟读取操作。
- 如何模拟写入操作。
Storage客户端的设计与其他 Google Cloud客户端不同的原因。
依赖项
将 Cloud Storage 客户端库添加到 Cargo.toml 文件中:
cargo add google-cloud-storage
如需编译和运行本文档中的单元测试,请添加以下开发依赖项:
cargo add --dev anyhow bytes mockall
cargo add --dev tokio --features macros,rt-multi-thread
编写可测试的接口
如果您不对存储代码进行单元测试,则可以直接根据 Storage 编写接口,其默认桩类型参数是客户端的生产实现:
如需使用模拟客户端测试代码,请根据受 gcs::stub::Storage + 'static 约束的通用类型参数 T 编写接口:
模拟读取操作
如需模拟 read_object 请求,请完成以下步骤:
编写一个接受通用
&Storage<T>客户端的应用函数。 例如,以下函数会下载一个对象并统计其中包含的换行符数量:创建一个辅助函数,通过将载荷传递给
ReadObjectResponse::from_source来构造伪造的ReadObjectResponse字节流。此方法接受与Storage::write_object相同的载荷类型:定义一个实现
gcs::stub::Storage的模拟结构体。本文档使用mockallcrate,但您可以在测试中使用其他模拟框架:编写一个单元测试,用于设置对模拟对象的预期、使用
Storage::from_stub构造客户端并调用count_newlines:
模拟写入操作
如需模拟 write_object 请求,请完成以下步骤:
编写一个应用函数,该函数使用通用
&Storage<T>客户端上传对象:使用上一部分中定义的
MockStorage结构体,编写一个单元测试,用于在模拟对象上配置预期、使用Storage::from_stub构造客户端并调用upload:
预期详情
由于 upload 函数会调用 send_unbuffered,因此请对相应的 expect_write_object_unbuffered 方法设置预期:
mockall::mock! 宏会将每个泛型实例化视为一个不同的方法。在闭包签名中指定具体载荷类型 Payload<BytesSource>,以便编译器可以匹配方法预期:
设计理由
以下部分介绍了 Storage 客户端为何为其桩使用通用类型参数,而不是动态调度。
其他客户
大多数客户端(例如 StorageControl)在内部保留其桩 trait 的引用计数 (Arc) 且与 dyn 兼容的实现。它们使用动态分派将请求从客户端转发到底层桩,底层桩可以是生产实现,也可以是模拟。
由于这些客户端使用动态分派,因此编译器无需在编译时间解析具体桩类型,并且客户端结构体无需为其桩提供泛型类型形参。
Storage 客户端
在 Rust 中,与 dyn 兼容的 trait 不能具有泛型方法或返回未装箱的 impl Future 值:
write_object_buffered和write_object_unbuffered方法是针对载荷来源类型的通用方法。gcs::stub::Storage特征方法会直接返回impl Future,而不是对返回的 future 进行装箱。
如果对 Storage 客户端使用动态分派,则需要在每次调用时对载荷和 future 进行装箱,从而增加数据路径上的堆分配量和动态分派开销。为了避免这种开销,Storage 客户端改为使用类型形参来具体实现 gcs::stub::Storage 特征。
完整的应用代码和测试套件
以下代码将应用函数和单元测试组合成一个完整示例:
运行 cargo test 以执行 test_count_lines 和 test_upload:
cargo test