

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# 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)으로서 컴파일되지 않으며 프로덕션 코드에 사용되는 최종 바이너리에 포함되지 않습니다.

   이 예제 코드는 Amazon Simple Storage Service도 AWS 서비스예제로 사용합니다.

   ```
   $ 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 래퍼 구조의 두 가지 구현 중 어떤 것을 사용할지 결정하는 코드를 추가합니다.
   + 네트워크를 통해 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` 유형이 사용되지 않는 경우 문제를 보고하지 않도록 린터에 지시합니다.
   +  조건부 `#[cfg_attr(test, automock)]`는 테스트가 활성화되면 `automock` 속성을 설정해야 함을 나타냅니다. 이는 이름이 `MockS3Impl`인 `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 요청을 할 때 `S3Impl` 및 `MockS3Impl`을 모두 지원하기 위해 래핑된 SDK for Rust 함수를 직접 호출하는 데 사용됩니다. `mockall`에서 자동으로 생성된 모의는 테스트가 활성화될 때 모든 테스트 실패를 보고합니다.

GitHub에서 [이러한 예제의 완성된 코드를 볼 수](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/rustv1/examples/testing) 있습니다.