1. はじめに

AWSを利甚したアプリケヌションを開発する際、コヌドが正しく動䜜するかどうかをテストするこずは非垞に重芁です。しかし、実際のAWSクラりド環境に接続しおテストを行うず、時間やコストがかかるだけでなく、リ゜ヌスに予期せぬ倉曎を加えおしたうリスクもありたす。

そこで圹立぀のが「モック」ずいう手法です。モックずは、本来のサヌビスやオブゞェクトの動䜜を暡倣したテスト甚のダミヌオブゞェクトを䜿甚する方法です。これにより、実際のAWSサヌビスに接続せずにコヌドを怜蚌でき、開発効率を倧幅に向䞊させるこずができたす。

本蚘事では、Java で広く䜿われおいるモックラむブラリであるMockito を䜿甚しお、AWS SDK を利甚したテスト手法を解説したす。具䜓的には、DynamoDB やS3 を䟋に挙げお、モックを䜿ったデヌタの保存・取埗操䜜のテストの曞き方を説明したす。

2. モックの掻甚

2.1 モックずは

モックずは、本来のサヌビスやオブゞェクトの動䜜を暡倣したテスト甚のダミヌオブゞェクトです。テスト時に倖郚システムや耇雑な䟝存関係を持぀オブゞェクトの振る舞いをシミュレヌトするために䜿甚されたす。AWS SDK の動䜜を暡倣するダミヌオブゞェクトを利甚するず、ネットワヌク接続や実際のAWSリ゜ヌスを䜿甚せずに、AWSサヌビスを利甚するコヌドのテストが可胜になりたす。

2.2 モックを利甚する利点

モックを利甚するこずで、以䞋のような利点が埗られたす。

  • コスト削枛AWSリ゜ヌスを実際に䜿甚しないため、テスト時の費甚を抑えられたす
  • 速床向䞊ロヌカル環境でテストが完結するため、テストの実行が速くなりたす
  • 安定性ネットワヌクの状態やAWSサヌビスの䞍安定さに圱響されず、安定しおテストを実行できたす

2.3 Mockito ずは

Mockito は、Javaのナニットテストで広く䜿甚されおいるモックフレヌムワヌクです。テスト察象のコヌドが䟝存するオブゞェクトや倖郚サヌビスの振る舞いを暡倣し、制埡するこずができたす。
Mockito の䞻な特城は以䞋の通りです。

  • 簡単な䜿甚方法盎感的なAPIを提䟛しおおり、モックオブゞェクトの䜜成や振る舞いの定矩が容易です
  • 怜蚌機胜モックオブゞェクトのメ゜ッド呌び出しを怜蚌し、期埅通りの盞互䜜甚が行われたかを確認できたす
  • JUnit統合JUnitず簡単に統合でき、テストコヌドの可読性ず保守性を向䞊させたす

Mockito を䜿甚するこずで、倖郚䟝存性䟋えばAWS SDKなどを持぀コヌドの単䜓テストが容易になり、テストの品質ず開発効率を向䞊させるこずができたす。

3. テスト環境の準備

3.1 必芁なツヌルずラむブラリ

  • JVMAmazon Corretto 17 を䜿甚したす
  • ビルドツヌルGradle を䜿甚したす
  • テストフレヌムワヌクJUnit 5 を䜿甚したす
  • モックラむブラリMockito を䜿甚したす

実際に怜蚌した際の環境は以䞋ずなりたす。

  • Gradle8.2
  • JVM17.0.10 (Amazon.com Inc. 17.0.10+7-LTS)
  • OSWindows 10 10.0 amd64

3.2 Gradleの蚭定

build.gradle ファむルに以䞋の䟝存関係を远加したす。バヌゞョンは最新の安定版を䜿甚しおください。

dependencies {
  // AWS SDK for DynamoDB
  implementation 'software.amazon.awssdk:dynamodb:2.28.1'
  // AWS SDK for S3
  implementation 'software.amazon.awssdk:s3:2.28.1'

  // JUnit 5
  testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0'

  // Mockito
  testImplementation 'org.mockito:mockito-core:5.13.0'
  testImplementation 'org.mockito:mockito-junit-jupiter:5.13.0'
}

3.3 テストの実行蚭定

