1. はじめに
手動QAとしての経験を活かしつつ、Web APIの自動テスト(pytest)とCI環境(GitHub Actions)の構築を一通り自力で体験してみました。
この記事では、「APIの簡易実装」から「テストコード記述」「GitHub Actionsでの自動化」までの全手順に加え、開発環境構築の段階で実際に自分がつまずいたリアルなエラーと解決策をまとめています。
同じようにテスト自動化(SETE / QAエンジニア)を目指している方の参考になれば幸いです!
2. 構成・使用技術
- 言語・フレームワーク: Python 3.12, Flask
- テストツール: pytest
- 環境構築: venv
- エディタ: VS Code
- バージョン管理・CI: Git, GitHub, GitHub Actions
3. ハンズオン手順
Step 1: 作業フォルダの作成とVS Codeの起動
ターミナルから作業フォルダを作成し、VS Codeを起動します。
# フォルダの作成と移動
mkdir api-test-demo
cd api-test-demo
# 今いるフォルダをVS Codeで直接開く
code .
(※ code . で別ウィンドウが開きますが、code -r . を使うと同ウィンドウ内で開くことも可能です)
Step 2: 仮想環境(venv)の作成とツールの準備
Pythonの環境を汚さないよう、プロジェクト専用の仮想環境を作ります。
# 1. 仮想環境(専用の部屋)を作成
python3 -m venv venv
# 2. 仮想環境を有効化(Mac / zsh の場合)
source venv/bin/activate
# ※成功するとターミナルの行頭に (venv) と表示されます
# 3. 必要なパッケージをインストール
pip install flask pytest
Step 3: API本体とテストコードの実装
venv フォルダと同じ階層(プロジェクト直下)に以下の2ファイルを作成します。
① app.py(タスク管理API)
新規タスク作成(POST)とステータス更新(PATCH)を行うシンプルなAPIです。
from flask import Flask, jsonify, request
app = Flask(__name__)
tasks = []
task_id_counter = 1
@app.route("/tasks", methods=["POST"])
def create_task():
global task_id_counter
data = request.get_json() or {}
title = data.get("title", "").strip()
if not title:
return jsonify({"error": "タイトルは必須です"}), 400
new_task = {
"id": task_id_counter,
"title": title,
"status": "未着手",
}
tasks.append(new_task)
task_id_counter += 1
return jsonify(new_task), 201
@app.route("/tasks/<int:task_id>/status", methods=["PATCH"])
def update_task_status(task_id):
data = request.get_json() or {}
new_status = data.get("status")
allowed_statuses = ["未着手", "進行中", "完了"]
if new_status not in allowed_statuses:
return jsonify({"error": "無効なステータスです"}), 400
for task in tasks:
if task["id"] == task_id:
task["status"] = new_status
return jsonify(task), 200
return jsonify({"error": "タスクが存在しません"}), 404
② test_app.py(自動テストコード)
QA観点を含めた正常系・異常系計5ケースのテストです。
import pytest
from app import app, tasks
@pytest.fixture
def client():
app.config["TESTING"] = True
tasks.clear()
with app.test_client() as client:
yield client
# ① 正常系:タスク作成
def test_create_task_success(client):
response = client.post("/tasks", json={"title": "pytestをマスターする"})
assert response.status_code == 201
assert response.get_json()["title"] == "pytestをマスターする"
# ② 異常系(QA観点):タイトルが空のとき
def test_create_task_empty_title(client):
response = client.post("/tasks", json={"title": " "})
assert response.status_code == 400
assert response.get_json()["error"] == "タイトルは必須です"
# ③ 正常系:ステータス更新
def test_update_status_success(client):
create_res = client.post("/tasks", json={"title": "テスト用タスク"})
task_id = create_res.get_json()["id"]
update_res = client.patch(
f"/tasks/{task_id}/status", json={"status": "進行中"}
)
assert update_res.status_code == 200
assert update_res.get_json()["status"] == "進行中"
# ④ 異常系(QA観点):無効なステータス
def test_update_status_invalid_value(client):
create_res = client.post("/tasks", json={"title": "テスト用タスク"})
task_id = create_res.get_json()["id"]
update_res = client.patch(
f"/tasks/{task_id}/status", json={"status": "保留"}
)
assert update_res.status_code == 400
# ⑤ 異常系(QA観点):存在しないタスクID
def test_update_status_not_found(client):
update_res = client.patch("/tasks/999/status", json={"status": "完了"})
assert update_res.status_code == 404
Step 4: ローカルでのテスト実行
ターミナルで pytest を実行します。
python3 -m pytest test_app.py
全5ケースが合格すると
5 passedと表示されます。
Step 5: Git管理とGitHubへのプッシュ
容量の大きい仮想環境(venv)を送信除外にするため、.gitignore を作成します。
-
.gitignoreの作成
venv/
__pycache__/
.pytest_cache/
- Gitの初期化とローカルコミット
git init
git add .
git commit -m "Initial commit: Task management API and tests"
- GitHubへプッシュ
git branch -M main
git remote add origin [https://github.com/](https://github.com/)<自身のユーザー名>/api-test-demo.git
git push -u origin main
Step 6: GitHub Actions(CI)の設定
.github/workflows/test.yml を作成し、プッシュ時に自動テストが回るように設定します。
name: Run Pytest
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flask pytest
- name: Run tests with pytest
run: |
pytest test_app.py
作成したらコミットしてGitHubへ送信します。
git add .github/workflows/test.yml
git commit -m "Add GitHub Actions workflow"
git push
GitHub上の Actions タブで実行状況を確認し、緑色のチェックマーク(✅ Passed) になれば構築完了です!
4. 実際にハマったポイント・エラーと解決策 ⚠️
個人的に一番勉強になった「実際につまずいたポイント」と「原因・解決策」のまとめです。
① command not found: code と出でVS Codeが開かない
-
原因: ターミナルから
codeコマンドを呼び出すためのPATH設定がされていなかった。 - 解決策:
- VS Codeで
Cmd + Shift + Pを押し、コマンドパレットを開く。 -
shell commandと入力し、「シェル コマンド: PATH 内に 'code' コマンドをインストールします」 を選択・実行。 - (※誤ってアンインストールを選択してしまった場合は、全く同じ手順で再度「インストール」を選択し直せば復旧します)
② pytest 実行時に ImportError: cannot import name 'app' from 'app'
-
原因: VS Codeでコードを貼り付けた後、保存(
Cmd + S)が実行されていなかった ため、ディスク上のファイルの中身が空(未保存)の状態だった。 - 解決策:
-
Cmd + Sを明確に押して保存する(タブの丸い点●が消えたことを確認)。 -
根本対策: VS Codeの設定(
Cmd + ,)からfiles.autoSaveをafterDelay等に変更し、自動保存を有効化するとミスを防げます。
③ ファイルの配置場所ミス(venv フォルダ内への格納)
-
原因:
app.pyやtest_app.pyを誤ってvenv/フォルダの中に作成してしまい、Pythonのインポートパスから外れていた。 -
解決策: ソースコードやテストコードは、必ず
venv/フォルダと同じ階層(プロジェクト直下)に配置する。
④ GitHub Actionsで Error: No event triggers defined in 'on'
-
原因:
test.yml(YAML形式)のインデント(スペース)や構文が崩れていたため、GitHub側でイベント(起動トリガー)として認識されなかった。 - 解決策: YAML形式はスペースの数やネスト構造に非常に厳密なため、インデントを見直して修正後に再プッシュする。
5. おわりに
手動QAで培った「どんなケースをテストすべきか(正常系・異常系・境界値)」という視点は、コードに落とし込む際にも強力な武器になると実感しました。
ローカルでのテスト実行だけでなく、GitHub Actionsで自動化(CI環境構築)まで繋げられたことで、開発から品質保証までの全体像を一気に掴むことができました!