[検証] Amazon Q Developerのドキュメント＆ユニットテスト自動生成機能はどこまで手動作成クオリティに近づくのか

# はじめに

こちらは[NRI OpenStandia Advent Calendar 2024](https://qiita.com/advent-calendar/2024/nri-openstandia)の10日目の記事です。
AWS re:Invent 2024にて、Amazon Q Developerのアップデートとして「コードに基づいたドキュメント自動生成」機能と「コードに基づいたユニットテスト自動生成」機能が発表されました。

https://aws.amazon.com/jp/about-aws/whats-new/2024/12/amazon-q-developer-automatic-unit-test-generation/?nc1=h_ls

https://aws.amazon.com/jp/about-aws/whats-new/2024/12/amazon-q-developer-generate-documentation-source-code/?nc1=h_ls

Amazon Q Developerは、AWS社のコードやドキュメントをもとに学習が行われています。つまり、「自動生成されるドキュメント」と「自動生成されるユニットテスト」はAWS本家が手動作成しているドキュメントやユニットテストと同じようなクオリティになるのではないでしょうか。

そこで、今回はAWS社から出しているOSSを題材として、どのようなドキュメントやユニットテストが自動生成されていくかを見ていこうと思います。

# Amazon Q Developerとは

[Amazon Q Developer](https://aws.amazon.com/jp/q/developer/)は運用開発者がVSCodeやIntelliJ等のコードエディタを用いてコーディングする際に、生成AIによるアシストをしてくれるツールです。

アシスト例はこちらです。

- コーディング時に、リアルタイムでコードを補完
- チャット上で自然言語（例：日本語、英語）を用いてQA回答
- `/dev`コマンドをトリガーとして、リアルタイムで実装コードを提案
- `/transform`コマンドをトリガーとして、Javaのレガシーコードのアップデート方法を提示
- `/review`コマンドをトリガーとして、コードレビュー機能にて脆弱性やリスクを特定し、緩和策も提示
- `/doc`コマンドをトリガーとして、ドキュメントを自動生成（※本記事での検証１）
- `/test`コマンドをトリガーとして、ユニットテストを自動生成（※本記事の検証２）

# 検証

## 検証に用いるAWS本家のソースコード

Java検証用にServerless Java container、Python検証用にaws-cliをピックアップしました。

https://github.com/aws/serverless-java-container

https://github.com/aws/aws-cli

## 検証方法

1. 該当コードをcloneします
1. 自動生成する予定のファイルを全部削除します
    - 検証1：README、フォルダ内に存在するドキュメント（例：docフォルダ配下すべて）
    - 検証2：フォルダ内に存在するテスト（例：testフォルダ配下すべて）
1. `/doc`コマンド or `/test`コマンドを実行します
    - `/doc`コマンドはコマンドを叩いたあとにREADMEを自動生成したいリポジトリを選択します
    - `/test`コマンドはユニットテストを自動作成したいファイルをあらかじめアクティブ状態にしてからコマンドを叩く必要があります  

## 検証1：`/doc`コマンドをトリガーとして、ドキュメントを自動生成

### Serverless Java containerの検証結果

Serverless Java containerの実行結果はこちらです。[公式のREADME](https://github.com/aws/serverless-java-container/blob/main/README.md)とは異なる全く異なるアウトプットであることから、「Amazon Q Developerは過学習していない（個別最適化されないように、大量のデータを学習している）」ということがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534839/b8aeaba6-0528-811a-a8c5-14539e704868.png)

中身を詳しく見ていきます。まずREADMEの目次構成は下記のようになっていました。READMEの構成として、存在すると便利な項目が綺麗に並んでおり、READMEとしての有用性が高くなっております。

```README.md の目次構成
AWS Serverless Java Container（タイトル）
├　Repository Structure（リポジトリ内のモジュール構成）
├　Usage Instructions（使い方）
│├　Installation（インストール方法）
│├　Getting Started（インストール後の使い方）
│├　Configuration（使う際の設定について）
│├　Common Use Cases（主なユースケース）
│├　Testing（テスト実行方法）
│└　Troubleshooting（トラブルシューティング）
├　Data Flow（データの流れ）
├　Deployment（デプロイ方法）
└　Infrastructure（インフラ構成）
```
 
それぞれの項目も、コマンドベースで丁寧に記載がされていました。また、内容もおおむね「一般的に正しいことが多い」回答となっておりました。

例えば、トラブルシューティングの章は一般的な対応方法が記載されています。インストールの章も同じくです。
なお、インストールバージョンに関しては、「Prerequisites」で指定されているバージョンよりも古いバージョン（例：Java17）も本OSSは利用可能と思われます。と言いますのも、本OSSは[githubのワークフロー設定（.github/workflows/continuous-integration-workflow.yml）](https://github.com/aws/serverless-java-container/blob/main/.github/workflows/continuous-integration-workflow.yml#L21)にてJava17を指定しています。

ただ最新LTSであるJava21を推奨しているのは個人的には悪くないと思います。アシスタントする生成AI側で明確に推奨バージョンを判別できない場合、一般的には新しいLTSを使う方が推奨されるため、回答として「明確な」間違いとなる可能性が低いためです。

```README.md（一部抜粋：Installation）
### Installation

Prerequisites:
- Java 21 or later
- Maven 3.6.3 or later
- AWS CLI configured with appropriate credentials

To install the AWS Serverless Java Container, clone the repository and build the project:

---
git clone https://github.com/aws/serverless-java-container.git
cd serverless-java-container
mvn clean install
---
```

```README.md（一部抜粋：Troubleshooting）
### Troubleshooting

Common issues and solutions:

1. ClassNotFoundException:
   - Ensure all dependencies are correctly specified in your `pom.xml` file.
   - Check that the `Handler` property in your `template.yml` file points to the correct class.

2. API Gateway errors:
   - Verify that your API routes are correctly defined in your application code.
   - Check the CloudWatch logs for your Lambda function for any error messages.

3. Performance issues:
   - Consider using the `async` initialization option for faster cold starts.
   - Optimize your application code and reduce the number of dependencies.
```

### aws-cliの検証結果

もう一つの検証対象であるaws-cliも見ていきます。READMEの目次構成は下記の通りです。

この目次は、Serverless Java containerのREADME自動生成の結果と完全一致はしていないものの、かなり似ております。このことから、裏側で動いている生成AI側である程度READMEの目次フォーマットを決めていることが推測できます。

```README.md の目次構成
AWS Command Line Interface (CLI)（タイトル）
├　Repository Structure（リポジトリ内のフォルダ構成、主要なファイル）
├　Usage Instructions（使い方）
│├　Installation（インストール方法）
│├　Configuration（使う際の設定について）
│├　Basic Usage（インストール後の基本的な使い方）
│└　Getting Help（使い方のヘルプコマンド）
├　Data Flow（データの流れ）
├　Deployment（デプロイされている形式）
├　Infrastructure（インフラ構成）
├　Testing & Quality（テスト方法）
├　Troubleshooting（トラブルシューティング）
└　Performance Optimization（パフォーマンスの最適化）
```

記載内容もおおむね正しいです。Repository Structureは検証のためにdocフォルダを削除しているためそれが記載されていないのは想定通りです。ドキュメント以外のフォルダは主要なものを問題なくピックアップできております。（awscliフォルダ配下にtopicsフォルダがあることを紹介できておりませんが、これは後述する「READMEの修正」で修正可能な範囲です。）

```README.md（一部抜粋：Repository Structure）
## Repository Structure

The repository is organized as follows:

- `aws-cli/`: Main directory containing the AWS CLI source code
  - `awscli/`: Core AWS CLI implementation
    - `customizations/`: Custom commands and behaviors for specific AWS services
    - `data/`: Configuration data files
    - `examples/`: Example files and templates
  - `bin/`: Executable scripts
  - `scripts/`: Utility scripts for development and testing
  - `tests/`: Test suite for the AWS CLI

Key Files:
- `aws-cli/awscli/__main__.py`: Entry point for the AWS CLI
- `aws-cli/awscli/clidriver.py`: Main driver for the CLI functionality
- `aws-cli/setup.py`: Setup script for installing the AWS CLI
```

また、InstallationにおいてPythonのバージョンが3.8以上である必要があることは、[setup.pyの記載](https://github.com/aws/aws-cli/blob/develop/setup.py#L51)とも一致します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534839/ca02ffaa-f70c-cce5-fdaa-8849f2daf1a3.png)

```README.md（一部抜粋：Installation）
### Installation

1. Ensure you have Python 3.8 or later installed.
2. Install the AWS CLI using pip:

---
pip install awscli
---
```

実際のところは、[aws-cliのインストール方法は多岐にわたります。](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html#getting-started-install-instructions)これらの情報も加味するには、別途手動で追加することになりそうです。

なお、ここまで初回生成のREADMEを見ていきましたが、このREADMEはプロンプト指示をだすことによる修正が可能です。
例えば、セクションの追加や削除、特定の文章自体の修正が可能です。この際、変更したいセクションや追加削除したいコンテンツ、文章の修正方針は明確に記述する必要があります。また、参照する必要があるものは、参照先がコードである必要があります。

また、READMEをより良いものとするためにAWS公式からベストプラクティスが提供されています。
https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/doc-generation-best-practices.html

## 検証2：`/test`コマンドをトリガーとして、ユニットテストを自動生成

`/test`コマンドを実行すると下記のような画面を見ることができます。なお、2024/12現在はJavaとPythonのみが対応となっております。ここでは文量の都合でaws-cliの検証結果のみ紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534839/9aa4af4f-5f49-c372-11a8-c9d2aadd0060.png)

### aws-cliの検証結果

下記ファイルの単体テストを行いました。
https://github.com/aws/aws-cli/blob/develop/awscli/customizations/s3/fileinfo.py

該当ファイルには、下記4つのメソッドがあります。
- `__init__(self, src, dest, compare_key, size,
                 last_update, src_type, dest_type,
                 operation_name, client, parameters,
                 source_client, is_stream,
                 associated_response_data)`
- `is_glacier_compatible(self)`
- `_is_glacier_object(self, response_data)`
- `_is_restored(self, response_data)`

生成されたテストケース一覧は下記の通りです。対象メソッド1つ1つに対して、正常ケースだけでなく異常ケースもしっかり検討されており、パターンも多く揃っております。（※番号1と3でまったく同じで何も記載のないテストが生成されていますが、この程度の余分なテストは削除する手間もたいしてないので、問題ないかと思っております。）

|＃|テストメソッド|テストの説明|テスト対象メソッド|
|---|---|---|---|
|1|test__is_glacier_object_1|既存のテスト（変更なし）|_is_glacier_object|
|2|test__is_glacier_object_2|非Glacierオブジェクトの場合にFalseを返すことをテスト|_is_glacier_object|
|3|test__is_glacier_object_3|既存のテスト（変更なし）|_is_glacier_object|
|4|test__is_restored_with_case_sensitive_restore_value|大文字小文字を区別するRestore値でのテスト|_is_restored|
|5|test__is_restored_with_empty_input|空の入力での_is_restoredメソッドのテスト|_is_restored|
|6|test__is_restored_with_empty_restore_value|空のRestore値での_is_restoredメソッドのテスト|_is_restored|
|7|test__is_restored_with_incorrect_type|不正な入力タイプでの_is_restoredメソッドのテスト|_is_restored|
|8|test__is_restored_with_invalid_restore_value|無効なRestore値での_is_restoredメソッドのテスト|_is_restored|
|9|test__is_restored_with_missing_restore_key|Restoreキーが欠落している場合の_is_restoredメソッドのテスト|_is_restored|
|10|test__is_restored_with_none_input|None入力での_is_restoredメソッドのテスト|_is_restored|
|11|test__is_restored_with_ongoing_request|進行中のリクエストでの_is_restoredメソッドのテスト|_is_restored|
|12|test__is_restored_with_partial_restore_value|部分的なRestore値での_is_restoredメソッドのテスト|_is_restored|
|13|test_init_with_all_parameters|すべてのパラメータを指定してFileInfoを初期化するテスト|`__init__`|
|14|test_init_with_default_parameters|デフォルトパラメータでのFileInfo初期化テスト|`__init__`|
|15|test_init_with_empty_src|空のsrcパラメータでの初期化テスト|`__init__`|
|16|test_init_with_invalid_associated_response_data|無効なassociated_response_dataでの初期化テスト|`__init__`|
|17|test_init_with_invalid_dest_type|無効なdest_typeパラメータでの初期化テスト|`__init__`|
|18|test_init_with_invalid_is_stream|無効なis_streamパラメータでの初期化テスト|`__init__`|
|19|test_init_with_invalid_last_update|無効なlast_updateパラメータでの初期化テスト|`__init__`|
|20|test_init_with_invalid_operation_name|無効なoperation_nameパラメータでの初期化テスト|`__init__`|
|21|test_init_with_invalid_parameters_type|無効なparameters型での初期化テスト|`__init__`|
|22|test_init_with_invalid_src_type|無効なsrc_typeパラメータでの初期化テスト|`__init__`|
|23|test_init_with_negative_size|負のsizeパラメータでの初期化テスト|`__init__`|
|24|test_init_with_parameters|パラメータを指定してFileInfoを初期化するテスト|`__init__`|
|25|test_is_glacier_compatible_1|Glacierオブジェクトのダウンロード操作の互換性テスト|is_glacier_compatible|
|26|test_is_glacier_compatible_3|Glacierオブジェクトの削除操作の互換性テスト|is_glacier_compatible|
|27|test_is_glacier_compatible_deep_archive_object|Deep Archiveオブジェクトの互換性テスト|is_glacier_compatible|
|28|test_is_glacier_compatible_glacier_object_copy|Glacierオブジェクトのコピー操作の互換性テスト|is_glacier_compatible|
|29|test_is_glacier_compatible_glacier_object_download|Glacierオブジェクトのダウンロード操作の互換性テスト|is_glacier_compatible|
|30|test_is_glacier_compatible_glacier_object_move_local_source|ローカルソースのGlacierオブジェクトの移動操作の互換性テスト|is_glacier_compatible|
|31|test_is_glacier_compatible_glacier_object_move_s3_source|S3ソースのGlacierオブジェクトの移動操作の互換性テスト|is_glacier_compatible|
|32|test_is_glacier_compatible_glacier_upload|Glacierオブジェクトのアップロード操作の互換性テスト|is_glacier_compatible|
|33|test_is_glacier_compatible_invalid_storage_class|無効なストレージクラスでの互換性テスト|is_glacier_compatible|
|34|test_is_glacier_compatible_move_from_s3_glacier|S3 Glacierからの移動操作の互換性テスト|is_glacier_compatible|
|35|test_is_glacier_compatible_no_associated_response_data|関連レスポンスデータがない場合の互換性テスト|is_glacier_compatible|
|36|test_is_glacier_compatible_non_glacier_object|非Glacierオブジェクトの互換性テスト|is_glacier_compatible|
|37|test_is_glacier_compatible_non_glacier_object_2|別の非Glacierオブジェクトの互換性テスト|is_glacier_compatible|
|38|test_is_glacier_compatible_restored_glacier_object|復元されたGlacierオブジェクトの互換性テスト|is_glacier_compatible|
|39|test_is_glacier_object_case_sensitivity|ストレージクラスの大文字小文字の区別テスト|_is_glacier_object|
|40|test_is_glacier_object_copy_operation|Glacierオブジェクトのコピー操作のテスト|_is_glacier_object|
|41|test_is_glacier_object_deep_archive|Deep Archiveストレージクラスの判定テスト|_is_glacier_object|
|42|test_is_glacier_object_empty_input|空の入力での_is_glacier_objectメソッドのテスト|_is_glacier_object|
|43|test_is_glacier_object_incorrect_type|不正な入力タイプでの_is_glacier_objectメソッドのテスト|_is_glacier_object|
|44|test_is_glacier_object_invalid_storage_class|無効なストレージクラスでの_is_glacier_objectメソッドのテスト|_is_glacier_object|
|45|test_is_glacier_object_missing_storage_class|ストレージクラスが欠落している場合の_is_glacier_objectメソッドのテスト|_is_glacier_object|
|46|test_is_glacier_object_restored_glacier|復元されたGlacierオブジェクトの_is_glacier_objectメソッドのテスト|_is_glacier_object|
|47|test_is_glacier_object_returns_false_for_empty_response_data|空のレスポンスデータでの_is_glacier_objectメソッドのテスト|_is_glacier_object|
|48|test_is_glacier_object_returns_false_for_non_glacier_storage_class|非Glacierストレージクラスでの_is_glacier_objectメソッドのテスト|_is_glacier_object|
|49|test_is_glacier_object_returns_false_for_none_response_data|Noneレスポンスデータでの_is_glacier_objectメソッドのテスト|_is_glacier_object|
|50|test_is_glacier_object_returns_false_for_restored_glacier_object|復元されたGlacierオブジェクトでの_is_glacier_objectメソッドのテスト|_is_glacier_object|
|51|test_is_glacier_object_returns_true_for_deep_archive_storage_class|Deep Archiveストレージクラスでの_is_glacier_objectメソッドのテスト|_is_glacier_object|
|52|test_is_glacier_object_returns_true_for_glacier_storage_class|Glacierストレージクラスでの_is_glacier_objectメソッドのテスト|_is_glacier_object|
|53|test_is_glacier_object_with_no_response_data|レスポンスデータがない場合の_is_glacier_objectメソッドのテスト|_is_glacier_object|
|54|test_is_restored_when_object_is_not_restored|オブジェクトが復元されていない場合の_is_restoredメソッドのテスト|_is_restored|
|55|test_is_restored_when_object_is_restored|オブジェクトが復元された場合の_is_restoredメソッドのテスト|_is_restored|
|56|test_is_restored_when_restore_info_is_missing|復元情報が欠落している場合の_is_restoredメソッドのテスト|_is_restored|

テストメソッドを一部抜粋します。こちらの中身のロジックも問題なさそうです。

```test_fileinfo.py（一部抜粋：6 空のRestore値での_is_restoredメソッドのテスト）
    def test__is_restored_with_empty_restore_value(self):
        """
        Test _is_restored method with empty Restore value
        """
        file_info = FileInfo(src='test')
        response_data = {'Restore': ''}
        assert not file_info._is_restored(response_data)
```

```test_fileinfo.py（一部抜粋：51 Deep Archiveストレージクラスでの_is_glacier_objectメソッドのテスト）
    def test_is_glacier_object_returns_true_for_deep_archive_storage_class(self):
        """
        Test that _is_glacier_object returns True for a DEEP_ARCHIVE storage class
        that is not restored.
        """
        file_info = FileInfo(src='dummy_src')
        response_data = {
            'StorageClass': 'DEEP_ARCHIVE',
            'Restore': 'ongoing-request="true"'
        }
        
        result = file_info._is_glacier_object(response_data)
        
        assert result == True
```

なお、本家のtest_fileinfo.pyファイルは下記の通りです。本家のユニットテストコードはより見やすいですね。とはいえ自動生成も健闘しているとは思うので、さらなる発展を期待します。
https://github.com/aws/aws-cli/blob/develop/tests/unit/customizations/s3/test_fileinfo.py

## さいごに

予想よりもドキュメント自動作成、ユニットテスト自動生成のクオリティが高かったため、活用できる箇所にはぜひ活用していきたいなと思いました。

また、ドキュメント自動生成には[サービスクォータ](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/doc-generation.html#quotas)がありますのでご注意ください！

:::note info
**（参考）サービスクォータ**
 - 既存のREADMEの最大サイズ：15KB
 - READMEを作成するプロジェクトに格納されているコードサイズ：200MB（非圧縮の場合）、50MB（圧縮の場合）
 - タスクごとのドキュメント生成数：3
:::