AWS CodeBuild で、Lambda 関数の自動ユニットテストを実行し、結果を S3 に格納する

# はじめに

[前回の記事](https://qiita.com/Yu-Chihuahua/items/cc6f0a2cc5d5774bea6f)で、コンテナイメージを使用した AWS Lambda 関数の開発環境を構築しました。
ローカル環境でテストレポートおよびカバレッジレポートを出力できるようにしたので、それを AWS CodeBuild で実行し、テスト結果を S3 に格納できるようにしました。

## 前提
下記の事前準備ができていることを前提とします。
- AWS コンソールにログインできるアカウントを有している
- ローカルの WSL2 Ubuntu 環境から、AWS CLI コマンドを実行できる（アクセスキーを有している）

## 今回のゴール
- CodeBuild で自動ユニットテストを実行するためのカスタムイメージを作成する
- CodeBuild で Lambda 関数の自動ユニットテストを実行し、テスト結果（テストレポートおよびカバレッジレポート）を S3 に格納する

## ディレクトリ構成
今回のディレクトリ構成は下記のようになります。
```diff_shell:WSL2 Ubuntu
/home/appuser

  ├── workspace
  │   ├── docker-compose.yaml
  │   ├── .vscode
  │   │   └── launch.json
  │   └── lambda001
  │        ├── Dockerfile
+ │        ├── Dockerfile_codebuild
+ │        ├── buildspec_pytest.yml
  │        ├── opt
  │        │   ├── bootstrap_debug
  │        │   ├── lambda-entrypoint_debug.sh
  │        │   └── pytest
  │        │        ├── run_pytest.sh
  │        │        └── reports
  │        └── var
  │             └── task
  │                  ├── app.py
  │                  ├── pytest.ini
  │                  └── tests
  │                       └── test_app_001.py
```

## 手順
### 資材作成
1. ```Dockerfile_codebuild```ファイルを作成
    CodeBuild で自動ユニットテストを実行するためのコンテナが必要になります。
    カスタムイメージを作るための Dockerfile として、```Dockerfile_codebuild```ファイルを下記のように作成します。
    ```shell:Dockerfile_codebuild
    # Python 3.12.1
    FROM public.ecr.aws/lambda/python:3.12.2024.03.04.10-x86_64
    
    # ツールをインストール
    RUN dnf install -y \
        unzip
    
    # AWS CLI をインストール
    RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
    RUN unzip awscliv2.zip
    RUN ./aws/install
    
    # Python のライブラリをインストール
    RUN pip3 install pytest==8.1.*
    RUN pip3 install pytest-cov==4.1.*
    RUN pip3 install pytest-html==4.1.*
    RUN pip3 install debugpy==1.8.*
    
    # 資材を配置する
    ###### COPY ./var/task/ /var/task/
    RUN mkdir -p /opt/pytest/reports/report_text/
    RUN mkdir -p /opt/pytest/reports/report_xml/
    RUN mkdir -p /opt/pytest/reports/report_html/
    RUN mkdir -p /opt/pytest/reports/coverage_html/
    ###### COPY ./opt/pytest/ /opt/pytest/
    COPY ./opt/bootstrap_debug /opt
    COPY ./opt/lambda-entrypoint_debug.sh/ /opt/
    
    # パーミッションを変更する
    RUN chmod 755 /opt/bootstrap_debug
    RUN chmod 755 /opt/lambda-entrypoint_debug.sh
    
    # 起動時のコマンドを設定する
    ###### CMD ["app.handler"]
    ```
    <br>

1. ```buildspec_pytest.yml```ファイルを作成
    CodeBuild で自動ユニットテストを実行するための定義として、```buildspec_pytest.yml```ファイルを下記のように作成します。
    ```yaml:buildspec_pytest.yml
    version: 0.2
    
    phases:
      pre_build:
        commands:
          - cp -r ./var/task/. /var/task/
      build:
        commands:
          # pytest をカバレッジレポートを出力するモードで起動する
          - cd /var/task/
          - python -m pytest -c pytest.ini -s -v --junitxml=/opt/pytest/reports/report_xml/report.xml --html=/opt/pytest/reports/report_html/report.html --cov-report=html:/opt/pytest/reports/coverage_html/ --cov=/var/task/ --cov-branch /var/task/ > /opt/pytest/reports/report_text/report.txt
      post_build:
        commands:
          # テストレポートとカバレッジレポートをS3にアップロードする
          - datetime=$(date '+%Y%m%d%H%M%S')
          - aws s3 cp /opt/pytest/reports/report_text/ s3://study-codebuild-202502/pytest_reports/lambda001/$datetime/report_text/ --recursive
          - aws s3 cp /opt/pytest/reports/report_html/ s3://study-codebuild-202502/pytest_reports/lambda001/$datetime/report_html/ --recursive
          - aws s3 cp /opt/pytest/reports/coverage_html/ s3://study-codebuild-202502/pytest_reports/lambda001/$datetime/coverage_html/ --recursive
    
    reports:
      report_xml:
        files:
          - report_xml/report.xml
        base-directory: /opt/pytest/reports
        discard-paths: yes
        file-format: JunitXml
    ```
### AWS コンポーネント作成
1. ECR リポジトリを作成
    ECR リポジトリ```lambda_codebuild```を作成します。
    （手順は省略します）
    <br>

1. コンテナイメージを作成して ECR に Push する
    自動ユニットテストを実行するためのコンテナイメージを作成して ECR に Push します。
    コマンドは、ECR リポジトリの「プッシュコマンドを表示」ボタンを押下してコピーします。

    1. Docker クライアントを認証
        ```shell:WSL2 Ubuntu
        cd /home/appuser/workspace/lambda001
        aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 999999999999.dkr.ecr.ap-northeast-1.amazonaws.com
        ```
        下記が出力されれば成功です。
        ```shell:WSL2 Ubuntu
        Login Succeeded
        ```
        <br>
    1. Docker イメージをビルド
        ```docker build```コマンドには、```-f ./Dockerfile_codebuild```オプションを追加で指定します。

        ```shell:WSL2 Ubuntu
        docker build -t lambda_codebuild . -f ./Dockerfile_codebuild
        ```
        下記が出力されれば成功です。
        ```shell:WSL2 Ubuntu
        [+] Building 4.4s (18/18) FINISHED
        ～～～～～～～～～～ 省略 ～～～～～～～～～～
        ```
        <br>
    1. イメージにタグ付け、ECR リポジトリに Push
        イメージにタグ付けするコマンドを実行した後、ECR リポジトリに Pushします。
        ```shell:WSL2 Ubuntu
        docker tag lambda_codebuild:latest 999999999999.dkr.ecr.ap-northeast-1.amazonaws.com/lambda_codebuild:latest
        docker push 999999999999.dkr.ecr.ap-northeast-1.amazonaws.com/lambda_codebuild:latest
        ```
        下記が出力されれば成功です。
        ```shell:WSL2 Ubuntu
        The push refers to repository [999999999999.dkr.ecr.ap-northeast-1.amazonaws.com/lambda_codebuild]
        598813a81bbf: Pushed
        ～～～～～～～～～～ 省略 ～～～～～～～～～～
        8a302ef602af: Pushed
        latest: digest: sha256:5474828fb3e5a60ed72ba03c3c5002d1c1f067b30c4663b4eed69be511363a1d size: 4078
        ```
    <br>

1. S3 バケットを作成
    下記のように S3 バケットとプレフィックスを作成します。
    （手順は省略します）
    今回のバケット名は```study-codebuild-202502```とします。
    ```shell:S3
    s3://study-codebuild-202502/
      ├── pytest_reports/
      └── lambda/
    ```
    <br>

1. ソースコードを格納
    ```s3://study-codebuild-202502/lambda/```内に、```\\wsl$\Ubuntu-22.04\home\appuser\workspace\lambda001```をディレクトリごと格納します。
    （手順は省略します）
    ```diff_shell:S3
      s3://study-codebuild-202502/
        ├── pytest_reports/
        └── lambda/
    +        └── lambda001
    +             ├── Dockerfile
    +             ├── Dockerfile_codebuild
    +             ├── buildspec_pytest.yml
    +             ├── opt
    +             │   ├── bootstrap_debug
    +             │   ├── lambda-entrypoint_debug.sh
    +             │   └── pytest
    +             │        ├── run_pytest.sh
    +             │        └── reports
    +             └── var
    +                  └── task
    +                       ├── app.py
    +                       ├── pytest.ini
    +                       └── tests
    +                            └── test_app_001.py
    ```
    <br>

1. CodeBuild ビルドプロジェクトを作成
    CodeBuild ビルドプロジェクトをを下記の通りに作成します。
    ![aaaaaaaaaaaaa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3767768/1a480726-2f5e-4cea-ad5f-b73054d64548.png)
    <br>

1. サービスロールにポリシーをアタッチ
    サービスロール```codebuild-lambda001_pytest-service-role```に、下記のポリシーをアタッチします。
    （手順は省略します）
    - ```AmazonS3FullAccess```
    - ```AWSAppRunnerServicePolicyForECRAccess```

### 動作確認
1. ビルド実行
    ビルドプロジェクト```lambda001_pytest```の「ビルドを開始」ボタンを押下します。
    [ビルド履歴]で、ビルドステータスが「成功」になることを確認します。
    ![bbbbbbbbbbbbbbbbb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3767768/fccb29fa-6072-4e62-9649-e8791f0611bc.png)

    <br>

1. S3 バケットを確認
    ```s3://study-codebuild-202502/pytest_reports/lambda001/{yyyyMMddHHmmss}/```内に、下記3つのプレフィックスが作成され、テスト結果が格納されていることを確認します。
    - coverage_html
    - report_html
    - report_text
    ![cccccccccccccccccccccccccccccccccc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3767768/28441eda-1c28-48e9-ba5c-0067d4339247.png)


## 最後に
今回はサンプル実装のため、ビルドプロジェクトのソースに S3 を指定しましたが、実際のプロジェクトでは GitHub を使うことが多いと思います。
また、AWS CodePipeline を利用して AWS CodeDeploy と組み合わせて使うこともあるかと思いますが、基本的な作り方は変わりません。
テストレポートの S3 バケットを HTTP で公開するなど工夫すれば、更に便利にできそうです。