LoginSignup
2
4

[Django+PostgreSQL+GitHub Actions] サービスコンテナを使ってdocker-composeを使わずにPytestを実行しよう

Last updated at Posted at 2023-05-11

前提

  • GitHub Actionsの基本的な用語についてある程度理解している
  • フレームワークはDjangoを使用
  • DBはPostgresを使用
  • Pytestを使用
  • Poetryを使用
  • PR内にカバレッジを表示させる方法についても説明

サービスコンテナとは

ワークフロー中でアプリケーションをテストもしくはビルドするのに必要なサービスを提供するためのDockerコンテナです
サービスコンテナを使うことでワークフロー内で例えばdocker-composeを使って自前でDBを作成せずにテストを実行することができます
runner内のリソースは限られているのでGitHub側で用意するサービスコンテナなどを使うケースが多いです
また、docker-composeを使わずにワークフローを実行するのでコンテナの起動時間分ワークフローの時間を短縮できます
今回はPostgresのサービスコンテナを使ってPytestを実行させます

ファイル構成

ファイル構成は以下の通りです

❯ tree 
.
├── .github
│   └── workflows
│       └── test.yml
└── application # Djangoのプロジェクトファイルおよびpyproject.tomlが入ったディレクトリ
    ├── project
    │   └── settings.py
    ├── manage.py
    ├── poetry.lock
    └── pyproject.toml

settings.py

settings.pyに以下のように設定します

settings.py
import os

SECRET_KEY = os.environ.get("SECRET_KEY")

DEBUG = os.environ.get("DEBUG") == "True"

ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS").split(" ")

# データベースの設定を行う
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ.get("POSTGRES_NAME"),
        "USER": os.environ.get("POSTGRES_USER"),
        "PASSWORD": os.environ.get("POSTGRES_PASSWORD"),
        "HOST": os.environ.get("POSTGRES_HOST", "db"),
        "PORT": os.environ.get("POSTGRES_PORT", 5432),
    }
}

ワークフローの作成

.github/workflows/test.ymlにテストの自動実行までの処理を記載していきます

.github/workflows/test.yml
name: Run Pytest
on:
  pull_request:
    types: [opened, reopened, synchronize, ready_for_review]

env:
  WORKING_DIRECTORY: application
  SECRET_KEY: test
  DJANGO_SETTINGS_MODULE: project.settings
  ALLOWED_HOSTS: 127.0.0.1
  # "True"にしないとDEBUG内がFalseになってしまう
  DEBUG: "True"
  POSTGRES_NAME: test
  POSTGRES_USER: test
  POSTGRES_PASSWORD: test
  POSTGRES_HOST: 127.0.0.1
  POSTGRES_PORT: 5432

jobs:
  Test:
    if: |
      github.event.pull_request.draft == false
      && !startsWith(github.head_ref, 'release')
      && !startsWith(github.head_ref, 'doc')
    name: Run Test Code
    runs-on: ubuntu-20.04
    # ルート直下にpyproject.tomlを置いている場合は不要
    # 今回はapplication/内にPoetryを含めたDjangoのソースコードが含まれているため、指定する
    defaults:
      run:
        working-directory: {{ env.WORKING_DIRECTORY }}
    # Postgresのサービスコンテナを設定
    services:
      db:
        # PostgresのDocker imageを使用
        image: postgres:15.2
        ports:
          - 5432:5432
        env:
          POSTGRES_NAME: ${{ env.POSTGRES_NAME }}
          POSTGRES_USER: ${{ env.POSTGRES_USER }}
          POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }}
        # Postgresより先にDjangoが起動しないようヘルスチェックを使って起動順を制御
        options: >-
          --health-cmd "pg_isready"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - name: Chekcout code
        uses: actions/checkout@v3
      # Poetryのインストール
      - name: Install poetry
        run: pipx install poetry
      # Pythonのセットアップを実行
      # 2回目以降のテスト実行時はrunner内にPoetryのCacheを生成してPoetryのセットアップを高速化
      - name: Use cache dependencies
        uses: actions/setup-python@v4
        with:
          # pyproject.tomlからPythonのversionを指定(絶対パス)
          python-version-file: "${{ env.WORKING_DIRECTORY }}/pyproject.toml"
          cache: 'poetry'
      # Poetry内の必要なパッケージのインストールを実行
      - name: Install Packages
        run: poetry install
      - name: Run migration
        run: |
          poetry run python manage.py makemigrations
          poetry run python manage.py migrate
      # Pytestを実行
      - name: Run Pytest
        run: |
          set -o pipefail
          poetry runpytest --junitxml=pytest.xml -x -n auto --cov --no-cov-on-fail | tee pytest-coverage.txt
      # カバレッジをPRに表示
      - name: Pytest coverage comment
        uses: MishaKav/pytest-coverage-comment@main
        with:
          pytest-coverage-path: {{ env.WORKING_DIRECTORY }}/pytest-coverage.txt
          junitxml-path: {{ env.WORKING_DIRECTORY }}/pytest.xml

