翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。
AWS SDK for Rust プロジェクトにユニットテストを実装する方法は多数ありますが、いくつかの方法をお勧めします。
-
AWS Smithy ランタイムの
StaticReplayClient
を使用して、通常は によって使用される標準 HTTP クライアントの代わりに使用できるフェイク HTTP クライアントを作成します AWS のサービス。このクライアントは、ネットワーク経由でサービスと通信するのではなく、指定した HTTP レスポンスを返します。これにより、テスト目的で既知のデータがテスト用に取得されます。
モコールを使用してモックを自動的に生成する
mockall
クレート automock
の一般的な を使用して、テストに必要なモック実装の大部分を自動的に生成できます。
この例では、 というカスタムメソッドをテストしますdetermine_prefix_file_size()
。このメソッドは、Amazon S3 を呼び出すカスタムlist_objects()
ラッパーメソッドを呼び出します。をモックすることでlist_objects()
、Amazon S3 に実際に連絡することなくdetermine_prefix_file_size()
メソッドをテストできます。
-
プロジェクトディレクトリのコマンドプロンプトで、依存関係として
mockall
木箱を追加します。$
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 ラッパー構造の 2 つの実装のうちどれを使用するかを決定するコードを追加します。
-
ネットワーク経由で 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
タイプが使用されていない場合、問題を報告しないように linter に指示します。 -
条件付き は、テストが有効になっている場合、
automock
属性を設定する必要がある#[cfg_attr(test, 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 } }
-
-
という名前のモジュールでテスト関数を作成します
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) }
タイプS3
は、ラップされた SDK for Rust 関数を呼び出して、HTTP リクエストを行うMockS3Impl
ときに S3Impl
と の両方をサポートするために使用されます。によって自動的に生成されたモックは、テストが有効になっているときにテストの失敗mockall
を報告します。
これらの例の完全なコードは、GitHub
静的再生を使用して HTTP トラフィックをシミュレートする
aws-smithy-runtime
クレートには、 というテストユーティリティクラスが含まれていますStaticReplayClient
を初期化するときはStaticReplayClient
、HTTP リクエストとレスポンスのペアのリストをReplayEvent
オブジェクトとして指定します。テストの実行中に、各 HTTP リクエストが記録され、クライアントはイベントリストReplayEvent
の次の にある次の HTTP レスポンスを HTTP クライアントのレスポンスとして返します。これにより、既知のデータを使用し、ネットワーク接続なしでテストを実行できます。
静的再生の使用
静的再生を使用するには、ラッパーを使用する必要はありません。代わりに、テストで使用するデータに対する実際のネットワークトラフィックがどのようになるかを判断し、SDK が AWS のサービス クライアントからリクエストを発行するたびにStaticReplayClient
使用するトラフィックデータを に提供します。
注記
予想されるネットワークトラフィックを収集するには、 AWS CLI や多数のネットワークトラフィックアナライザー、パケットスニッファツールなど、いくつかの方法があります。
-
予想される HTTP リクエストと返されるレスポンスを指定する
ReplayEvent
オブジェクトのリストを作成します。 -
前のステップで作成した HTTP トランザクションリスト
StaticReplayClient
を使用して を作成します。 -
AWS クライアントの設定オブジェクトを作成し、 を
Config
オブジェクトのStaticReplayClient
として指定しますhttp_client
。 -
前のステップで作成した設定を使用して、 AWS のサービス クライアントオブジェクトを作成します。
-
を使用するように設定されたサービスオブジェクトを使用して、テストするオペレーションを実行します
StaticReplayClient
。SDK が API リクエストを送信するたびに AWS、リスト内の次のレスポンスが使用されます。注記
送信されたリクエストが
ReplayEvent
オブジェクトのベクトルのレスポンスと一致しない場合でも、リスト内の次のレスポンスは常に返されます。 -
必要なリクエストがすべて実行されたら、
StaticReplayClient.assert_requests_match()
関数を呼び出して、SDK によって送信されたリクエストがReplayEvent
オブジェクトのリスト内のリクエストと一致することを確認します。
例
前の例で同じdetermine_prefix_file_size()
関数のテストを見てみましょう。ただし、モックの代わりに静的再生を使用します。
-
プロジェクトディレクトリのコマンドプロンプトで、依存関係として
aws-smithy-runtime
木箱を追加します。 $
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 リクエストオブジェクトと、 が AWS のサービス 通常応答する情報を表す HTTP レスポンスオブジェクトが含まれます。これらのイベントは、 への呼び出しに渡されます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