はじめてのぎっとはぶあくしょんず

# きっかけ
GitHubActionsを最近学んでいます。もしGitHubActionsで自動テスト動作する環境を作るためには、どういう感じに書くのだろうか、と思い作ってみました。


# 実施した内容
- CIの自動テストをGitHubActionsで動かす
  - 主なライブラリはPytestとLocalStackを使う
- 疑似プロダクションコード(main.py)は、S3にオブジェクトをを作成する
- テストコードでは(test_main,py)は、指定したオブジェクト名がS3に存在するかテストする

```yml
name: localstack-ci-example
on: pull_request

jobs:
  ci-example:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: start LocalStack
        run: docker-compose up -d localstack
      - name: Wait for LocalStack to start up for up to 60 seconds
        run: "end_time=$(( $(date +%s)+60));
              while [[ $(curl -s localhost:4566 | jq -r '.status') != 'running' ]];do
                [[ $(date +%s) > ${end_time} ]] && exit 1 || sleep 1;
              done"

      - run: pipx install pipenv
      - uses: actions/setup-python@v3
        with:
          python-version: '3.10'
      - run: pipenv install --dev

      - name: Execute unit test
        run: pipenv run pytest tests/
```

```docker-compose.yml
version: '3.8'

services:
  localstack:
    image: localstack/localstack:latest
    environment:
      - SERVICES=s3
      - DEFAULT_REGION=ap-northeast-1
      - TZ=Asia/Tokyo
    ports:
      - 4566:4566
```

```Pipfile
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
boto3 = "*"

[dev-packages]
pytest = "*"
pytest-mock = "*"

[requires]
python_version = "3.10"
```



```main.py
import boto3
from datetime import datetime as dt

s3 = boto3.resource(
    's3'
)

def main():
    # ファイル名はカレント日時
    upload_object(dt.now().strftime('%Y%m%d%H%M%S'))

def upload_object(obj_name: str) -> None:
    bucket = s3.Bucket('test-bucket')
    bucket.Object(obj_name).put()

if __name__ == '__main__':
    main()
```

```test_main.py
import pytest
import boto3
import botocore
import os
import sys
from datetime import datetime as dt

currrent_path = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(currrent_path, '../'))
import main

s3 = boto3.resource(
    's3',
    endpoint_url='http://localhost:4566',
    aws_access_key_id='DEFAULT_ACCESS_KEY',
    aws_secret_access_key='DEFAULT_SECRET'
)
obj_name :str = 'test_file_' + dt.now().strftime('%Y%m%d%H%M%S')

@pytest.fixture(scope='session', autouse=True)
def setup_s3():
    try:
        s3.create_bucket(
            Bucket='test-bucket',
            CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1'}
        )
    except s3.meta.client.exceptions.BucketAlreadyOwnedByYou:
        # バケットがすでに存在するなら何もしない
        pass


def test_uploead_object(mocker):
    # 向き先をlocalstackに変更
    mocker.patch('main.s3', s3)

    # テスト対象呼び出し
    main.upload_object(obj_name)

    bucket = s3.Bucket('test-bucket')
    # S3にファイルが存在するかassert
    ls_result = bucket.meta.client.list_objects_v2(Bucket=bucket.name, Prefix=obj_name)
    assert ls_result.get('Contents')

    # 後片付け
    bucket.Object(obj_name).delete()
```

# おわりに
GiHubActionsで動かすならこういうことをやるんだろうな、と想像しながら書いてみました。
実際に使うときは、`pipenv install`、`LocalStackのpullとup`は時間が掛かるので、cacheを使用したほうが良さそうですね。


# 参考リンク
https://dev.classmethod.jp/articles/localstack-on-github-actions-sample/
https://note.com/dd_techblog/n/n75bffb92874d
https://zenn.dev/alivelimb/articles/20220508-aws-python-testing