はじめに
FlaskとPostgreSQLを用いたWebアプリ開発のハンズオンを完了したため、その応用実践として自分自身の書籍管理アプリ(book-log-app)の開発に取り組みました。
基本機能であるCRUDの実装から始まり、ハンズオンの流れを踏襲しながらpytestによる自動テスト、そしてGitHub ActionsでのCI環境(PostgreSQLサービスコンテナ連携)構築まで進めました。
本記事では、開発の流れと、特に何度も壁にぶち当たったCI/CD構築時のトラブルシューティングについて詳しく記録します。
開発の流れ(ハンズオンからのステップアップ)
-
環境構築 & DB設計
- Python 3.12 / Flask / PostgreSQL 16 の構成。
-
updated_at自動更新を含むbooksテーブルを作成し、.envで環境変数を分離。
-
CRUD機能の実装
- 新規登録(Create)、一覧表示(Read)、編集・更新(Update)、削除(Delete)を一通り実装。
-
pytest によるテストコード作成
- Flask の
test_clientを使い、テスト実行ごとにテーブルをクリーンアップして検証する仕組みを構築。
- Flask の
-
GitHub Actions による CI(自動テスト)化
- GitHub 上で
git pushするたびに、PostgreSQL サービスコンテナを起動してpytestを自動実行する環境を作成。
- GitHub 上で
🛠️ 主な実装コード
テストコード(test_app.py)
import pytest
from app import app
from db import DATABASE_URL
from init_db import init_db
import psycopg2
@pytest.fixture
def client():
app.config["TESTING"] = True
# テーブルが存在しない場合は自動作成
init_db()
with app.test_client() as client:
# テスト実行前にテーブルを初期化
conn = psycopg2.connect(DATABASE_URL)
cur = conn.cursor()
cur.execute("TRUNCATE TABLE books RESTART IDENTITY;")
conn.commit()
cur.close()
conn.close()
yield client
GitHub Actions ワークフロー(.github/workflows/test.yml)
name: Flask App CI Tests
on:
push:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: bookgram_db
POSTGRES_PASSWORD: postgrespassword
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Run Pytest
env:
DATABASE_URL: postgresql://postgres:postgrespassword@localhost:5432/bookgram_db
run: |
pytest test_app.py -v
💥 CI/CD構築で何度もエラーになった試行錯誤の全記録
CI環境を構築するにあたり、一発では成功せず何度も赤色のバツ印(FAILED)に悩まされました。実際に遭遇したエラーと解決までのアプローチです。
エラー1: PAT(Personal Access Token)の権限不足による git push 拒否
-
症状: ワークフロー定義ファイル(
.github/workflows/test.yml)をgit pushしようとした際、以下のエラーでリモートから拒否された。
! [remote rejected] main -> main (refusing to allow a Personal Access Token to create or update workflow `.github/workflows/test.yml` without `workflow` scope)
-
原因: GitHubのPersonal Access Token(PAT)に、Actionsのワークフローファイルを書き換えるための
workflowスコープが付与されていなかった。 -
解決法: GitHubの
Settings➔Developer settings➔Personal access tokensを開き、対象トークンのスコープ設定でworkflowにチェックを入れて更新した。
エラー2: モジュール読み込み失敗(ImportError: cannot import name ...)
-
症状: GitHub Actions上で
pytest実行時にProcess completed with exit code 2.で失敗。
E ImportError: cannot import name 'DATABASE_URL' from 'db'
E ImportError: cannot import name 'init_db' from 'init_db'
-
原因: CI環境でスクリプトが読み込まれた際、
db.pyやinit_db.py側での関数・変数の export/import 名が呼び出し元(app.pyやtest_app.py)と一致していなかった。 - 解決法:
-
db.py内でos.getenv("DATABASE_URL")を使って環境変数を正しく保持・エクスポートする形に修正。 -
init_db.py内にdef init_db():を明示的に関数として定義し、外部からインポートできるように整理した。
エラー3: DBテーブルが存在しない(UndefinedTable: relation "books" does not exist)
- 症状: モジュール読み込みは成功したものの、テストのセットアップ段階でエラーが発生。
psycopg2.errors.UndefinedTable: relation "books" does not exist
-
原因: GitHub Actions上でPostgreSQLコンテナは起動していたが、
pytest実行時にコンテナ内のデータベースにbooksテーブルがまだ作成されていなかった。 -
解決法:
test_app.pyの pytest フィクスチャ内で、テスト前に必ずinit_db()(CREATE TABLE IF NOT EXISTS)を実行させるように変更し、テーブルがなければ自動構築される堅牢な構造にした。
エラー4: IDE(PyCharm)での psycopg2 インポートエラー
-
症状: ターミナルでのテストは通るものの、PyCharm上で
import psycopg2に赤波線が表示されUnresolved referenceになる。 -
原因: PyCharmのプロジェクト設定(Python Interpreter)が、パッケージをインストールした仮想環境(
venv)ではなくグローバル環境を参照していた。 -
解決法: PyCharmの
Settings➔Project: book-log-app➔Python Interpreterで、venv/bin/pythonを明示的に選択してパスを通した。
まとめと感想
ハンズオンを終えて終わりにせず、自分で応用アプリを作成し、CI/CD環境まで構築したことで多くの学びがありました。
特にCI環境構築では「ローカルで動くのにクラウド上で動かない」という壁に何度もぶつかりましたが、エラーログを一つずつ紐解いて設定やコードを見直すことで、緑色のチェックマーク(PASSED)を表示させることができました。
次はコードの保守性を高めるため、HTMLテンプレートの分離(templates/)や検索機能の実装に挑戦したいと思います!