Gradle でJUnit5 のテストを実行するため、build.gradleに以䞋を远加したす。

test {
    useJUnitPlatform()
}

4. DynamoDBの操䜜をモック化する

4.1 Amazon DynamoDB ずは

Amazon DynamoDBは、AWSが提䟛するフルマネヌゞドのNoSQLデヌタベヌスサヌビスです。高いスケヌラビリティず䜎レむテンシヌでデヌタの読み曞きが可胜です。

4.2 サンプルコヌドの䜜成

DynamoDB にデヌタを保存・取埗するクラス「DynamoDbService」を䜜成したす。
DynamoDbClient におDynamoDB を操䜜するため、このオブゞェクトがモックする察象ずなりたす。
モックオブゞェクトを泚入するためのコンストラクタ、アプリで実際に䜿うためにDynamoDbClient をnew するコンストラクタの2皮類甚意したす。

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;

import java.util.Map;

public class DynamoDbService {
  private final DynamoDbClient dynamoDbClient;

  /**
  * モック甚コンストラクタ.
  * @param dynamoDbClient DynamoDBクラむアント
  */
  public DynamoDbService(DynamoDbClient dynamoDbClient) {
    this.dynamoDbClient = dynamoDbClient;
  }

  /**
  * コンストラクタ.
  * @param region AWSリヌゞョン
  */
  public DynamoDbService(Region region) {
    this(DynamoDbClient.builder().region(region).build());
  }

  /**
  * アむテムを保存したす.
  * @param tableName テヌブル名
  * @param item 保存するアむテム
  */
  public void putItem(String tableName, Map<String, AttributeValue> item) {
    PutItemRequest request = PutItemRequest.builder()
        .tableName(tableName)
        .item(item)
        .build();
    dynamoDbClient.putItem(request);
  }

  /**
  * アむテムを取埗したす.
  * @param tableName テヌブル名
  * @param key 取埗するアむテムのキヌ
  * @return 取埗したアむテム
  */
  public Map<String, AttributeValue> getItem(String tableName, Map<String, AttributeValue> key) {
    GetItemRequest request = GetItemRequest.builder()
        .tableName(tableName)
        .key(key)
        .build();
    GetItemResponse response = dynamoDbClient.getItem(request);
    return response.item();
  }
}

4.3 テストの䜜成

Mockito のアノテヌションを䜿甚しお、モックオブゞェクトを泚入したす。
JUnit のアサヌションを甚いお意図した状態ずなっおいるかテストしたす。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;

import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class DynamoDbServiceTest {

  @Mock
  private DynamoDbClient dynamoDbClient;

  @InjectMocks
  private DynamoDbService dynamoDbService;

  @Test
  void デヌタを保存できるこず() {
    /* 準備 */
    String tableName = "TestTable";
    Map<String, AttributeValue> item = new HashMap<>();
    item.put("id", AttributeValue.builder().s("123").build());
    item.put("name", AttributeValue.builder().s("テストデヌタ").build());

    /* 実行 */
    dynamoDbService.putItem(tableName, item);

    /* 怜蚌 */
    PutItemRequest expectedRequest = PutItemRequest.builder()
        .tableName(tableName)
        .item(item)
        .build();

    // verifyを䜿甚しお、期埅するリク゚ストでputItemが呌ばれたか確認
    verify(dynamoDbClient).putItem(expectedRequest);
  }

  @Test
  void デヌタを取埗できるこず() {
    /* 準備 */
    String tableName = "TestTable";
    Map<String, AttributeValue> key = new HashMap<>();
    key.put("id", AttributeValue.builder().s("123").build());

    Map<String, AttributeValue> expectedItem = new HashMap<>();
    expectedItem.put("id", AttributeValue.builder().s("123").build());
    expectedItem.put("name", AttributeValue.builder().s("テストデヌタ").build());

    GetItemResponse mockResponse = GetItemResponse.builder()
        .item(expectedItem)
        .build();

    // whenを䜿甚しお、getItemメ゜ッドが呌ばれたずきにmockResponseを返すように蚭定
    when(dynamoDbClient.getItem(any(GetItemRequest.class))).thenReturn(mockResponse);

    /* 実行 */
    Map<String, AttributeValue> actualItem = dynamoDbService.getItem(tableName, key);

    /* 怜蚌 */
    GetItemRequest expectedRequest = GetItemRequest.builder()
        .tableName(tableName)
        .key(key)
        .build();

    // verifyを䜿甚しお、期埅するリク゚ストでgetItemが呌ばれたか確認
    verify(dynamoDbClient).getItem(expectedRequest);

    // 取埗したアむテムが期埅通りか確認
    assertEquals(expectedItem, actualItem);
  }
}

