CodeCommitとCodeBuildを連結してアプリケーションテストの一括処理にトライしてみた

## はじめに
「テストフェーズは無限に時間が溶ける、、、」
「設計フェーズや開発フェーズは楽しいけど、テストフェーズに入ると一気にテンション低下、、、」

エンジニアであれば誰もがこう思った経験があるのではないでしょうか？
私は歴6ヶ月の新米エンジニアですが、既に幾度とこの思いをしてきました。
なぜテストフェーズは労力も時間もかかるのでしょうか？それは数十~数百のテストケースを1つずつ試して問題があればバグ修正、修正したらデグレーションチェックのために、一度成功したケースも再びテストする、、、のループに嵌ってしまうからだと私は考えています。

もしそんなハードなテストをプログラムが自動で、しかも全ケース一括で実施してくれたら、負担が軽減されるのになぁ〜
とお考えの方は必見です。AWSを使えば、その夢が現実に変わります。

そこで今回はアプリケーションとテストケースのソースコードをリポジトリにプッシュしたら自動的にビルドとテストを行い、テストをクリアしたソースコードをストレージに格納するシステムをAWSのクラウドに構築してみようと思います。
(テストケースをコードで書くって、なんか楽しそう！🤔)


## 作成するシステムの構成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/2de5f0b3-e2d7-26e4-62f6-54dc08b7e0ed.png)


## システムの構築
＊前提条件
IAMユーザでシステムを作成する場合、使用するサービスに対する権限が十分に割り当てられていることを確認してください。
今回は以下のようにポリシーを割り当てたIAMユーザを使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/1d0885b0-2329-ba9a-83c4-65db8f780b54.png)


IAMによるIAMユーザへの権限の割り当ては以下のサイトを参照してください↓↓
>[IAMの基本(ポリシー,ユーザー,グループ,ロール)](https://www.wakuwakubank.com/posts/476-aws-iam-basic/)


### Step 1 : テスト成果物の格納先のS3バケットを作成

AWSホーム画面上部の検索窓から「S3」を検索し、S3のページに移動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/71f5d99d-8ea0-3b0d-a3a4-9eba50b1ad70.png)


「バケットの作成」ボタンを押します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/d82902cf-dc18-e1ce-a284-44cf7707c42d.png)


S3バケットの詳細設定をします。今回は全てデフォルトにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/9260560b-c4e3-f211-e02d-94b7ec1c2575.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/c5e8a85c-1234-00b9-2ede-246530e0863e.png)


作成できました。ソースコードを自動的にビルド&テストし、出来上がったアーティファクトをこのバケットに格納するシステムをこれから構築していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/12ea7b6a-ebe5-51e0-c4b1-acc7d661f872.png)


### Step 2 : CodeCommitリポジトリ作成とリソースファイルのアップロード

リソースファイルをアップロードするリポジトリをAWSに作成します。AWSホーム画面上部の検索窓からCodeCommitを検索し、CodeCommitのページに移動します。

リソースファイルをアップロードするリポジトリを作成します。リポジトリ一覧ページ右の「リポジトリを作成」から作成できます。リポジトリ名はチームメンバー全員がわかりやすいものに指定しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/c75c94bd-ea75-9b30-d78b-d5e398e06fec.png)

なお、通信プロトコルに応じてCodeCommit用の認証情報の発行(HTTPSの場合)、またはIAMユーザへの公開鍵の登録(SSHの場合)が必要となるので、次の手順に進む前に、以下のサイトを参考にセットアップします。
>[CodeCommitで利用できる3種類のアクセス方法ハンズオン](https://cloud5.jp/3type-acces-method-in-codecommit/)


ローカルPCの任意の場所に作業用ディレクトリを作成します。私はtest_workspaceという名前で作成しました。
続いて、ターミナルで作業ディレクトリまで移動し、git cloneコマンドに先ほどコピーしたリポジトリのURLをペーストし、実行します。

```
cd 作業ディレクトリまでのパス
git clone CodeCommitリポジトリのURL
```

実行すると、作業ディレクトリ配下にCodeCommitのリポジトリと同名のディレクトリが追加されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/ed59b195-d576-0d32-b19e-9f061be7707e.png)


ここで作成されたディレクトリはCodeCommitで作成したリポジトリと連携しており、
git add、git commit、git pushの一連のコマンドを実行することでローカルのディレクトリにあるファイルをCodeCommitのリポジトリにも配置することができます。

ということで、まずはシステムに必要なファイルを作成します。今回は以下の3つのファイルを作成します。


* __アプリケーションの本体のソースコード app.py__
与えられた2つの値に対して四則計算を行うという簡易的なプログラムです。
```Python:app.py
class Calculator:

    #加算メソッド
    def add(self, a, b):
        return a + b

    #減算メソッド
    def subtract(self, a, b):
        return a - b

    #乗算メソッド
    def multiply(self, a, b):
        return a * b

    #除算メソッド
    def divide(self, a, b):
        if b == 0:
            return None
        return a / b
```