それでは、一つずつ解説していきます

ワークフローを実行する前の設定

以下のように冒頭で

  • どの場面でワークフローを実行するか
  • どの環境変数を使用するか

記載します
今回はがプルリクエスト

  • opened(作成時)
  • reopened(再作成時)
  • synchronize(pushするたびに)
  • ready_for_review(レビューできる状態になったら)

の時にワークフローを実行します
また、envを使うとワークフロー内で使用できる独自の環境変数を定義できます
今回はテストを実行するだけのワークフロー内に秘匿情報を使う必要がないです
そのため、下記のようにsecretsを使わずに環境変数を直書きしています

.github/workflows/test.yml
name: Run Pytest
on:
  pull_request:
    types: [opened, reopened, synchronize, ready_for_review]

env:
  WORKING_DIRECTORY: application
  SECRET_KEY: test
  DJANGO_SETTINGS_MODULE: project.settings
  ALLOWED_HOSTS: 127.0.0.1
  DEBUG: "True"
  POSTGRES_NAME: test
  POSTGRES_USER: test
  POSTGRES_PASSWORD: test
  POSTGRES_HOST: 127.0.0.1
  POSTGRES_PORT: 5432

テストの実行を制限したいとき

テストに関しては

  • draft
  • releaseブランチ
  • docブランチ(ドキュメント作成用)

の時は実行したくないので

.github/workflows/test.yml
if: |
      github.event.pull_request.draft == false
      && !startsWith(github.head_ref, 'release')
      && !startsWith(github.head_ref, 'doc')

を追記します

ワーキングディレクトリの指定

今回はapplication/内にPoetryを含めたDjangoのソースコードが含まれているため、以下のように記載します

.github/workflows/test.yml
    defaults:
      run:
        working-directory: {{ env.WORKING_DIRECTORY }}

Postgres用のサービスコンテナの設定

Postgresのサービスコンテナを設定する際に

  • Docker image
  • ポート
  • 環境変数
  • ヘルスチェック

を記載します
Docker imageはPostgres15.2のものを使用します

Postgresの環境変数で必要なものは

  • POSTGRES_NAME
  • POSTGRES_USER
  • POSTGRES_PASSWORD

です
上記がないとDBに接続できずにテストが失敗してしまうので必ず設定しましょう

optionsでヘルスチェックを行い、Djangoより先にPostgresの起動が完了するよう制御します

ヘルスチェックについて知りたい方は以下の記事を参照してください

.github/workflows/test.yml
    # Postgresのサービスコンテナを設定
    services:
      db:
        # PostgresのDocker imageを使用
        image: postgres:15.2
        ports:
          - 3306:3306
        env:
          POSTGRES_NAME: ${{ env.POSTGRES_NAME }}
          POSTGRES_USER: ${{ env.POSTGRES_USER }}
          POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }}
        # Postgresより先にDjangoが起動しないようヘルスチェックを使って起動順を制御
        options: >-
          --health-cmd "pg_isready"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

