

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# AWS SDK for Rust `mockall` で を使用してモックを自動的に生成する
<a name="testing-automock"></a>

 AWS SDK for Rust は、 とやり取りするコードをテストするための複数のアプローチを提供します AWS のサービス。`[mockall](https://docs.rs/mockall/latest/mockall)` クレートの一般的な `[automock](https://docs.rs/mockall/latest/mockall/attr.automock.html)` を使用することにより、テストに必要なモック実装の大部分を自動的に生成できます。

この例では、`determine_prefix_file_size()` というカスタムメソッドをテストします。このメソッドは、Amazon S3 を呼び出すカスタム `list_objects()` ラッパーメソッドを呼び出します。`list_objects()` のモックを作成することにより、Amazon S3 と実際にやり取りすることなく `determine_prefix_file_size()` メソッドをテストできます。

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)として扱われるため、これはコンパイルされず、本番コードで使用される最終バイナリには含まれません。

   また、このサンプルコードは、 AWS のサービスの例として Amazon Simple Storage Service を使用します。

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

   これにより、`[dependencies]` ファイルの `Cargo.toml` セクションにクレートが追加されます。

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 ラッパー構造の 2 つの実装のうち、どちらを使用するかを決定するコードを追加します。
   + ネットワーク経由で 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` 属性を設定する必要があることを示します。これにより、`Mock{{S3Impl}}` という名前の `S3Impl` のモックを生成するように `mockall` に指示します。
   + この例では、`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` 属性が `true` の場合、`mockall` によってテストモジュールを構築する必要があることを示します。

   ```
   #[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();` を使用して `MockS3Impl` の `mock` インスタンスを作成します。
   + モックの `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` 型は、HTTP リクエストを行うときに、ラップされた SDK for Rust 関数を呼び出して `S3Impl` と `MockS3Impl` の両方をサポートするために使用されます。テストが有効になっている場合、`mockall` によって自動的に生成されたモックはテスト失敗を報告します。

GitHub で[これらの例の完全なコードを表示](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/rustv1/examples/testing)できます。