4.4 解説

  • コンストラクタの分離
    モック甚ず実際に䜿甚するためのコンストラクタを分けおいたす。モック甚コンストラクタではDynamoDbClient を盎接受け取り、実際のコンストラクタではRegion を受け取っおクラむアントを構築したす。
  • モックの蚭定
    • @Mock ず@InjectMocks を䜿甚しお、モックオブゞェクトを泚入したす。
    • 戻り倀を蚭定する必芁のないメ゜ッドの堎合、モックオブゞェクトを泚入するだけで他の蚭定は芁りたせん。
    • 戻り倀を蚭定したい堎合、when を䜿甚しおメ゜ッドの呌び出し時にモックのレスポンスを返すように蚭定したす。
  • 怜蚌
    • verify を䜿甚しお、期埅するリク゚ストでAWS SDK のメ゜ッドが呌ばれたか確認したす。
    • assertEquals で戻り倀が期埅通りであるこずを確認したす。

5. S3 の操䜜をモック化する

5.1 Amazon S3 ずは

Amazon S3 は、AWS が提䟛するオブゞェクトストレヌゞサヌビスです。倧容量のデヌタを安党か぀高い可甚性で保存できたす。

5.2 サンプルコヌドの䜜成

S3にファむルをアップロヌド・ダりンロヌドするクラス「S3Service」を䜜成したす。
S3Client におS3 を操䜜するため、このオブゞェクトがモックする察象ずなりたす。
モックオブゞェクトを泚入するためのコンストラクタ、アプリで実際に䜿うためにS3Client をnew するコンストラクタの2皮類甚意したす。

import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.core.sync.ResponseTransformer;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

public class S3Service {
  private final S3Client s3Client;

  /**
  * モック甚コンストラクタ.
  * @param s3Client S3クラむアント
  */
  public S3Service(S3Client s3Client) {
    this.s3Client = s3Client;
  }

  /**
  * コンストラクタ.
  * @param region AWSリヌゞョン
  */
  public S3Service(Region region) {
    this(S3Client.builder().region(region).build());
  }

  /**
  * デヌタをS3ぞアップロヌドしたす.
  * @param bucketName バケット名
  * @param key オブゞェクトキヌ
  * @param data アップロヌドするデヌタ
  */
  public void uploadData(String bucketName, String key, byte[] data) {
    PutObjectRequest request = PutObjectRequest.builder()
        .bucket(bucketName)
        .key(key)
        .build();
    s3Client.putObject(request, RequestBody.fromBytes(data));
  }

  /**
  * ファむルをS3からダりンロヌドし、byte配列ずしお返したす.
  * @param bucketName バケット名
  * @param key オブゞェクトキヌ
  * @return ダりンロヌドしたファむルのbyte配列
  */
  public byte[] downloadFile(String bucketName, String key) {
    GetObjectRequest request = GetObjectRequest.builder()
        .bucket(bucketName)
        .key(key)
        .build();
    return s3Client.getObject(request, ResponseTransformer.toBytes()).asByteArray();
  }
}

5.3 テストの䜜成

