Github ActionsでPostgreSQLを使ったテストをする

# 背景

PythonからPostgreSQLにアクセスする処理のテストをpytestで記述した。これをGithub ActionsのCIに組み込みたい。

# 環境

- Python 3.8

ディレクトリ構成は以下。

```
my-repository
├── app
│   ├── my_module.py
│   └── tests
│       └── test_my_module.py
└── .github
    └── workflows
        └── psql-ci.yml
```

# 実装

早速だが実装例。

## テストコード

[pytest](https://docs.pytest.org/en/7.0.x/)を使った以下のようなテストがあることを想定する。

pytestの詳細な説明は他に譲るとしてざっくりと何をやっているかを書いておく。

- `setup_and_teardown_psql`
  - `yield`より前: データの準備。テーブルの作成と`data/init_data.csv`ファイルに記述された初期データのinsertを行う。この処理はテストケースの実行前に行われる。
  - `yield`より後: テスト用に作ったテーブルを削除する
- `test_my_function`
  - PostgreSQLに接続して何かしらする関数のテストを行う。

```py
from pathlib import Path, PurePath

import psycopg2
import pytest

from my_module import my_function

connect_info = {
    "host": "localhost",
    "port": "5432",
    "user": "test",
    "password": "password",
    "dbname": "postgres",
}


@pytest.fixture(scope="function")
def setup_and_teardown_psql():
    conn = psycopg2.connect(**connect_info)
    # connコンテキストが終わったタイミングでコミットされる
    with conn:
        with conn.cursor() as cur:
            cur.execute("""
                CREATE TABLE users (
                    name VARCHAR(50),
                    age INTEGER,
                );
            """)
            data_path = PurePath(Path(__file__)).parent / "data/init_data.csv"
            with open(data_path, "r") as f:
                cur.copy_expert("COPY users FROM stdin WITH csv;", f)
    
    # yieldより前がsetup/後がteardown
    yield
    
    with conn:
        with conn.cursor() as cur:
            cur.execute("DROP TABLE users;")


def test_my_function(setup_and_teardown_psql):
    # PostgreSQLへの接続が必要な関数
    res = my_function(**connect_info)
    expected_res = {}
    
    assert res == expected_res
```

## Github Actions

`.github/workflows/psql-ci.yml`など適当な名前でファイルを作成して以下を記載する。

流れとしてはざっくりと

- レポジトリのコードを取ってきて
- Pythonのセットアップをして
- テストコードを実行

という感じ。

```yml
name: postgres-ci

on:
  push:
    branches:
      - '**'
  
  workflow_dispatch:

jobs:
  postgres-test:
    runs-on: ubuntu-latest
    
    # サービスコンテナには`localhost:<port>`または`127.0.0.1:<port>`で接続可能
    services:
      postgres:
        image: postgres
        # refer: https://hub.docker.com/_/postgres
        env:
          POSTGRES_PASSWORD: password
          POSTGRES_USER: test
          POSTGRES_DB: postgres
        ports:
          - 5432:5432
    
    steps:
      - uses: actions/checkout@v2
      
      - name: Setup
        run: sudo apt install -y curl
      
      - name: Python
        uses: actions/setup-python@v2
        with:
          python-version: '3.8'
          architecture: 'x64'
      
      - name: Setup Poetry
        run: |
          curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
          echo "$HOME/.poetry/bin" >> $GITHUB_PATH
      
      - name: Setup Python Libraries
        run: poetry install
        working-directory: ./app

      - name: test
        run: poetry run pytest -vs
        working-directory: ./app/tests
```

# 参考

- [PostgreSQLサービスコンテナの作成 - GitHub Docs](https://docs.github.com/ja/actions/using-containerized-services/creating-postgresql-service-containers)
- [サービスコンテナについて - GitHub Docs](https://docs.github.com/ja/actions/using-containerized-services/about-service-containers)
- [pytestを使ってみる②【実行前、実行後処理編】 – BAMBi's Blog](https://www.anypalette.com/ja/posts/20201231_how-to-pytest-2/)
- [pytest：フィクスチャ(fixture)の使い方 - Qiita](https://qiita.com/_akiyama_/items/9ead227227d669b0564e)
- [Basic module usage — Psycopg 2.9.3 documentation](https://www.psycopg.org/docs/usage.html#transactions-control)