Python | DevOps・運用:CI 自動テスト

Python Python
スポンサーリンク
  1. CI 自動テストって何?一言でいうと「人がサボっても、機械が毎回テストしてくれる仕組み」
  2. まずはイメージ:Python プロジェクトでの CI 自動テストの流れ
    1. ローカルでやっていることを、そのままクラウドで自動化する
  3. 具体例:GitHub Actions で pytest を自動実行する設定
    1. 最小の CI 設定ファイルを書いてみる
  4. 重要ポイント1:「いつテストを走らせるか」をちゃんと決める
    1. 典型的なトリガーは「push」と「pull_request」
  5. 重要ポイント2:「何をテストするか」を段階的に増やしていく
    1. 最初は「ユニットテストだけ」でいい
    2. 余裕が出てきたら「品質チェック」を足していく
  6. 重要ポイント3:「速さ」と「安定性」が CI 自動テストの生命線
    1. 遅い CI は、だんだん誰も見なくなる
    2. フレークテスト(たまに落ちるテスト)は早めに潰す
  7. 重要ポイント4:ローカルと CI の環境差をできるだけなくす
    1. 「ローカルでは通るのに CI では落ちる」を減らす
  8. 重要ポイント5:CI 自動テストを「開発フローの前提」にする
    1. 「テストが通らない PR はマージしない」をルール化する
    2. 「ローカルでテストしてから push」も習慣にする
  9. 初心者が CI 自動テストを身につけるためのステップ
    1. ステップ1:ローカルで pytest を回す習慣をつける
    2. ステップ2:GitHub Actions で「push / PR で pytest を回す」最小の CI を入れる
    3. ステップ3:少しずつチェックを増やし、ブランチ保護ルールを設定する
  10. まとめ(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 自動テストが“面倒な儀式”ではなく、“自分の代わりに毎回ちゃんと確認してくれる相棒”になっていく。

タイトルとURLをコピーしました