* __コードベースのアプリケーションテストケース  test_app.py__
```Python:test_app.py
class TestCalculator(unittest.TestCase):
    #Calculatorクラスのテストケース

    def setUp(self):
        #テスト前のセットアップ。各テストメソッドでCalculatorインスタンスを使用できるようにする
        self.calc = Calculator()

    def test_add(self):
        #加算メソッドのテスト
        self.assertEqual(self.calc.add(2, 3), 5)
        self.assertEqual(self.calc.add(-1, 1), 0)

    def test_subtract(self):
        #減算メソッドのテスト
        self.assertEqual(self.calc.subtract(10, 5), 5)
        self.assertEqual(self.calc.subtract(-1, -1), 0)

    def test_multiply(self):
        #乗算メソッドのテスト
        self.assertEqual(self.calc.multiply(3, 7), 21)
        self.assertEqual(self.calc.multiply(-1, 3), -3)

    def test_divide(self):
        #除算メソッドのテスト
        self.assertEqual(self.calc.divide(10, 2), 5)
        self.assertEqual(self.calc.divide(5, 0), None)

#TestCaseクラスに含まれる各テストメソッドを個別に実行
if __name__ == '__main__':
    unittest.main()
```


* __ビルドの際に実行するコマンドを記述したもの　buildspec.yml(必ずこの名前にします)__
buildspec.ymlの書き方は以下の記事を参考にしました。
>[CodeBuild のビルド仕様に関するリファレンス](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-spec-ref.html)
```YAML:buildspec.yml
version: 0.2

proxy:
  upload-artifacts: no
  logs: no
        
phases:
  install:
    on-failure: ABORT
    runtime-versions:
      python: 3.12
    # steps:
  pre_build:
    commands:
      - echo Running pre-build steps
    finally:
      - echo Finished pre-build steps
    # steps:
  build:
    on-failure: ABORT
    commands:
      - echo Running build steps
      - python app.py
    finally:
      - echo Finished build steps
    # steps:
  post_build:
    on-failure: CONTINUE
    commands:
      - echo Running tests
      - python -m unittest test_app.py
    finally:
      - echo Finished tests
      - echo all done

    # steps:

artifacts:
  files:
    - app.py
    - test_app.py
```


これらのファイルを作業ディレクトリ配下のディレクトリに格納します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/0fa84e3f-0577-3206-507a-92c7f5a06832.png)


作業ディレクトリから以下のコマンドを順次実行します。
```
cd 231212_test #作業ディレクトリ配下に作成されたディレクトリ名
git add app.py test_app.py buildspec.yml #追加したファイル名
git commit -m “システムに必要なファイル一式を新規作成” #ローカルでの変更を確定させる
git push #ローカルでの変更をCodeCommit上のファイルにも反映
```


gitのコマンドについては以下の記事も参考にしてください↓
>[git addとcommit、pushの関係をわかりやすく説明する【Gitコマンド解説①】](https://qiita.com/atsushi101011/items/96054ebbd876e378cac4)


CodeCommitのリポジトリにも3つのファイルが入りました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/e2961811-f0f4-4a84-4534-2264ae75f32d.png)


### Step 3 : CodeBuildの設定

続いて、更新されたCodeCommit上のファイルをビルドし、test_app.pyの記述に従ってテストを実施する仕組みを構築します。これにはCodeBuildを使用します。
CodeCommitとCodeBuildの連携については以下の記事も参考に。後述するCodePipelineの説明も先んじて出てきます。
>[【初心者】CodeCommit, CodeDeploy, CodePipeline を使ってみる](https://qiita.com/mksamba/items/63c87ca7e910eef1e843)


サービス検索窓からCodeBuildのページに移動し、ビルドプロジェクトの作成に進みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/5b3a6050-b42d-61d7-3ab5-4ae6db9daf2a.png)


CodeBuildの設定はこんな感じに。ソースプロバイダにCodeCommitのリポジトリ・ブランチを指定することで、指定したブランチ内のファイルがCodeBuildに渡されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/ad22def1-cc9b-1bcc-f012-42562445fdeb.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/cb1f8a82-7d29-341f-033a-6ca6fa5add85.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/657cc6de-ac99-b941-1b41-e63ba4966e3b.png)


設定を終えたら「ビルトプロジェクトを作成する」を押します。

作成できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/d8d02609-9fd0-0279-09bf-da07ee93edac.png)


### Step 4 : CodePipelineによるビルド&テストの自動化

