기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.
AWS SDK for Rust 프로젝트에서 단위 테스트를 구현할 수 있는 방법은 여러 가지가 있지만 몇 가지 권장 사항은 다음과 같습니다.
-
AWS Smithy 런타임을 사용하여 일반적으로에서 사용되는 표준 HTTP 클라이언트 대신 사용할 수 있는 가짜 HTTP 클라이언트를
StaticReplayClient
생성합니다 AWS 서비스. 이 클라이언트는 네트워크를 통해 서비스와 통신하는 대신 지정한 HTTP 응답을 반환하므로 테스트는 테스트 목적으로 알려진 데이터를 가져옵니다.
Mockall을 사용하여 모의 자동 생성
mockall
크레이트automock
에서 널리 사용되는를 사용하여 테스트에 필요한 모의 구현의 대부분을 자동으로 생성할 수 있습니다.
이 예제에서는 라는 사용자 지정 메서드를 테스트합니다determine_prefix_file_size()
. 이 메서드는 Amazon S3를 호출하는 사용자 지정 list_objects()
래퍼 메서드를 호출합니다. 를 모의하여 Amazon S3에 실제로 연락하지 않고도 determine_prefix_file_size()
메서드list_objects()
를 테스트할 수 있습니다.
-
프로젝트 디렉터리에 대한 명령 프롬프트에서
mockall
crate를 종속 항목으로 추가합니다.$
cargo add mockall그러면
Cargo.toml
파일의[dependencies]
섹션에 크레이트가 추가됩니다. -
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};
-
그런 다음 애플리케이션의 Amazon S3 래퍼 구조 구현 중 사용할 두 가지를 결정하는 코드를 추가합니다.
-
네트워크를 통해 Amazon S3에 액세스하기 위해 작성된 실제 입니다.
-
에서 생성된 모의 구현입니다
mockall
.
이 예제에서는 선택한에 이름이 지정됩니다
S3
. 선택은test
속성을 기반으로 하는 조건부입니다.#[cfg(test)] pub use MockS3Impl as S3; #[cfg(not(test))] pub use S3Impl as S3;
-
-
S3Impl
구조는 실제로에 요청을 보내는 Amazon S3 래퍼 구조의 구현입니다 AWS.-
테스트가 활성화되면 요청이 모의 로 전송되고 전송되지 않으므로이 코드는 사용되지 않습니다 AWS.
dead_code
속성은S3Impl
유형이 사용되지 않는 경우 문제를 보고하지 않도록 리터에 지시합니다. -
조건은 테스트가 활성화되면
automock
속성을 설정해야 함을#[cfg_attr(test, automock)]
나타냅니다. 이렇게 하면 이름이 인 모의S3Impl
를 생성mockall
하도록 지시합니다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 } }
-
-
라는 모듈에서 테스트 함수를 생성합니다
test
.-
조건은
test
속성이 인 경우가 테스트 모듈을 빌드mockall
해야 함을#[cfg(test)]
나타냅니다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()
. 반환된 값은 어설션을 사용하여 올바른지 확인합니다.
-
-
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) }
유형은 HTTP 요청을 할 MockS3Impl
때 S3Impl
및를 모두 지원하기 위해 래핑된 SDK for Rust 함수를 호출하는 데 S3
사용됩니다. 에서 자동으로 생성된 모의는 테스트가 활성화될 때 테스트 실패를 mockall
보고합니다.
GitHub에서 이러한 예제의 전체 코드를 볼
정적 재생을 사용하여 HTTP 트래픽 시뮬레이션
aws-smithy-runtime
크레이트에는 라는 테스트 유틸리티 클래스가 포함되어 있습니다StaticReplayClient
를 초기화StaticReplayClient
할 때 HTTP 요청 및 응답 페어 목록을 ReplayEvent
객체로 제공합니다. 테스트가 실행되는 동안 각 HTTP 요청이 기록되고 클라이언트는 ReplayEvent
이벤트 목록의 다음에 있는 다음 HTTP 응답을 HTTP 클라이언트의 응답으로 반환합니다. 이렇게 하면 네트워크 연결 없이 알려진 데이터를 사용하여 테스트를 실행할 수 있습니다.
정적 재생 사용
정적 재생을 사용하려면 래퍼를 사용할 필요가 없습니다. 대신 테스트에서 사용할 데이터에 대해 실제 네트워크 트래픽이 어떤 모습이어야 하는지 결정하고 SDK가 AWS 서비스 클라이언트로부터 요청을 발행할 때마다 사용할 StaticReplayClient
수 있도록 해당 트래픽 데이터를에 제공합니다.
참고
AWS CLI 및 많은 네트워크 트래픽 분석기와 패킷 스니퍼 도구를 포함하여 예상 네트워크 트래픽을 수집하는 방법에는 여러 가지가 있습니다.
-
예상 HTTP 요청과 반환해야 하는 응답을 지정하는
ReplayEvent
객체 목록을 생성합니다. -
이전 단계에서 생성된 HTTP 트랜잭션 목록을
StaticReplayClient
사용하여를 생성합니다. -
를 객체의
StaticReplayClient
로 지정하여 AWS 클라이언트에 대한 구성Config
객체를 생성합니다http_client
. -
이전 단계에서 생성한 구성을 사용하여 AWS 서비스 클라이언트 객체를 생성합니다.
-
를 사용하도록 구성된 서비스 객체를 사용하여 테스트하려는 작업을 수행합니다
StaticReplayClient
. SDK가 API 요청을 보낼 때마다 목록의 AWS다음 응답이 사용됩니다.참고
전송된 요청이
ReplayEvent
객체 벡터의 응답과 일치하지 않더라도 목록의 다음 응답은 항상 반환됩니다. -
원하는 모든 요청이 이루어지면
StaticReplayClient.assert_requests_match()
함수를 호출하여 SDK에서 보낸 요청이ReplayEvent
객체 목록의 요청과 일치하는지 확인합니다.
예제
이전 예제에서 동일한 determine_prefix_file_size()
함수에 대한 테스트를 살펴보되, 모의 대신 정적 재생을 사용하겠습니다.
-
프로젝트 디렉터리에 대한 명령 프롬프트에서
aws-smithy-runtime
crate를 종속 항목으로 추가합니다. $
cargo add aws-smithy-runtime --features test-util그러면
Cargo.toml
파일의[dependencies]
섹션에 크레이트가 추가됩니다. -
소스 파일에 필요한
aws_smithy_runtime
유형을 포함합니다.use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_types::body::SdkBody;
-
테스트는 테스트 중에 수행해야 하는 각 HTTP 트랜잭션을 나타내는
ReplayEvent
구조를 생성하는 것으로 시작됩니다. 각 이벤트에는 HTTP 요청 객체와가 일반적으로 회신할 정보를 나타내는 HTTP 응답 객체가 포함되어 AWS 서비스 있습니다. 이러한 이벤트는에 대한 호출로 전달됩니다StaticReplayClient::new()
.let page_1 = ReplayEvent::new( http::Request::builder() .method("GET") .uri("https://test-bucket.s3.us-east-1.amazonaws.com/?list-type=2&prefix=test-prefix") .body(SdkBody::empty()) .unwrap(), http::Response::builder() .status(200) .body(SdkBody::from(include_str!("./testing/response_multi_1.xml"))) .unwrap(), ); let page_2 = ReplayEvent::new( http::Request::builder() .method("GET") .uri("https://test-bucket.s3.us-east-1.amazonaws.com/?list-type=2&prefix=test-prefix&continuation-token=next") .body(SdkBody::empty()) .unwrap(), http::Response::builder() .status(200) .body(SdkBody::from(include_str!("./testing/response_multi_2.xml"))) .unwrap(), ); let replay_client = StaticReplayClient::new(vec![page_1, page_2]);
결과는에 저장됩니다
replay_client
. 이는 SDK for Rust에서 클라이언트 구성에 지정하여 사용할 수 있는 HTTP 클라이언트를 나타냅니다. -
Amazon S3 클라이언트를 생성하려면 클라이언트 클래스의
from_conf()
함수를 호출하여 구성 객체를 사용하여 클라이언트를 생성합니다.let client: s3::Client = s3::Client::from_conf( s3::Config::builder() .behavior_version(BehaviorVersion::latest()) .credentials_provider(make_s3_test_credentials()) .region(s3::config::Region::new("us-east-1")) .http_client(replay_client.clone()) .build(), );
구성 객체는 빌더의
http_client()
메서드를 사용하여 지정되고 자격 증명은credentials_provider()
메서드를 사용하여 지정됩니다. 자격 증명은 라는 함수를 사용하여 생성되며make_s3_test_credentials()
,이 함수는 가짜 자격 증명 구조를 반환합니다.fn make_s3_test_credentials() -> s3::config::Credentials { s3::config::Credentials::new( "ATESTCLIENT", "astestsecretkey", Some("atestsessiontoken".to_string()), None, "", ) }
이러한 자격 증명은 실제로 전송되지 않으므로 유효하지 않아도 됩니다 AWS.
-
테스트가 필요한 함수를 호출하여 테스트를 실행합니다. 이 예제에서 해당 함수의 이름은 입니다
determine_prefix_file_size()
. 첫 번째 파라미터는 요청에 사용할 Amazon S3 클라이언트 객체입니다. 따라서를 사용하여 생성된 클라이언트를 지정StaticReplayClient
하여 네트워크를 통해 나가지 않고에서 요청을 처리합니다.let size = determine_prefix_file_size(client, "test-bucket", "test-prefix") .await .unwrap(); assert_eq!(19, size); replay_client.assert_requests_match(&[]);
에 대한 호출
determine_prefix_file_size()
이 완료되면 어설트를 사용하여 반환된 값이 예상 값과 일치하는지 확인합니다. 그런 다음StaticReplayClient
메서드assert_requests_match()
함수를 호출합니다. 이 함수는 기록된 HTTP 요청을 스캔하고 재생 클라이언트를 생성할 때 제공된ReplayEvent
객체 배열에 지정된 것과 모두 일치하는지 확인합니다.
GitHub에서 이러한 예제의 전체 코드를 볼