

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

# 在適用於 Rust 的 AWS SDK `mockall`中使用 自動產生模擬
<a name="testing-automock"></a>

 適用於 Rust 的 AWS SDK 提供多種方法來測試與 互動的程式碼 AWS 服務。您可以使用 `[automock](https://docs.rs/mockall/latest/mockall/attr.automock.html)` `[mockall](https://docs.rs/mockall/latest/mockall)` 箱中的熱門 ，自動產生測試所需的大多數模擬實作。

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

1. 在專案目錄的命令提示中，新增`[mockall](https://docs.rs/mockall/latest/mockall)`木箱做為相依性：

   ```
   $ cargo add --dev mockall
   ```

   使用 `--dev`[選項](https://doc.rust-lang.org/cargo/commands/cargo-add.html)將木箱新增至 `Cargo.toml` 檔案的 `[dev-dependencies]`區段。作為[開發相依性](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#development-dependencies)，它不會編譯並包含在用於生產程式碼的最終二進位檔中。

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

   ```
   $ cargo add aws-sdk-s3
   ```

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

1. 從`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};
   ```

1. 接著，新增程式碼，以決定應用程式 Amazon S3 包裝函式結構的兩個實作中要使用哪個。
   + 實際寫入以透過網路存取 Amazon S3 的項目。
   + 產生的模擬實作`mockall`。

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

   ```
   #[cfg(test)]
   pub use MockS3Impl as S3;
   #[cfg(not(test))]
   pub use S3Impl as S3;
   ```

1. `S3Impl` 結構是實際傳送請求的 Amazon S3 包裝函式結構實作 AWS。
   + 啟用測試時，不會使用此程式碼，因為請求會傳送到模擬而非模擬 AWS。`dead_code` 如果未使用 `S3Impl`類型， 屬性會告知 linter 不要報告問題。
   +  條件式`#[cfg_attr(test, automock)]`表示啟用測試時，應設定 `automock` 屬性。這會通知 `mockall`產生`S3Impl`將命名為 的模擬 `Mock{{S3Impl}}`。
   + 在此範例中， `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
       }
   }
   ```

1. 在名為 的模組中建立測試函數`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()`。使用聲明檢查傳回的值以確認其正確。

1. `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 上[檢視這些範例的完整程式碼](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/rustv1/examples/testing)。