この記事のポイント
conftest.pyにフィクスチャをまとめると複数テストファイルから共有できる@pytest.fixture(params=[...])でパラメータ化フィクスチャを使うと、DBバックエンドや環境ごとのテストを自動展開できるmocker.patch()は"モジュール名.関数名"のパスで指定する——from app import funcした参照は別オブジェクトになるので直接パッチできないcoverage run -m pytest→coverage report --show-missingで未テスト行を特定できる- 本記事は Ubuntu 24.04 LTS(Docker公式イメージ)で実際に実行した結果を掲載しています
pytest の基礎(インストール・基本的なテスト実行)はすでに使えるとして、今回はフィクスチャ・モック・カバレッジ計測の3本柱を掘り下げます。
「外部APIを呼び出すコードをどうテストするか」「同じセットアップコードをテスト間で使い回したい」——実際の開発で壁にぶつかる問題です。Ubuntu 24.04 LTS の Docker 環境で全コマンドを実行し、出てきた実出力をそのまま掲載します。
動作確認済み環境
Ubuntu 24.04.4 LTS(docker run –rm ubuntu:24.04)/ Python 3.12.3 / pytest 9.1.1 / pytest-mock 3.15.1 / coverage 7.14.2(2026-06-22 実測)
目次
- インストール:pytest + pytest-mock + coverage
- フィクスチャの基本とconftest.py
- パラメータ化フィクスチャで複数環境を自動展開
- pytest-mock でモックを使う
- coverage でカバレッジを計測する
- バージョン別の違い(Ubuntu 22.04 vs 24.04)
- よくあるエラーと解決策
- まとめ
インストール:pytest + pytest-mock + coverage
Ubuntu には venv(仮想環境)を使ってインストールします。python3-venv パッケージが必要なので、先にインストールしておきます。
Reading package lists… Done
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install pytest pytest-mock coverage
Successfully installed coverage-7.14.2 pytest-9.1.1 pytest-mock-3.15.1 …
$ pytest –version
pytest 9.1.1
Docker 環境で実際に実行した結果がこちらです。

pytest-mock は pytest のプラグインで、テスト内で mocker フィクスチャを使えるようにするパッケージです。coverage は別パッケージとして提供されていますが、pip install 1行で全部入ります。
フィクスチャの基本とconftest.py
フィクスチャとは、テストに渡す「お膳立て済みのオブジェクト」を用意する仕組みです。@pytest.fixture デコレータを付けた関数がそれにあたります。
①基本的なフィクスチャを書く
テストファイルと同じ場所に conftest.py を作ると、そのディレクトリ以下の全テストから自動で読み込まれます。
@pytest.fixture
def sample_user():
return {“id”: 1, “name”: “Alice”, “email”: “alice@example.com”, “active”: True}
@pytest.fixture
def inactive_user():
return {“id”: 2, “name”: “Bob”, “email”: “bob@example.com”, “active”: False}
テスト関数の引数にフィクスチャ名を書くだけで、pytest が自動的に注入してくれます。
assert sample_user[“name”] == “Alice”
def test_user_active(sample_user):
assert sample_user[“active”] is True
class TestUserEmail:
def test_email_format(self, sample_user):
assert “@” in sample_user[“email”]
クラスの中でもフィクスチャは同じように使えます。self の次の引数として書くだけです。
②フィクスチャのスコープ
デフォルトでは、フィクスチャはテストごとに1回呼ばれます。DBコネクションのように「セッション中に1回だけ確立したい」場合は scope を指定します。
def db_connection():
conn = create_connection() # セッション全体で1回だけ
yield conn
conn.close() # テスト終了後にクリーンアップ
スコープは function(デフォルト)・class・module・session の4段階です。yield を使うと前処理と後処理を1つのフィクスチャに書けます——ここが最初に詰まったポイントでした。return ではなく yield にすると、yield 以降がテスト終了後に実行されます。
パラメータ化フィクスチャで複数環境を自動展開
「SQLiteとPostgreSQLの両方でテストを通したい」というケースで効果を発揮するのがパラメータ化フィクスチャです。
def db_backend(request):
return request.param
assert db_backend in (“sqlite”, “postgresql”)
これだけで、pytest が自動的に [sqlite] と [postgresql] の2パターンを作って実行します。