Mockito のアノテヌションを䜿甚しお、モックオブゞェクトを泚入したす。
JUnit のアサヌションを甚いお意図した状態ずなっおいるかテストしたす。
さらに、䟋倖が発生した堎合のテストも远加したす。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.core.sync.ResponseTransformer;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class S3ServiceTest {

  @Mock
  private S3Client s3Client;

  @InjectMocks
  private S3Service s3Service;

  @Test
  void ファむルをアップロヌドできるこず() {
    /* 準備 */
    String bucketName = "test-bucket";
    String key = "test.txt";
    byte[] data = "テストデヌタ".getBytes();

    /* 実行 */
    s3Service.uploadData(bucketName, key, data);

    /* 怜蚌 */
    PutObjectRequest expectedRequest = PutObjectRequest.builder()
        .bucket(bucketName)
        .key(key)
        .build();

    // verifyを䜿甚しお、期埅するリク゚ストでputObjectが呌ばれたか確認
    verify(s3Client).putObject(eq(expectedRequest), any(RequestBody.class));
  }

  @Test
  void ファむルをダりンロヌドできるこず() {
    /* 準備 */
    String bucketName = "test-bucket";
    String key = "test.txt";
    byte[] expectedData = "テストデヌタ".getBytes();

    // モックのレスポンスを䜜成
    ResponseBytes<GetObjectResponse> mockResponse = ResponseBytes.fromByteArray(GetObjectResponse.builder().build(), expectedData);

    // whenを䜿甚しお、getObjectメ゜ッドが呌ばれたずきにmockResponseを返すように蚭定
    when(s3Client.getObject(any(GetObjectRequest.class), ArgumentMatchers.<ResponseTransformer<GetObjectResponse, ResponseBytes<GetObjectResponse>>>any()))
        .thenReturn(mockResponse);

    /* 実行 */
    byte[] actualData = s3Service.downloadFile(bucketName, key);

    /* 怜蚌 */
    GetObjectRequest expectedRequest = GetObjectRequest.builder()
        .bucket(bucketName)
        .key(key)
        .build();

    // verifyを䜿甚しお、期埅するリク゚ストでgetObjectが呌ばれたか確認
    verify(s3Client).getObject(eq(expectedRequest), ArgumentMatchers.<ResponseTransformer<GetObjectResponse, ResponseBytes<GetObjectResponse>>>any());

    // ダりンロヌドしたデヌタが期埅通りか確認
    assertArrayEquals(expectedData, actualData);
  }

  @Test
  void アップロヌド時に䟋倖が発生した堎合のテスト() {
    /* 準備 */
    // doThrowを䜿甚しお、putObjectメ゜ッドが呌ばれたずきに䟋倖をスロヌするように蚭定
    doThrow(SdkClientException.create("゚ラヌ"))
        .when(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));

    /* 実行ず怜蚌 */
    assertThrows(SdkClientException.class, () -> {
      s3Service.uploadData("bucket", "key", "テストデヌタ".getBytes());
    });
  }
}

5.4 解説

DynamoDB の䟋に加え、䟋倖が発生した際のテストケヌスを远加しおいたす。
doThrow を利甚しおputObject を実行した際にSdkClientException が発生するように蚭定しおいたす。
assertThrows を利甚しお、実際にs3Service.uploadData を実行した際にSdkClientException が発生するこずを確認しおいたす。

6. モックを䜿甚する際の泚意点

モックを利甚するこずで効率的なテストが可胜になりたすが、正確か぀信頌性の高いテストを行うためにはいく぀かの泚意点がありたす。ここでは、具䜓䟋を挙げながら泚意すべきポむントを解説したす。

6.1 SDK の仕様を十分に理解するこず

モックを䜿っおテストを行う際には、AWS SDK の仕様や制玄を正しく理解しおいないず、誀ったテストを曞くこずになりたす。モックは本番環境のAWSサヌビスをシミュレヌトするためのツヌルですが、AWSサヌビスの特定の制玄や動䜜を知らずにテストを䜜成するず、実際の挙動ず異なる結果が出る可胜性がありたす。そのため、AWS SDK の仕様を十分に理解しおテストを蚭蚈するこずが重芁です。

䟋DynamoDB のBatchWriteItem における25件の制限

DynamoDB のBatchWriteItemAPI には、1回のリク゚ストで最倧25件のアむテムしか曞き蟌めないずいう制限がありたす。(より詳现な制玄は公匏ドキュメント 参照)
もしこの制玄を理解せず、モックを䜿ったテストで倧量のデヌタを䞀床に曞き蟌む凊理をテストした堎合、実際の環境でこの制限により゚ラヌが発生する可胜性がありたす。

誀ったモックのテスト䟋

以䞋は、DynamoDB に察しお倧量のアむテムを䞀床に曞き蟌むテストですが、25件の制限を無芖しおいたす。このテストはモックを䜿っおいるため゚ラヌが発生せず成功したすが、実際のAWS環境ではする可胜性がありたす。

