目次
- はじめに
- 導入前の課題
- GitHub Actionsでテストを自動化する
- RuffでLintとFormatチェックを追加する
- 最終的なCI構成
- やってみて気づいたこと
- まとめ
- 追記:CI導入で気づいたこと
はじめに
Python製CLIツールの個人開発でMVP v1が完成した後、保守性の向上を目的にGitHub ActionsとRuffを導入してコード品質チェックを自動化しました。この記事では、その手順と気づきをまとめます。
対象読者
- Python学習者
- 個人開発者
- GitHub ActionsやCI/CDに興味はあるけど、まだ試したことがない方
導入前の課題
MVP v1完成時点では、テストは手動で実行していました。
python -m unittest discover -s tests
毎回このコマンドを手打ちしているうちに、いくつかの問題が気になりはじめました。
- フォーマット崩れ:インデントや空白がいつの間にか乱れることがある
- Lint違反:未使用の変数やインポートが混入することがある
- push前のチェック漏れ:「テスト実行したつもりが、してなかった」という状況が起きる
個人開発では、チーム開発のようにレビューしてくれる人がいません。だからこそ、仕組みで防ぐ必要があると感じました。
「GitHubにPull Requestを出した時点で自動的にチェックが走る状態」を目指すことにしました。
ブランチ運用の変更について
GitHub Actions導入と同じタイミングで、ブランチ運用も見直しました。
それまでもIssueごとに作業ブランチは切っていましたが、統合先は基本的に main でした。
MVP v1完成後は、main を常に正常動作する安定版として残したかったため、次リリース候補の統合先として develop を追加しました。
以後は、以下の流れで進めることにしました。
feat/*(Issue単位の作業ブランチ)
↓
develop(次リリース候補の統合先)
↓
main(安定版)
この構成にしたことで、機能追加中の変更をいきなり main に入れず、まず develop 側でCIを通しながら確認できるようになりました。
個人開発でも、作業中のコードと安定版コードを分離した方が安全だと感じました。このブランチ運用とCIの組み合わせが、今回の保守性向上の軸になっています。
GitHub Actionsでテストを自動化する
Issue #135(GitHub Actions導入) / PR #136
※ 実際の作業も GitHub上で Issue #135 / PR #136 として管理しました。
まずはGitHub Actionsを使って、PR作成時にテストが自動実行される仕組みを作りました。
このプロジェクトでは、Issue作成 → 作業ブランチ作成 → 実装 → PR → Actions確認 → Merge という流れで作業を進めています。IssueとPR番号をブランチ名やコミットに含めておくと、後から作業履歴を追いやすくなります。
作業ブランチを切る
このプロジェクトでは、以下のブランチ構成を採用しています。
| ブランチ | 役割 |
|---|---|
main |
安定版。リリース済みのコードのみ置く |
develop |
次リリース候補の統合先 |
feat/* |
Issue単位の作業ブランチ |
作業はまず develop を最新状態に更新してから、そこを起点にブランチを切ります。main から直接切らないのは、main を常に安定した状態として保つためです。
git switch develop
git pull origin develop
git switch -c feat/135-github-actions-ci
ブランチ名の 135 はIssue番号です。ブランチ名にIssue番号を入れておくと、後から「このブランチは何のためのものか」が一目でわかります。
GitHub Actions用ディレクトリを作成する
GitHub Actionsのワークフローは、リポジトリ直下の .github/workflows/ ディレクトリに YAML ファイルを置くことで認識されます。まずそのディレクトリを作成します。
mkdir -p .github/workflows
-p オプションを付けると、.github/ が存在しない場合でも一度に作成できます。
ワークフローファイルを作成する
.github/workflows/test.yml を以下の内容で作成しました。
name: CI
# main / develop に入れる前に、最低限の自動チェックを行う。
# このCIはデプロイやリリースは行わず、PR時の品質確認だけを目的にする。
on:
pull_request:
branches:
- main
- develop
push:
branches:
- main
- develop
jobs:
test:
runs-on: ubuntu-latest
steps:
# リポジトリの内容を GitHub Actions 実行環境へ取得する。
- name: Checkout repository
uses: actions/checkout@v4
# ローカル開発環境に近い Python 3.12 を使ってテストする。
- name: Set up Python
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
python -m pip install -r requirements.txt
fi
# src 配下を import 対象にして、既存の unittest をすべて実行する。
- name: Run unit tests
run: PYTHONPATH=src python -m unittest discover -s tests
# 行末スペースや競合マーカーなど、Git差分上の基本的な問題を検出する。
- name: Check whitespace errors
run: git diff --check
各設定項目の説明
初めてGitHub Actionsを触る方向けに、主要な項目を説明します。
on.pull_request / on.push
CIを実行するタイミングを定義します。今回は main と develop へのPR作成時・push時に実行されるよう設定しました。PRを出すたびに自動でチェックが走ります。
runs-on: ubuntu-latest
CIが動く実行環境のOSを指定します。ubuntu-latest にしておくと、GitHubが管理する最新のUbuntu環境が使われます。無料枠の範囲内で利用できます。
actions/checkout@v4
リポジトリのコードをCI実行環境に取得するための公式アクションです。これがないと、次のステップでコードを参照できません。
actions/setup-python@v5 / python-version: "3.12"
CI環境にPythonをセットアップする公式アクションです。python-version でバージョンを指定することで、ローカル開発環境と揃えられます。環境差異によるテスト失敗を防ぐために明示的に指定しています。
requirements.txt
プロジェクトの依存パッケージを記述したファイルです。ファイルが存在する場合にのみインストールするよう if 文で確認しています。
PYTHONPATH=src
このプロジェクトはソースコードを src/ 以下に配置しているため、Pythonにそのパスを認識させる必要があります。PYTHONPATH=src を付けないとモジュールの解決に失敗します。
git diff --check
行末スペースや競合マーカー(<<<<<< など)のような、コード上のノイズを検出します。エラーがあればCIが失敗するため、プッシュ前に気づくことができます。
PRを作成してActionsの実行を確認する
ファイルを作成したらコミットしてpushし、GitHubでPRを作成します。
PRを作成する際、GitHubのデフォルトでは base: main になっていることがあります。このプロジェクトでは develop を統合先としているため、必ず確認して変更します。
base: develop ← ここを確認する(デフォルトが main になっている場合は変更)
compare: feat/135-github-actions-ci
PR作成後、GitHub Actions のチェックが自動で実行されます。
今回がGitHub Actions初導入だったため、最初はPR作成後の挙動がよく分かっていませんでした。すぐにMergeできると思っていましたが、CI実行中はMerge前に確認待ちの状態になります。
最初は「PRを作成したのに、なかなかMergeできる状態にならない」と思いましたが、実際にはGitHub Actionsがまだ実行中だっただけでした。
Actionsの実行には少し時間がかかるため、PR画面に表示されるチェック状況を見ながら、すべてのCIチェックが成功するまで待ちます。
PR作成
↓
Actions実行
↓
CI成功確認
↓
Merge
この流れを一度経験すると、PRを作ったらまずCI結果を見る、という確認手順が自然に身につきました。
Mergeとローカルのブランチ後片付け
Merge後はローカルのブランチを削除して整理します。
git switch develop
git pull origin develop
git fetch --prune
git branch -d feat/135-github-actions-ci
git status
git fetch --prune はリモートで削除されたブランチの追跡情報をローカルからも削除します。git branch -d はMerge済みブランチのみ削除できるオプションです(未Mergeの場合はエラーになるため安全です)。最後に git status で状態を確認してから次の作業に移ります。
なお、今回のworkflowは pull_request と push の両方をトリガーに設定しています。
そのため、PR作成時だけでなく、Merge後に develop ブランチへ反映されたタイミングでもActionsが再実行されます。
PR作成時
↓
pull_request イベントでCI実行
Merge後
↓
develop ブランチへの push イベントでCI再実行
これはエラーではなく、workflowの設定通りの動きです。
実際に私も、Merge後にPR画面がすぐ完了しないため「何か失敗したのかな」と思いましたが、実際にはMerge後の push イベントによるActions再実行中でした。
push トリガーを含めているのは、PRを経由せずに直接pushされた場合でもCIを実行するためです。
RuffでLintとFormatチェックを追加する
Issue #137(Ruff導入) / PR #138
※ 実際の作業も GitHub上で Issue #137 / PR #138 として管理しました。
GitHub Actionsの仕組みができたところで、次はRuffを導入しました。
RuffはRust製のPython向けLinter/Formatterです。flake8 や black など複数のツールを組み合わせて使うのが従来の方法でしたが、Ruffは一つのツールでLintとFormatの両方をカバーできます。実行速度も非常に速いです。
PHPやLaravelの開発経験がある方には、以下のように置き換えるとイメージしやすいと思います。
| 言語 | 品質チェック | 整形 |
|---|---|---|
| Python | Ruff | Ruff |
| PHP | PHPStan | PHPPint |
| Laravel | Larastan | PHPPint |
| JavaScript | ESLint | Prettier |
私はLaravel学習時にPHPPintやLarastanを使っていたため、
- PHPPint → Ruff format
- PHPStan / Larastan → Ruff check
のような感覚で理解できました。
もちろん役割は完全に同じではありませんが、PHP経験者にはイメージしやすいと思います。
Ruff用の作業ブランチを切る
git switch develop
git pull origin develop
git switch -c feat/137-ruff
開発用依存ファイルを作成する
このプロジェクトでは依存ファイルを用途で分けています。
| ファイル | 用途 |
|---|---|
requirements.txt |
アプリの実行に必要なパッケージ |
requirements-dev.txt |
CIや開発時にのみ使うツール |
Ruffはアプリの動作には不要なので requirements-dev.txt に追加します。
cat > requirements-dev.txt <<'EOF'
ruff
EOF
仮想環境を作成してRuffをインストールする
最初は通常通り以下のコマンドを実行しました。
python3 -m pip install -r requirements-dev.txt
しかしUbuntu 24.04の環境では、以下のエラーが発生しました。
error: externally-managed-environment
最初はPythonやpipが壊れているのかと思いましたが、調べてみるとUbuntu側がシステムPythonを保護するために意図的に設けている制限でした。--break-system-packages を付ければ強制的にインストールできますが、システムのPython環境にプロジェクトの依存関係を混入させたくないため、今回はプロジェクト専用の仮想環境を作る方針に切り替えました。
結果として、この方針には以下のメリットもありました。
- システムPythonを汚さない
- プロジェクトごとに依存関係を分離できる
仮想環境の作成からRuffのインストールまでは以下の手順で進めました。
sudo apt install -y python3-venv
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install -r requirements-dev.txt
ruff --version
.venv/ ディレクトリはリポジトリにコミットしないため、.gitignore に追加済みです。
Ruffの初回チェックを実行する
インストール後、まず現在のコードに問題がないか確認します。
ruff check .
ruff format --check .
ruff check . を実行したところ、未使用のimportが検出されました。
F401 imported but unused
--fix オプションで自動修正することもできますが、今回はどのファイルのどの行に問題があるかを確認したかったため、まずエラーの内容を見てから手動で対応しました。影響範囲を把握してから修正するほうが、予期せぬ変更を防げます。
Ruff formatを適用する
ruff format --check . でも複数のファイルが整形対象として検出されました。こちらはコードの動作には影響しないフォーマットの問題なので、自動整形を適用します。
ruff format .
適用後、チェックがすべて通ることを確認します。
ruff check .
ruff format --check .
PYTHONPATH=src python -m unittest discover -s tests
git diff --check
ここで問題がなければ、次のステップでCIに組み込みます。
CIにRuffを追加する
.github/workflows/test.yml を更新して、requirements-dev.txt のインストールと Ruff チェックを追加しました。
# 実行用依存と開発用依存をインストールする。
# requirements-dev.txt には Ruff などCI用ツールを入れる。
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then
python -m pip install -r requirements.txt
fi
if [ -f requirements-dev.txt ]; then
python -m pip install -r requirements-dev.txt
fi
# Pythonコードのlintを確認する。
- name: Run Ruff check
run: ruff check .
# Ruffのformatter基準で整形漏れがないか確認する。
- name: Run Ruff format check
run: ruff format --check .
これにより、PR作成時および develop / main への push 時に、以下のチェックが自動実行されるようになりました。
ruff check .
ruff format --check .
PYTHONPATH=src python -m unittest discover -s tests
git diff --check
Ruff導入後のPRを作成してActionsを確認する
コミットしてpushし、GitHubでPRを作成します。
base: develop ← ここを確認する(デフォルトが main になっている場合は変更)
compare: feat/137-ruff
PR作成後、Actionsが実行されます。すべてのCIチェックが成功したことを確認してからMergeしました(成功時は「1 check passed」のように表示されます)。
Ruff導入後のMergeとローカルブランチ後片付け
git switch develop
git pull origin develop
git fetch --prune
git branch -d feat/137-ruff
git status
最終的なCI構成
現在、PR作成時および develop / main への push 時に、以下のチェックが自動実行されます。
ruff check .
ruff format --check .
PYTHONPATH=src python -m unittest discover -s tests
git diff --check
実行順序は「LintとFormat → テスト → 空白チェック」という流れです。Ruffが先に走ることで、コードの問題を早い段階で検出できます。
完成した .github/workflows/test.yml の全体は以下の通りです。
name: CI
# main / develop に入れる前に、最低限の自動チェックを行う。
# このCIはデプロイやリリースは行わず、PR時の品質確認だけを目的にする。
on:
pull_request:
branches:
- main
- develop
push:
branches:
- main
- develop
jobs:
test:
runs-on: ubuntu-latest
steps:
# リポジトリの内容を GitHub Actions 実行環境へ取得する。
- name: Checkout repository
uses: actions/checkout@v4
# ローカル開発環境に近い Python 3.12 を使ってテストする。
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
# 実行用依存と開発用依存をインストールする。
# requirements-dev.txt には Ruff などCI用ツールを入れる。
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then
python -m pip install -r requirements.txt
fi
if [ -f requirements-dev.txt ]; then
python -m pip install -r requirements-dev.txt
fi
# Pythonコードのlintを確認する。
- name: Run Ruff check
run: ruff check .
# Ruffのformatter基準で整形漏れがないか確認する。
- name: Run Ruff format check
run: ruff format --check .
# src 配下を import 対象にして、既存の unittest をすべて実行する。
- name: Run unit tests
run: PYTHONPATH=src python -m unittest discover -s tests
# 行末スペースや競合マーカーなど、Git差分上の基本的な問題を検出する。
- name: Check whitespace errors
run: git diff --check
やってみて気づいたこと
「手動でやればいい」は長続きしない
テストを手動で実行するルールを自分に課しても、作業に集中しているときは忘れがちです。自動化して「やらなくてもチェックされる状態」にするのが一番確実でした。
個人開発でもCIは有効
チーム開発のための仕組みというイメージがありましたが、自分一人でも十分に役立ちます。むしろ、レビュアーがいない分、ツールに頼る部分が大きいと感じました。
Ruffの初回チェックは手動で確認する価値がある
--fix で自動修正もできますが、最初は手動で確認してよかったと思っています。どのファイルにどんな問題があるかを把握することで、コードベース全体の状態を見直すきっかけになりました。
RuffはまずデフォルトでOK
最初から細かく設定しようとすると手間がかかります。デフォルトのままでも十分なチェックが走るので、まず動かしてみるのが良いと思います。
まとめ
| 導入したもの | 解決した課題 |
|---|---|
| GitHub Actions | テスト実行忘れ・push前チェック漏れ |
git diff --check |
行末スペースなどのノイズ混入 |
Ruff(ruff check) |
Lint違反の見落とし |
Ruff(ruff format --check) |
フォーマット崩れ |
MVP完成後に「動くものを作った」から「安心して触れるコードにする」へシフトする作業として、GitHub ActionsとRuffの導入は費用対効果が高かったです。
設定ファイルを数十行書いただけで、毎回のPRで自動チェックが走るようになります。Python個人開発でまだCI/CDを試していない方は、ぜひ導入を検討してみてください。
追記:CI導入で気づいたこと
GitHub Actions と Ruff を導入した際、実際には CI設定そのものよりも「既存コードの状態を揃えること」が重要でした。
今回の作業では、以下の順番で問題が見つかりました。
- GitHub Actions を追加
-
ruff: command not foundが発生 -
requirements-dev.txtを追加 - Ruff 実行で未使用 import を検出
-
ruff format --checkで既存コードの整形差分を検出 - フォーマット適用後に CI 成功
最初は GitHub Actions の設定ミスを疑っていましたが、実際には CIが正しく動いたことで、ローカル環境では見逃していた問題が可視化されました。
今後の個人的な運用メモ
新規プロジェクトや CI導入時は、次の順番で進める方がスムーズだと感じました。
① Ruff format導入
② Ruff check導入
③ unittest確認
④ requirements-dev.txt作成
⑤ GitHub Actions作成
⑥ PR作成
⑦ CI確認
CIは品質を保証する仕組みですが、導入時には「隠れていた問題を見つける仕組み」としても役立つことを実感しました。
今回の経験で、Checks が失敗したときは GitHub Actions自体を疑うだけでなく、プロジェクト側の状態も確認する重要性を学べました。