- CI 自動テストって何?一言でいうと「人がサボっても、機械が毎回テストしてくれる仕組み」
- まずはイメージ:Python プロジェクトでの CI 自動テストの流れ
- 具体例:GitHub Actions で pytest を自動実行する設定
- 重要ポイント1:「いつテストを走らせるか」をちゃんと決める
- 重要ポイント2:「何をテストするか」を段階的に増やしていく
- 重要ポイント3:「速さ」と「安定性」が CI 自動テストの生命線
- 重要ポイント4:ローカルと CI の環境差をできるだけなくす
- 重要ポイント5:CI 自動テストを「開発フローの前提」にする
- 初心者が CI 自動テストを身につけるためのステップ
- まとめ(CI 自動テストは「push や PR のたびに、機械が真顔でテストしてくれる仕組み」)
CI 自動テストって何?一言でいうと「人がサボっても、機械が毎回テストしてくれる仕組み」
CI 自動テストは、コードを GitHub に push したり Pull Request を作ったタイミングで、機械が自動でテストを実行してくれる仕組みです。
「ローカルで pytest 回すの忘れてた」「動くと思ってた」がそのまま main に混ざるのを防ぐための“安全装置”だと思ってください。
DevOps 的に言うと、CI(Continuous Integration)は「こまめに統合して、こまめにテストする」文化で、その中心にあるのが自動テストです。
Python プロジェクトなら、ほぼ必ず「push や PR で pytest を自動実行する」ところから始まります。
まずはイメージ:Python プロジェクトでの CI 自動テストの流れ
ローカルでやっていることを、そのままクラウドで自動化する
あなたがローカルで普段やっていることを思い出してください。
仮想環境を作る
依存ライブラリをインストールする
pytest を実行する
CI 自動テストは、これを「GitHub 上のサーバーで自動でやる」だけです。
つまり、やっていることはシンプルで、「場所」と「トリガー」が違うだけです。
ローカルでは自分の PC で、手でコマンドを叩く。
CI では GitHub のサーバーで、push や PR をトリガーに自動で動く。
このイメージが掴めれば、もう半分理解できています。
具体例:GitHub Actions で pytest を自動実行する設定
最小の CI 設定ファイルを書いてみる
GitHub Actions を使う場合、リポジトリに .github/workflows/ci.yml のようなファイルを置きます。
例えば、こんな感じです。
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install -U pip
pip install -e .[dev]
- name: Run tests
run: pytest
YAMLやっていることを噛み砕きます。
on: push / pull_request
main に push されたとき、main 向けの PR が作られたときに動く。
runs-on: ubuntu-latest
GitHub が用意してくれる Ubuntu の仮想マシンで動かす。
checkout
リポジトリのコードをその仮想マシンにチェックアウトする。
setup-python
指定したバージョンの Python を入れる。
Install dependencies
pip で依存ライブラリをインストールする。-e .[dev] は「このプロジェクトを開発モードでインストール+dev 依存も入れる」というイメージ。
Run tests
pytest を実行する。
ローカルでやっていることを、そのまま YAML に書いているだけです。
重要ポイント1:「いつテストを走らせるか」をちゃんと決める
典型的なトリガーは「push」と「pull_request」
よくあるパターンはこうです。
main への push
main 向けの PR
この二つでテストを走らせる設定にすることで、
PR を出した時点でテスト結果が分かる
main に入るコードは必ずテスト済みになる
という状態を作れます。
もう少し厳密にするなら、「main には直接 push させず、必ず PR 経由にする」+「PR に CI 成功を必須にする」というブランチ保護ルールを組み合わせます。
これで、「テストが赤いコードは main に入らない」世界になります。
重要ポイント2:「何をテストするか」を段階的に増やしていく
最初は「ユニットテストだけ」でいい
いきなり全部を自動化しようとすると、だいたい挫折します。
最初の一歩は、「pytest を回す」だけで十分です。
小さな関数のテスト
サービス層のテスト
FastAPI なら TestClient を使ったエンドポイントテスト
これらを pytest で書いておき、CI でそれを回します。
余裕が出てきたら「品質チェック」を足していく
次のステップとして、テスト以外のチェックも CI に乗せていきます。
フォーマットチェック(black)
静的解析(ruff / flake8)
型チェック(mypy)
テストカバレッジ計測(coverage.py)
例えば、Run tests の前にこういうステップを足せます。
- name: Lint
run: ruff check .
- name: Type check
run: mypy src
- name: Run tests
run: pytest --cov=src
YAMLこうすると、「テストが通る」だけでなく、「スタイルや型も一定の基準を満たしている」状態でないと PR が緑になりません。
人間のレビューは、より設計や仕様に集中できるようになります。
重要ポイント3:「速さ」と「安定性」が CI 自動テストの生命線
遅い CI は、だんだん誰も見なくなる
CI が毎回 20 分かかるような状態だと、開発体験が一気に悪くなります。
PR を出しても結果がなかなか返ってこないので、「まあローカルで通ってるしマージしちゃえ」となりがちです。
理想は、「数分以内に終わる」こと。
そのためにできる工夫はいくつかあります。
テストをユニットテスト中心にして、外部サービスへの依存をモックする
重い統合テストは別ジョブに分ける(例えば main への push のときだけ)
依存インストールをキャッシュする(actions/cache を使う)
「速くて、よく動く CI」は、それだけでチームのリズムを良くします。
フレークテスト(たまに落ちるテスト)は早めに潰す
CI 自動テストで一番ストレスなのが、「たまに落ちるテスト」です。
ネットワーク依存、時間依存、ランダム依存などが原因で、同じコードなのに通ったり落ちたりするテストは、信頼を削ります。
CI でフレークが見つかったら、「たまたま通ったからOK」ではなく、「原因を潰す」ことが大事です。
外部APIはモックする
時間に依存する処理は固定の時刻を注入する
ランダムはシードを固定する
CI 自動テストは「常に同じ結果が出る」ことが前提です。
ここが揺れると、DevOps 全体が不安定になります。
重要ポイント4:ローカルと CI の環境差をできるだけなくす
「ローカルでは通るのに CI では落ちる」を減らす
よくあるパターンはこうです。
ローカルの Python バージョンと CI のバージョンが違う
ローカルには入っている依存が CI では入っていない
OS の違いで挙動が変わる
これを減らすために、いくつかの工夫ができます。
pyproject.toml や requirements.txt で依存を明示的に管理する
Python バージョンを固定する(3.11 なら 3.11 に揃える)
できれば Docker イメージを使って、「ローカルも CI も同じコンテナでテストする」
特に Docker を使うと、「ローカルで docker compose up してテスト」「CI でも同じイメージでテスト」という構成にできるので、環境差によるバグがかなり減ります。
重要ポイント5:CI 自動テストを「開発フローの前提」にする
「テストが通らない PR はマージしない」をルール化する
CI 自動テストを本当に効かせるには、「文化」として定着させる必要があります。
その一番シンプルなルールがこれです。
テストが赤い PR はマージしない
main に入るコードは必ず CI が緑
GitHub のブランチ保護ルールで、「このブランチにマージするには CI 成功が必須」と設定しておくと、物理的に守れます。
これを徹底すると、「main は常にテスト済み」という状態が保たれます。
CD(自動デプロイ)と組み合わせると、「main にマージ = 本番候補」という強い流れが作れます。
「ローカルでテストしてから push」も習慣にする
CI 自動テストがあるからといって、ローカルで一切テストしなくていいわけではありません。
理想的な流れはこうです。
ローカルで pytest を回す
問題なければ push
CI でもう一度テスト(別環境でのダブルチェック)
ローカルは「素早いフィードバック」、CI は「客観的な保証」として役割分担させるイメージです。
初心者が CI 自動テストを身につけるためのステップ
ステップ1:ローカルで pytest を回す習慣をつける
まずは「テストを書く」「pytest を回す」が当たり前になること。
テストがないと、CI も何もしてくれません。
ステップ2:GitHub Actions で「push / PR で pytest を回す」最小の CI を入れる
さっきの ci.yml をほぼコピペでいいので入れてみる。
PR を作って、GitHub 上でテストが走るのを一度体験する。
ステップ3:少しずつチェックを増やし、ブランチ保護ルールを設定する
ruff や mypy を足してみる。
main に対して「CI 成功必須」を設定してみる。
ここまで来ると、「CI 自動テスト」が単なるオマケではなく、
あなたの開発フローの“前提”になっていきます。
まとめ(CI 自動テストは「push や PR のたびに、機械が真顔でテストしてくれる仕組み」)
初心者目線で整理すると、Python の CI 自動テストはこういうものです。
ローカルでやっている「依存インストール+pytest 実行」を、GitHub Actions などの CI サービス上で、push や PR をトリガーに自動で回す仕組み。
「テストが通らないコードは main に入れない」というルールと組み合わせることで、main が常にテスト済みの状態になり、DevOps 的な「小さく頻繁に安全にリリースする」流れの土台になる。
最初は「pytest を自動で回すだけ」から始めて、少しずつ Lint・型チェック・カバレッジ・ブランチ保護を足していくと、CI 自動テストが“面倒な儀式”ではなく、“自分の代わりに毎回ちゃんと確認してくれる相棒”になっていく。