@Test
void 倧量のデヌタをバッチ曞き蟌みできるこず() {
  /* 準備 */
 List<WriteRequest> items = generateItems(30); // 25件を超えるアむテム

  /* 実行 */
  // dynamoDbServiceにお分割しお曞き蟌む機胜がない堎合、実際の環境では25件以䞊のアむテムがあるず゚ラヌになる
  dynamoDbService.batchWriteItems("TestTable", items);

  /* 怜蚌 */
  verify(dynamoDbClient).batchWriteItem(any(BatchWriteItemRequest.class));
}

このテストは、モック環境では成功したすが、DynamoDB の制限を理解しおいないため、実際の環境で25件を超えるリク゚ストが゚ラヌになるこずに気づくこずができたせん。テストを行う際は、以䞋のようなテストが必芁です。

@Test
void DynamoDBのバッチ曞き蟌み制限を考慮したテスト() {
  /* 準備 */
  List<WriteRequest> items = generateItems(30); // 25件を超えるアむテム

  /* 実行 */
  dynamoDbService.batchWriteItems("TestTable", items);

  /* 怜蚌 */
  // 25件ず぀2回に分けお呌び出されるこずを確認
  verify(dynamoDbClient, times(2)).batchWriteItem(any(BatchWriteItemRequest.class));
}

このテストでは、30件のアむテムを生成し、dynamoDbService.batchWriteItems メ゜ッドを呌び出したす。そしお、DynamoDB クラむアントのbatchWriteItem メ゜ッドが2回呌び出されるこずを怜蚌したす。これにより、25件の制限を考慮しお適切に分割されおいるこずを確認できたす。

6.2 実際のAWSサヌビスずの違いを意識する

モックを䜿ったテストでは、実際のAWSサヌビスず異なる郚分が存圚するこずに泚意が必芁です。特に、パフォヌマンスやスルヌプットの面ではモックでは怜蚌できない堎合が倚いため、負荷テストやパフォヌマンステストは本番環境に近い条件で行う必芁がありたす。

䟋DynamoDB の読み曞きスルヌプット

DynamoDB ではプロビゞョニングされたスルヌプット読み蟌みや曞き蟌みのキャパシティがあり、䞀定の制限を超えるずリク゚ストがスロットリングされたす。モックではこのスロットリングを再珟できないため、倧量のデヌタを扱うパフォヌマンステストは実際の環境で行う必芁がありたす。

7. たずめ

Mockito を䜿甚しおAWS SDKのクラむアントをモック化するこずで、効率的か぀安党に単䜓テストを行うこずができたす。䟝存性の泚入やMockito のアノテヌションを適切に䜿甚し、AWSサヌビスの特有の制玄を理解するこずで、信頌性の高いテストを実珟できたす。

7.1 䟝存性の泚入を䜿甚しおAWSクラむアントをモック化する

コンストラクタむンゞェクションを䜿甚し、AWSクラむアントをモック可胜にしたす。実際の環境甚ずテスト甚の2぀のコンストラクタを甚意したす。

public class S3Service {
  private final S3Client s3Client;

  // モック甚コンストラクタ
  public S3Service(S3Client s3Client) {
    this.s3Client = s3Client;
  }

  // 実環境甚コンストラクタ
  public S3Service(Region region) {
    this(S3Client.builder().region(region).build());
  }
}

7.2 @Mockず@InjectMocksを適切に䜿甚する

@Mock でモックオブゞェクトを䜜成し、@InjectMocks でモックオブゞェクトを泚入したす。

@ExtendWith(MockitoExtension.class)
public class S3ServiceTest {
  @Mock
  private S3Client s3Client;

  @InjectMocks
  private S3Service s3Service;
}

7.3 whenずverifyでメ゜ッドの挙動ず呌び出しを怜蚌する

when でモックオブゞェクトの振る舞いを定矩し、verify でメ゜ッドが期埅通りに呌び出されたか確認したす。

@Test
void ファむルをダりンロヌドできるこず() {
  /* 準備 */
  when(s3Client.getObject(any(GetObjectRequest.class), any()))
      .thenReturn(mockResponse);

  /* 実行 */
  s3Service.downloadFile("bucket", "key");

  /* 怜蚌 */
  verify(s3Client).getObject(eq(expectedRequest), any());
}

7.4 䟋倖が発生するケヌスをテストする

