在適用於 Rust 的 AWS SDK mockall中使用 自動產生模擬 - 適用於 Rust 的 AWS SDK

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

在適用於 Rust 的 AWS SDK mockall中使用 自動產生模擬

適用於 Rust 的 AWS SDK 提供多種方法來測試與 互動的程式碼 AWS 服務。您可以使用automockmockall從木箱 熱門的 ,自動產生測試所需的大多數模擬實作。

此範例會測試名為 的自訂方法determine_prefix_file_size()。此方法會呼叫呼叫 Amazon S3 的自訂list_objects()包裝函式方法。透過模擬 list_objects(),可以測試determine_prefix_file_size()方法,而無需實際聯絡 Amazon S3。

  1. 在專案目錄的命令提示字元中,新增mockall木箱做為相依性:

    $ cargo add --dev mockall

    使用 --dev選項將 木箱新增至 Cargo.toml 檔案的 [dev-dependencies]區段。作為開發相依性,它不會編譯並包含在用於生產程式碼的最終二進位檔中。

    此範例程式碼也會使用 Amazon Simple Storage Service 做為範例 AWS 服務。

    $ cargo add aws-sdk-s3

    這會將 木箱新增至 Cargo.toml 檔案的 [dependencies]區段。

  2. mockall木箱中包含 automock模組。

    同時包含與您正在測試 AWS 服務 之 相關的任何其他程式庫,在此情況下為 Amazon S3。

    use aws_sdk_s3 as s3; #[allow(unused_imports)] use mockall::automock; use s3::operation::list_objects_v2::{ListObjectsV2Error, ListObjectsV2Output};
  3. 接著,新增程式碼,以決定應用程式 Amazon S3 包裝函式結構的兩個實作中要使用哪個。

    • 實際寫入以透過網路存取 Amazon S3 的項目。

    • 產生的模擬實作mockall

    在此範例中,選取的名稱為 S3。選項是根據 test 屬性的條件式:

    #[cfg(test)] pub use MockS3Impl as S3; #[cfg(not(test))] pub use S3Impl as S3;
  4. S3Impl 結構是實際傳送請求的 Amazon S3 包裝函式結構實作 AWS。

    • 啟用測試時,不會使用此程式碼,因為請求會傳送到模擬而非模擬 AWS。dead_code 如果未使用 S3Impl類型, 屬性會告知 linter 不要報告問題。

    • 條件式#[cfg_attr(test, automock)]表示啟用測試時,應設定 automock 屬性。這會通知 mockall產生將命名為 S3Impl的模擬 MockS3Impl

    • 在此範例中, list_objects()方法是您想要模擬的呼叫。 automock 會自動為您建立 expect_list_objects()方法。

    #[allow(dead_code)] pub struct S3Impl { inner: s3::Client, } #[cfg_attr(test, automock)] impl S3Impl { #[allow(dead_code)] pub fn new(inner: s3::Client) -> Self { Self { inner } } #[allow(dead_code)] pub async fn list_objects( &self, bucket: &str, prefix: &str, continuation_token: Option<String>, ) -> Result<ListObjectsV2Output, s3::error::SdkError<ListObjectsV2Error>> { self.inner .list_objects_v2() .bucket(bucket) .prefix(prefix) .set_continuation_token(continuation_token) .send() .await } }
  5. 在名為 的模組中建立測試函數test

    • 條件式#[cfg(test)]表示如果 test 屬性為 , mockall應該建置測試模組true

    #[cfg(test)] mod test { use super::*; use mockall::predicate::eq; #[tokio::test] async fn test_single_page() { let mut mock = MockS3Impl::default(); mock.expect_list_objects() .with(eq("test-bucket"), eq("test-prefix"), eq(None)) .return_once(|_, _, _| { Ok(ListObjectsV2Output::builder() .set_contents(Some(vec![ // Mock content for ListObjectsV2 response s3::types::Object::builder().size(5).build(), s3::types::Object::builder().size(2).build(), ])) .build()) }); // Run the code we want to test with it let size = determine_prefix_file_size(mock, "test-bucket", "test-prefix") .await .unwrap(); // Verify we got the correct total size back assert_eq!(7, size); } #[tokio::test] async fn test_multiple_pages() { // Create the Mock instance with two pages of objects now let mut mock = MockS3Impl::default(); mock.expect_list_objects() .with(eq("test-bucket"), eq("test-prefix"), eq(None)) .return_once(|_, _, _| { Ok(ListObjectsV2Output::builder() .set_contents(Some(vec![ // Mock content for ListObjectsV2 response s3::types::Object::builder().size(5).build(), s3::types::Object::builder().size(2).build(), ])) .set_next_continuation_token(Some("next".to_string())) .build()) }); mock.expect_list_objects() .with( eq("test-bucket"), eq("test-prefix"), eq(Some("next".to_string())), ) .return_once(|_, _, _| { Ok(ListObjectsV2Output::builder() .set_contents(Some(vec![ // Mock content for ListObjectsV2 response s3::types::Object::builder().size(3).build(), s3::types::Object::builder().size(9).build(), ])) .build()) }); // Run the code we want to test with it let size = determine_prefix_file_size(mock, "test-bucket", "test-prefix") .await .unwrap(); assert_eq!(19, size); } }
    • 每個測試都會使用 let mut mock = MockS3Impl::default();來建立 的mock執行個體MockS3Impl

    • 它使用模擬的 expect_list_objects()方法 (由 自動建立automock) 來設定在程式碼中其他位置使用該list_objects()方法時的預期結果。

    • 建立期望之後,它會使用這些期望來呼叫 來測試函數determine_prefix_file_size()。使用聲明檢查傳回的值以確認其正確。

  6. determine_prefix_file_size() 函數使用 Amazon S3 包裝函式來取得字首檔案的大小:

    #[allow(dead_code)] pub async fn determine_prefix_file_size( // Now we take a reference to our trait object instead of the S3 client // s3_list: ListObjectsService, s3_list: S3, bucket: &str, prefix: &str, ) -> Result<usize, s3::Error> { let mut next_token: Option<String> = None; let mut total_size_bytes = 0; loop { let result = s3_list .list_objects(bucket, prefix, next_token.take()) .await?; // Add up the file sizes we got back for object in result.contents() { total_size_bytes += object.size().unwrap_or(0) as usize; } // Handle pagination, and break the loop if there are no more pages next_token = result.next_continuation_token.clone(); if next_token.is_none() { break; } } Ok(total_size_bytes) }

類型S3用於呼叫包裝的 SDK for Rust 函數,以在提出 HTTP 請求MockS3Impl時同時支援 S3Impl和 。啟用測試時, 自動產生的模擬會mockall報告任何測試失敗。

您可以在 GitHub 上檢視這些範例的完整程式碼