実測では7テストが0.01秒で全PASSしました。test_db_backend_available[sqlite] と test_db_backend_available[postgresql] が別テストとして実行されているのが確認できます。テストを追加するたびにパラメータの数だけ自動展開されるので、環境ごとの書き漏らしを防げます。
pytest-mock でモックを使う
外部API・データベース・SMTPサーバーなど、テスト環境で実際に呼び出せないものをテストするのに使います。mocker フィクスチャは pytest-mock が自動で提供してくれます。
①基本的なpatch
テスト対象の app.py は外部APIを呼び出します。
def fetch_user_status(user_id: int) -> str:
response = requests.get(f”https://api.example.com/users/{user_id}”)
data = response.json()
return data.get(“status”, “unknown”)
テストでは実際にHTTPリクエストを飛ばさず、mocker.patch() で差し替えます。
from unittest.mock import MagicMock
def test_fetch_user_status_active(mocker):
mock_response = MagicMock()
mock_response.json.return_value = {“status”: “active”, “id”: 1}
mocker.patch(“app.requests.get”, return_value=mock_response)
result = app.fetch_user_status(1)
assert result == “active”
よく踏む落とし穴:patchのパスはモジュール参照を指定する
from app import fetch_user_status とインポートしてしまうと、テスト側は fetch_user_status への参照を直接持ちます。この場合 mocker.patch("app.requests.get") は機能しますが、もし app.requests ではなく requests.get とパッチしてしまうと app.py の呼び出しには反映されません。パッチ先は「その関数が使われる名前空間」で指定するのが鉄則です。
②call_countで呼び出し回数を検証する
mock_response = MagicMock()
mock_response.json.return_value = {“status”: “active”}
mock_get = mocker.patch(“app.requests.get”, return_value=mock_response)
app.fetch_user_status(1)
app.fetch_user_status(2)
assert mock_get.call_count == 2
③side_effectで呼び出しごとに異なる値を返す
1回目と2回目で異なるレスポンスを返させたい場合は side_effect を使います。
mock_res1 = MagicMock()
mock_res1.json.return_value = {“status”: “active”}
mock_res2 = MagicMock()
mock_res2.json.return_value = {“status”: “inactive”}
mocker.patch(“app.requests.get”, side_effect=[mock_res1, mock_res2])
assert app.fetch_user_status(1) == “active”
assert app.fetch_user_status(2) == “inactive”
実際に動かすと4テスト全てPASSします。

coverage でカバレッジを計測する
テストが通っても、コードの全行を実行しているとは限りません。coverage を使うと「どの行がテストで実行されたか」を可視化できます。
①coverage run でテストを計測する
… 5 passed in 0.01s
$ coverage report –show-missing
Name Stmts Miss Cover Missing
————————————————–
calculator.py 12 0 100%
test_calculator.py 16 0 100%
————————————————–
TOTAL 28 0 100%

実測では calculator.py の全12ステートメントをカバーできました。pytest.raises で例外分岐(ゼロ除算)も通しているため、分岐行も100%に含まれます。
②HTMLレポートで視覚的に確認する
Wrote HTML report to htmlcov/index.html
$ python3 -m http.server 8080 –directory htmlcov
Serving HTTP on 0.0.0.0 port 8080 …
ブラウザで http://localhost:8080 を開くと、各ソースファイルの行ごとにカバレッジが色付きで確認できます。未実行行は赤くハイライトされるので、テストが届いていない分岐がひと目でわかります。
③pytestと組み合わせるなら pytest-cov が便利
$ pytest –cov=. –cov-report=term-missing
———- coverage: platform linux, python 3.12.3 ———-
Name Stmts Miss Cover Missing
calculator.py 12 0 100%
pytest-cov を使うと coverage run と coverage report を1コマンドにまとめられます。CI環境では --cov-fail-under=80 オプションを追加して、カバレッジが80%未満なら CI を失敗させる設定が一般的です。
バージョン別の違い(Ubuntu 22.04 vs 24.04)
Docker で Ubuntu 22.04 と 24.04 の両方を実際に動かして確認した結果です。

pytest・pytest-mock・coverage のバージョンは pip でインストールする場合は同じ最新版が入りますが、Python 本体は 22.04 が 3.10.12・24.04 が 3.12.3 と2世代の差があります。Python 3.11 以降ではエラーメッセージが大幅に改善されているので、VPS やサーバーを新規に立てるなら Ubuntu 24.04 を選ぶ方が長い目で見て得です。
GitHub Actions でCI統合する
ローカルで通ったテストをプッシュ時にも自動実行するのが CI です。GitHub Actions を使う場合、 .github/workflows/test.yml を以下のように書きます。
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-24.04
steps:
– uses: actions/checkout@v4
– uses: actions/setup-python@v5
with:
python-version: “3.12”
– run: pip install pytest pytest-mock pytest-cov
– run: pytest –cov=. –cov-fail-under=80
runs-on: ubuntu-24.04 にすることでローカルの Ubuntu 24.04 環境と揃えられます。--cov-fail-under=80 でカバレッジが80%を下回るとCIが赤くなる設定です。この数値は最初は低めに設定して、テストを増やしながら段階的に上げていくのが実際のプロジェクトでの使い方です。
よくあるエラーと解決策
①ModuleNotFoundError: No module named ‘pytest’
仮想環境が有効になっていない状態で実行しているケースがほとんどです。source .venv/bin/activate を実行してから pytest を叩いてください。
②AssertionError: Expected ‘xxx’ to be called once. Called 0 times.
from app import send_notification でインポートした後に mocker.patch("app.send_notification") してしまうと、テストコードが持つ参照はパッチ前のオブジェクトのままです。解決策は import app して app.send_notification(...) と呼ぶか、mocker.patch("test_app.send_notification") と呼び出し側の名前空間でパッチすることです。
③fixture ‘mocker’ not found
pytest-mock がインストールされていません。pip install pytest-mock で解決します。
④no data was collected(coverage)
coverage run pytest(-m なし)としているか、テストファイルが見つかっていないケースです。coverage run -m pytest と -m を付けてください。
まとめ
Ubuntu 24.04 LTS(Docker)で pytest 9.1.1 + pytest-mock 3.15.1 + coverage 7.14.2 を実際に動かして確認した内容をまとめます。
conftest.pyにフィクスチャを書くと、そのディレクトリ以下の全テストから自動で読み込まれる@pytest.fixture(params=[...])を使うとパラメータの数だけテストが自動展開されるmocker.patch("app.requests.get")は「その関数が使われる名前空間」でパッチする——from app import funcした参照は別オブジェクトなので引っかかりやすいside_effectに配列を渡すと呼び出し順に異なる戻り値を返せるcoverage run -m pytest && coverage report --show-missingで未テスト行を特定できる- GitHub Actions に組み込む場合は
--cov-fail-under=80でカバレッジの下限を設けるのが実用的
テストが書けるようになると、コードを変えるときの「動いていたものを壊していないか」という不安がかなり減ります。VPS でサーバーを運用するつもりなら、デプロイ前に CI でテストを通す習慣を早めに作るのがおすすめです。
VPS の選び方や初期セットアップは https://linuxlab.jp/ubuntu-server-setup/ にまとめています。


コメント