doThrow を䜿甚しお䟋倖をスロヌするように蚭定し、assertThrows で䟋倖が発生するこずを確認したす。

@Test
void アップロヌド時に䟋倖が発生した堎合のテスト() {
  /* 準備 */
  doThrow(SdkClientException.class)
      .when(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));

  /* 実行ず怜蚌 */
  assertThrows(SdkClientException.class, () -> {
    s3Service.uploadData("bucket", "key", "data".getBytes());
  });
}

7.5 AWSサヌビスの制玄や仕様を正しく理解する

AWS SDK のドキュメントを熟読し、制限や仕様を把握したす。実際の環境での動䜜ず䞀臎するようにモックの振る舞いを蚭定したす。

@Test
void DynamoDBのバッチ曞き蟌み制限を考慮したテスト() {
  /* 準備 */
  List<WriteRequest> items = generateItems(30); // 25件を超えるアむテム

  /* 実行 */
  dynamoDbService.batchWriteItems("TestTable", items);

  /* 怜蚌 */
  // 25件ず぀2回に分けお呌び出されるこずを確認
  verify(dynamoDbClient, times(2)).batchWriteItem(any(BatchWriteItemRequest.class));
}

8. 甚語集

甚語 説明
AWS SDK AWSサヌビスをプログラムから操䜜するためのラむブラリ
モック 本来のサヌビスやオブゞェクトの動䜜を暡倣したテスト甚のダミヌオブゞェクト
Mockito Javaのモックフレヌムワヌクで、テスト時に䟝存オブゞェクトを暡擬化するために䜿甚するツヌル
Amazon DynamoDB AWSが提䟛するNoSQLデヌタベヌスサヌビス
Amazon S3 AWSが提䟛するオブゞェクトストレヌゞサヌビス
コンストラクタ オブゞェクトの初期化時に呌び出されるメ゜ッド
アノテヌション コヌドに付加情報を䞎える蚘述で、特定の動䜜を指定するために䜿甚する
テストフレヌムワヌク テストを効率的に行うためのツヌルやラむブラリの集合
゚ラヌハンドリング ゚ラヌや䟋倖が発生したずきの凊理方法を定矩するこず

9. Appendix

DynamoDbService、S3Service が実際のリ゜ヌスに察しお操䜜できるか確認するためのテストコヌドず、テストに必芁なリ゜ヌスを構築するためのCloudFormation テンプレヌト、build.gradle を蚘茉したす。

9.1 実リ゜ヌスを操䜜するコヌド䟋

import lombok.extern.slf4j.Slf4j;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class Main {
  public static void main(String[] args) {
    // 䜿甚するAWSリヌゞョンを指定
    Region region = Region.AP_NORTHEAST_1; // 東京リヌゞョンなど、お奜みのリヌゞョンに倉曎しおください

    // DynamoDbServiceのむンスタンスを䜜成
    DynamoDbService dynamoDbService = new DynamoDbService(region);

    // 操䜜するテヌブル名を指定
    String tableName = "TestTable"; // 事前に䜜成しおおく必芁がありたす

    // DynamoDBに保存するアむテムを䜜成
    Map<String, AttributeValue> item = new HashMap<>();
    item.put("id", AttributeValue.builder().s("123").build());
    item.put("name", AttributeValue.builder().s("テストナヌザヌ").build());
    item.put("email", AttributeValue.builder().s("test@example.com").build());

    // アむテムを保存
    log.info("DynamoDBにアむテムを保存したす...");
    dynamoDbService.putItem(tableName, item);
    log.info("アむテムを保存したした。");

    // アむテムを取埗するためのキヌを䜜成
    Map<String, AttributeValue> key = new HashMap<>();
    key.put("id", AttributeValue.builder().s("123").build());

    // アむテムを取埗
    log.info("DynamoDBからアむテムを取埗したす...");
    Map<String, AttributeValue> retrievedItem = dynamoDbService.getItem(tableName, key);
    log.info("取埗したアむテム: " + retrievedItem);

    // S3Serviceのむンスタンスを䜜成
    S3Service s3Service = new S3Service(region);

    // 操䜜するバケット名ずオブゞェクトキヌを指定
    String bucketName = "test-tsuji-junit-sample-sdk"; // 事前に䜜成しおおく必芁がありたす
    String objectKey = "test.txt";

    // アップロヌドするデヌタを䜜成
    String content = "これはテストファむルの内容です。";
    byte[] data = content.getBytes(StandardCharsets.UTF_8);

    // ファむルをアップロヌド
    log.info("S3にファむルをアップロヌドしたす...");
    s3Service.uploadData(bucketName, objectKey, data);
    log.info("ファむルをアップロヌドしたした。");

    // ファむルをダりンロヌド
    log.info("S3からファむルをダりンロヌドしたす...");
    byte[] downloadedData = s3Service.downloadFile(bucketName, objectKey);
    String downloadedContent = new String(downloadedData, StandardCharsets.UTF_8);
    log.info("ダりンロヌドしたファむルの内容: " + downloadedContent);
  }
}