ビルドプロジェクト作成完了画面の右上にある「ビルドを開始」を押すと、CodeCommitのリポジトリにあるリソースに対してCodeBuildがビルドとテストを始めます。
しかし、プッシュのたびにCodeBuildの画面を訪れてビルド開始の操作をするのは面倒です。
そこで、CodeCommitへリソースをプッシュしたら自動的にCodeBuildが作動するようにします。
この仕組みの構築にはCodePipelineを使用します。

まずはサービス検索窓からCodePipelineのトップページに移動します。ここはもうお馴染みですね。

パイプラインを新規作成しましょう。右上の「パイプラインを作成する」をクリックし、パイプラインの設定に進みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/352885b4-965a-06f0-240c-246d8bc8b171.png)


以下のように順に設定していきました。
設定は以下の記事を参考にしました。
>[【AWS】CodePipelineの使い方を解説！【CodeBuildと連携してS3にデプロイする】](https://engineer-ninaritai.com/codepipeline-how-to-use/)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/98fd4f8c-769d-8586-1c66-58080c477c9e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/7b46abe6-1367-6123-922a-f5e6eb84e411.png)

ビルドの役目を果たすのはCodeBuildです。先ほど作成したプロジェクトを選択しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/b3448699-c35d-562f-27fa-cf2bd6f56dba.png)


デプロイ先として、先ほど作成したS3バケットを指定します。
オブジェクトキーでは、リソース群の格納名と格納場所を指定します。
画像の例では、テスト済みのリソースはmy-appという名前のzipファイルに圧縮され、product-2023バケット配下の2023-12フォルダ配下に格納されます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/1a63eebb-10f7-aea8-f67b-7f1947e8b93c.png)


最後にパイプライン設定の確認画面が出るので、確認の上で作成を実行します。


## システムの実行
パイプラインを作成すると、既にCodeCommitに上がっているソースコードを検知して、ビルド&テスト→デプロイまでの流れが実行されていました。うまくいっていますね！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/3eb26038-0983-1d93-d26c-cff907bdccbf.png)


Buildフェーズのログには、buildspec.ymlで記述した文言が出力され、テストのプロセスが記録されています。最後にOKと出力されていれば、test_app.pyに指定した全てのテストケースを通過したことを意味します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/585dfa74-66dd-67e2-a66f-c30f076787ac.png)


S3バケットにはオブジェクトキーで指定した名前と場所にてファイルが格納されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/4934d689-d2ef-ba5c-1bff-90b264d79693.png)


## エラーケース
では、試しに意図的にテストでエラーを発生させてみます。
app.pyを、subtractでエラーが発生するように編集します。出力はどうなるのでしょうか。
```Python:app.py
class Calculator:

    #加算メソッド
    def add(self, a, b):
        return a + b

    #減算メソッド
    def subtract(self, a, b):
        return a - b + 1  #ここでエラーを発生させる

    #乗算メソッド
    def multiply(self, a, b):
        return a * b

    #除算メソッド
    def divide(self, a, b):
        if b == 0:
            return None
        return a / b
```


Buildフェーズが失敗します。※Deployフェーズには到達していません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/187d827a-a401-673c-79e2-b5a2f3389b45.png)


ログを見てみましょう。
24行目以降でエラーのテストケースが示されています。先ほど意図的に編集した箇所です。
しかし、エラーが出てもテストは4つのメソッド全て通しで実施されています(33行目)。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632437/f92acbe7-6c2d-f189-136b-2afc8d4c9205.png)


なお、このシステムはローカルのファイルを(新規作成ではなく)編集したときもgit add→git commit→git pushのコマンドでCodeCommitのファイルを更新するだけでテストからデプロイまでの流れを自動的に行うことができます！
変更をかけるごとにテストケースを一括で実行できますし、エラーとなるテストケースがあっても中断はなく最後のテストケースまで実行してくれるので、短時間でクリティカルなエラーを発見することができます。これは有意義に時間を使えそうです。



## 使ってみた感想
ローカルPCのターミナルを叩くだけでテストまで一気にできてしまうとは驚きでした。テストは人間が一から行うとかなり手間がかかるので、時間・労力という恩恵を得られ、かなり重宝するのではないかと思いました。
とはいえ、このシステムを使いこなすには「テストケースを正確にコードに起こす力」が求められます。テストを行うのはプログラムなので、人間のように行間補完が働かないところが難しいところです。仕組みの構築は30分程度でできたので、おそらくテストケース作成の方が何倍もかかりますね、、その経験を積むことは人間側の役目のようです。
今回はテスト後のファイルをS3にデプロイして終わりでしたが、S3にLambdaを連携させることでファイルにさらなる加工を施す試みも面白そうだと思いました(例えばS3にデプロイされたファイルにLambda関数の処理を実行させて、ファイル内の画像の拡大・縮小を自動化するなど)。
まだまだ駆け出しのエンジニアですが、引き続きAWSにさらなる発見をするべく、キャッチアップを続けていきたいです。
最後まで読んでくださりありがとうございました。