App Engineerの開発ノート

AWS、Flutterや開発活動に役立つツール作りなど

DynamoDBへアクセスを行うJavaのLambda関数の単体テスト実施方法

f:id:Simoroid:20211011015609p:plain
DynamoDBへ書き込み・読み込みを行う関数を効率良く単体テストする方法です。
テストデータの事前投入を簡易化することが今回のメインです。

データ投入に今回はエクセルファイル(拡張子がxlsx)を使用します。
エクセルファイルを使用するメリットとして、その試験に使用したデータがブックでまとめられて分かりやすいという点があると思います。
CSVXMLを使用するとファイルサイズが軽量だと思いますがファイルが分散されて割りと探すのが面倒だということがありました。
テストケースとテストデータ(エクセルファイル)が1対1になっていて欲しいという思いです。
f:id:Simoroid:20211011235230p:plain:w240

■事前準備

全体のソースコードは下記で取得可能です。
ソースコードaws公式が公開しているシンプルなlambdaアプリの
プロジェクトであるjava-basicをべースにして改良しています。

git clone https://github.com/YasuoFromDeadScream/lambda_java_unittest.git

また今回DynamoDBへのアクセスは全てローカル環境で実施する必要であるため、Localstackを使用します。
ikodatech.com

■実装ポイント

1.テスト実施クラスの親クラス - DynamoDBTestBase
エクセルファイルに基づいたデータ投入を行う処理を行うベースとなるクラス。
WorkbookFactoryクラスを使用してエクセルファイルを読み込みます。

    String path = "/data/" + excelFileName;
    try {
      in = getClass().getResourceAsStream(path);
      wb = WorkbookFactory.create(in);

テーブルとGSIを作成します。

    CreateTableRequest createTableRequest = dynamoDBMapper.generateCreateTableRequest(pojoClass);
    createTableRequest.setProvisionedThroughput(new ProvisionedThroughput(5L, 1L));
    // GSIの作成
    if (CollectionUtils.isNotEmpty(createTableRequest.getGlobalSecondaryIndexes())) {
      createTableRequest
          .getGlobalSecondaryIndexes()
          .forEach(
              globalSecondaryIndex -> {
                globalSecondaryIndex.setProvisionedThroughput(new ProvisionedThroughput(1L, 1L));
                globalSecondaryIndex.setProjection(
                    new Projection().withProjectionType(ProjectionType.KEYS_ONLY));
              });
    }
    // テーブル作成
    try {
      amazonDynamoDB.createTable(createTableRequest);

テーブルにレコードを詰め込んでいきます。

      Table table = dynamoDB.getTable(sheet.getSheetName());
      table.putItem(item);

2.テスト実施クラス - InvokeTest
DynamoDBTestBaseを継承します。
junitのバージョンに応じてアノテーションは変わってきますが、
junit5では@BeforeEachと@AfterEach使用して試験実施の前後でDBにデータを入れたり消したりします。

  @BeforeEach
  protected void init() {
    super.init();
  }

  @AfterEach
  protected void tearDown()  {
    try {

テストメソッドの適当なタイミングでデータ投入メソッドを呼び出します。
引数はファイル名を指定するのみです。

  @Test
  void invokeTest() throws IOException, ClassNotFoundException {
    logger.info("Invoke TEST");

    insertTestData("sample.xlsx");

3.Model(POJO)クラス
各テーブルのデータを紐づけるためのModelクラスを作成します。
アノテーションを使用してDynamoDBのキーや項目を設定します。

@DynamoDBTable(tableName = "UserInfo")
public class UserInfo {

    @DynamoDBHashKey(attributeName = "UserId")
    @DynamoDBIndexRangeKey(
            globalSecondaryIndexName = "UserInfoIndex1")
    private String userId;

    @DynamoDBRangeKey(attributeName = "UserName")
    private String userName;
■使用方法

投入するエクセルファイルを下記ディレクトリに配置します。
f:id:Simoroid:20211011012724p:plain
各シート名が作成した前述のpojoクラス名と一致している必要があります。
f:id:Simoroid:20211011013004p:plain
あとはjunitに基づいたテストを実施します。

下記のようにscanを使用してDBに登録されたレコードを参照します。

    String result = handler.handleRequest(event, context);

    List<UserInfo> UserInfoList = new ArrayList<>();
    Map<String, AttributeValue> lastKeyEvaluated = null;
    do {
      DynamoDBScanExpression scanExpression = new DynamoDBScanExpression()
              .withExclusiveStartKey(lastKeyEvaluated);
      ScanResultPage<UserInfo> resultResult = dynamoDBMapper.scanPage(UserInfo.class,scanExpression);
      if(resultResult.getCount() > 0){
        UserInfoList.addAll(resultResult.getResults());
      }
      lastKeyEvaluated = resultResult.getLastEvaluatedKey();
    } while (lastKeyEvaluated != null);

   logger.info(UserInfoList.stream().findFirst().toString());
   assertEquals(UserInfoList.get(0).getAge(),22);

f:id:Simoroid:20211011013514p:plain
コンソールにテーブルの内容が表示されて、
処理結果と期待動作の突合せを行い、処理対象が上手くいっているかを確認します。

以上でDynamoDBにアクセスする関数の単体テスト方法を確立できました。
今後はS3やAuroraなどAWSの色々なサービスをテストできるよう拡張できればいいな。

ここまで 読んで頂きありがとうございました。
よければフォローお願いします!