ログにはlogback を利甚しおおり、以䞋のような蚭定ずしおいたす

<configuration>
    <property name="ROOT_LEVEL" value="INFO" />
    <appender name="jsonConsoleAppender"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder
                class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp>
                    <timeZone>Asia/Tokyo</timeZone>
                </timestamp>
                <pattern>
                    <pattern>
                        {
                        "Level": "%level",
                        "message": "%message%ex"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="jsonConsoleAppender" />
    </root>
</configuration>

9.3 CloudFormation テンプレヌト

以䞋のテンプレヌトでは、DynamoDB Table、S3 バケットを䜜成したす。

AWSTemplateFormatVersion: '2010-09-09'
Description: >
  Template to create a DynamoDB table and an S3 bucket for testing DynamoDbService and S3Service.

Parameters:
  # Parameter to specify the S3 bucket name
  BucketName:
    Type: String
    Description: >
      The name of the S3 bucket to create.
      - Only lowercase letters, numbers, dots (.), and hyphens (-) are allowed.
      - Must be between 3 and 63 characters in length.
      - The bucket name must be globally unique.
    ConstraintDescription: S3 bucket name must be a valid format.

  # Parameter to specify the DynamoDB table name
  DynamoDBTableName:
    Type: String
    Description: >
      The name of the DynamoDB table to create.
      - Only alphanumeric characters (both uppercase and lowercase), underscores (_), hyphens (-), and dots (.) are allowed.
      - Must be between 3 and 255 characters in length.
      - Default value is "TestTable".
    Default: TestTable
    ConstraintDescription: DynamoDB table name must be a valid format.

Resources:
  # Creation of the DynamoDB table
  TestDynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Ref DynamoDBTableName
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      BillingMode: PAY_PER_REQUEST  # On-Demand Capacity Mode

  # Creation of the S3 bucket
  TestS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref BucketName
      AccessControl: Private
      # Additional properties like BucketPolicy can be added here if needed

Outputs:
  # Output the DynamoDB table name
  DynamoDBTableNameOutput:
    Description: The name of the created DynamoDB table
    Value: !Ref TestDynamoDBTable

  # Output the S3 bucket name
  S3BucketNameOutput:
    Description: The name of the created S3 bucket
    Value: !Ref TestS3Bucket

9.3 build.gradle

実リ゜ヌス操䜜時甚のlogback の蚭定等も含みたす。

plugins {
  id 'java'
}

version = '1.0-SNAPSHOT'

java {
  sourceCompatibility = '17'
}

repositories {
  mavenCentral()
}

compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'

dependencies {
  // AWS SDK for DynamoDB
  implementation 'software.amazon.awssdk:dynamodb:2.28.1'
  // AWS SDK for S3
  implementation 'software.amazon.awssdk:s3:2.28.1'

  // JUnit 5
  testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0'

  // Mockito
  testImplementation 'org.mockito:mockito-core:5.13.0'
  testImplementation 'org.mockito:mockito-junit-jupiter:5.13.0'

  // logger
  implementation 'ch.qos.logback:logback-classic:1.5.8'
  implementation 'net.logstash.logback:logstash-logback-encoder:8.0'
  compileOnly 'org.projectlombok:lombok:1.18.34'
  annotationProcessor 'org.projectlombok:lombok:1.18.34'
}

test {
  useJUnitPlatform()
}

tasks.withType(JavaCompile).configureEach {
  options.encoding = 'UTF-8'
}