どうして127.0.0.1を指定しているのか

docker-composeを使う際はsettings.pyのPOSTGRES_HOSTにservice名を指定することで名前解決でPostgresのIPアドレスを取得して接続していましたが、今回はDockerを使わずにランナー上で直接テストを実行しています
公式ドキュメントでも記載しているようにホストを127.0.0.1に指定します

ランナーマシン上でのジョブの実行
ランナーマシン上でジョブを直接実行する場合、localhost:port か 127.0.0.1:port を使ってサービスコンテナにアクセスできます。 GitHubは、サービスコンテナからDockerホストへの通信を可能にするよう、コンテナネットワークを設定します

Poetryの設定

公式ドキュメントに記載の通りPoetryの設定を行います

Poetryをインストールしたあとpyproject.toml内のパッケージをインストールしていきます

cache: 'poetry'

と指定することで2回目以降のpoetry installをCacheを使って高速化させます
スクリーンショット 2023-07-05 8.13.03.png
スクリーンショット 2023-07-05 8.13.46.png
スクリーンショット 2023-07-05 8.14.02.png

.github/workflows/test.yml
      # Poetryのインストール
      - name: Install poetry
        run: pipx install poetry
      # Pythonのセットアップを実行
      # 2回目以降のテスト実行時はrunner内にPoetryのCacheを生成
      # Poetryのセットアップを高速化
      - name: Use cache dependencies
        uses: actions/setup-python@v4
        with:
          # pyproject.tomlからPythonのversionを指定(絶対パス)
          python-version-file: "${{ env.WORKING_DIRECTORY }}/pyproject.toml"
          cache: 'poetry'
      # Poetry内の必要なパッケージのインストールを実行
      - name: Install Packages
        run: poetry install

マイグレーションの実行

DB内にテーブルを作成するためにマイグレーションを行います

.github/workflows/test.yml
      - name: Run migration
        run: |
          poetry run python manage.py makemigrations
          poetry run python manage.py migrate

Pytestの実行

Pytestを実行します
後述するカバレッジをPRに表示させるActionを使用する際にPytestが失敗したのにワークフローが成功してしまう不具合がありました

set -o pipefail

を記載することでPytestが失敗したら以下のコマンドが実行されず、ワークフローが異常終了してくれます

tee pytest-coverage.txt

以下のissueに記載されていました

.github/workflows/test.yml
      # Pytestを実行
      - name: Run Pytest
        run: |
          set -o pipefail
          # pytest.xmlとpytest-coverage.txtはapplication/の配下に作成される
          poetry run pytest --junitxml=pytest.xml -x -n auto --cov --no-cov-on-fail | tee pytest-coverage.txt

オプションは以下の通りです

オプション 説明
-x テストが一つでも失敗したらPytestを強制終了させる
-n auto マルチスレッドでPytestを実行。runnerは1コア2スレッドなのでauto=2
--no-cov-on-fail テストが失敗したらカバレッジを表示させない

-n auto

pytest-xdistをインストールすると上記のオプションを使用することができます

カバレッジをPRに表示

.github/workflows/test.yml
      # カバレッジをPRに表示
      - name: Pytest coverage comment
        uses: MishaKav/pytest-coverage-comment@main
        with:
          # パスは今回はカレントディレクトリではなく、applicationを指定
          pytest-coverage-path: {{ env.WORKING_DIRECTORY }}/pytest-coverage.txt
          junitxml-path: {{ env.WORKING_DIRECTORY }}/pytest.xml

以下のActionを使用します

ワークフローを実行しよう

PRを作成したらワークフローが実行されます
下記のようにテストが実行できたら成功です

スクリーンショット 2023-07-05 8.14.58.png
スクリーンショット 2023-07-05 8.15.37.png

また、以下のようにPRにカバレッジが表示させることも確認できました

スクリーンショット 2023-07-05 8.15.57.png
スクリーンショット 2023-07-05 8.16.11.png

参考

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4