シリーズ構成
この記事は2部構成です。
| 記事 | 内容 |
|---|---|
| 前編(本記事) | 基本・assert・pytest.raises・parametrize・カバレッジ |
| 後編 | fixture・モック |
まずはこの前編で基本を身につけてから、後編に進むのがおすすめです。
はじめに
「テストコードって何?なんで必要なの?」というところから始めて、実際にPytestでテストを書いて動かすところまでを丁寧に解説します。
難しいことは一切なし。Pythonが少し書けるなら大丈夫です。
テストって何?なんで書くの?
テストとは、「自分のコードが期待どおりに動くかを自動で確認する仕組み」 です。
たとえば「1 + 1 を計算する関数を作ったとして、本当に2が返ってくるか?」を自動でチェックしてくれます。
手動で動作確認するのと何が違うの?
| 手動確認 | テストコード |
|---|---|
| 毎回自分で実行して目で確認 | コマンド一発で全部まとめてチェック |
| 変更のたびに全部やり直し | 何度でも瞬時に繰り返せる |
| 確認漏れが起きやすい | 網羅的に確認できる |
Pytestとは?
pytest はPythonで最もよく使われるテストフレームワークです。
- シンプルで書きやすい
- 公式の
unittestより直感的 - 業務でも個人開発でも広く使われている標準的な選択肢
インストール
pip install pytest
インストールできたか確認:
pytest --version
pytest 8.x.x のようなバージョンが表示されればOKです。
まず動かしてみる
Step 1: テスト対象の関数を作る
calculator.py というファイルを作ります。
# calculator.py
def add(a, b):
return a + b
Step 2: テストファイルを作る
test_calculator.py というファイルを作ります。
# test_calculator.py
from calculator import add
def test_add():
result = add(1, 2)
assert result == 3
ポイントが3つあります:
- ファイル名は
test_で始める → Pytestが自動で見つけてくれます - 関数名も
test_で始める → テスト対象として認識されます -
assertで期待値と比較する → 一致しなければテスト失敗
Step 3: テストを実行する
pytest
こんな結果が表示されれば成功です:
========================= test session starts ==========================
collected 1 item
test_calculator.py . [100%]
========================== 1 passed in 0.01s ===========================
🎉 テストが通りました!
assert の基本
テストの肝は assert 文です。
assert 1 + 1 == 2 # OK(True)
assert "hello"[0] == "h" # OK(True)
assert [1, 2, 3][1] == 2 # OK(True)
assert 1 + 1 == 3 # NG → テスト失敗になる
assert 期待する条件 と書くだけ。条件がFalseになったときにテスト失敗と判定されます。
テストが失敗したときはどう見える?
わざと失敗させてみましょう。
def test_add_wrong():
result = add(1, 2)
assert result == 999 # わざと間違えた
実行すると:
FAILED test_calculator.py::test_add_wrong - AssertionError: assert 3 == 999
どのファイルの・どの関数で・何が違ったかが一目でわかります。
複数のテストをまとめて書く
テスト関数はいくつでも書けます。
# test_calculator.py
from calculator import add
def test_add_positive():
assert add(1, 2) == 3
def test_add_negative():
assert add(-1, -2) == -3
def test_add_zero():
assert add(0, 5) == 5
pytest を実行すると、全部まとめてチェックしてくれます:
test_calculator.py ... [100%]
3 passed in 0.01s
よく使うオプション
# 詳細を表示(どのテストが通ったか一覧で見たい)
pytest -v
# 特定のファイルだけ実行
pytest test_calculator.py
# テスト名を指定して実行
pytest test_calculator.py::test_add_positive
ファイル構成の例
小さなプロジェクトならこれで十分です:
my_project/
├── calculator.py # テスト対象のコード
└── test_calculator.py # テストコード
大きくなってきたら:
my_project/
├── src/
│ └── calculator.py
└── tests/
└── test_calculator.py
Note:
src/以下にコードを置く構成では、from calculator import addがそのままでは動きません。プロジェクトルートにconftest.pyを置くか、PYTHONPATH=src pytestのように環境変数を設定してください。
ステップアップ①:例外をテストする(pytest.raises)
「この入力のときはエラーになるはず」という動作もテストできます。
たとえば calculator.py に割り算関数を追加します:
# calculator.py
def divide(a, b):
if b == 0:
raise ValueError("0で割ることはできません")
return a / b
ゼロ除算のときに ValueError が発生することをテストするには:
# test_calculator.py
import pytest
from calculator import divide
def test_divide_by_zero():
with pytest.raises(ValueError):
divide(10, 0)
with pytest.raises(例外の種類): のブロック内で指定の例外が発生すれば、テスト成功です。
エラーメッセージの内容まで確認したい場合:
def test_divide_by_zero_message():
with pytest.raises(ValueError, match="0で割ること"):
divide(10, 0)
match= には正規表現パターンを指定します。指定したパターンがエラーメッセージに含まれていればテスト成功です("0で割ること" は正規表現としても機能します)。
ステップアップ②:複数パターンをまとめてテストする(@pytest.mark.parametrize)
同じ関数に対して「入力と期待値の組み合わせ」を複数試したいとき、テスト関数を何個も書くのは冗長です。
@pytest.mark.parametrize を使うとすっきり書けます:
import pytest
from calculator import add
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(-1, -2, -3),
(0, 5, 5),
(100, -100, 0),
])
def test_add(a, b, expected):
assert add(a, b) == expected
実行すると、4パターンが個別のテストとして実行されます:
test_calculator.py::test_add[1-2-3] PASSED
test_calculator.py::test_add[-1--2--3] PASSED
test_calculator.py::test_add[0-5-5] PASSED
test_calculator.py::test_add[100--100-0] PASSED
1つ失敗しても、どのパターンで失敗したかが一目でわかります。
ステップアップ③:カバレッジを計測する(pytest-cov)
カバレッジとは、「テストによってコードの何%が実行されたか」を示す指標です。カバレッジが低い=テストできていない箇所が多い、ということを意味します。
インストール
pip install pytest-cov
実行方法
pytest --cov=calculator
出力例:
---------- coverage: platform linux, python 3.11 ----------
Name Stmts Miss Cover
------------------------------------
calculator.py 6 1 83%
------------------------------------
TOTAL 6 1 83%
Miss の列が「テストされていない行数」、Cover が「カバレッジ率」です。
どの行がテストされていないか確認する
pytest --cov=calculator --cov-report=term-missing
Name Stmts Miss Cover Missing
----------------------------------------------
calculator.py 6 1 83% 12
Missing の列に行番号が表示されます。その行を見てテストを追加するかどうか判断できます。
カバレッジの目安
100%を目指す必要はありません。重要なのは**「テストされていない箇所を把握すること」**です。
| カバレッジ | 目安 |
|---|---|
| 〜50% | テストが少ない。重要な関数から優先的に追加 |
| 70〜80% | 多くのプロジェクトで現実的な目標ライン |
| 90%以上 | かなり手厚い状態。維持コストとのバランスを意識 |
まとめ
| やること | 方法 |
|---|---|
| インストール | pip install pytest |
| テストファイルを作る | ファイル名を test_ で始める |
| テスト関数を書く | 関数名を test_ で始め、assert で確認 |
| テストを実行する | pytest |
| 例外をテストする | pytest.raises |
| 複数パターンをテストする | @pytest.mark.parametrize |
| カバレッジを確認する |
pip install pytest-cov → pytest --cov
|
まずは基本の assert から始めて、慣れてきたら parametrize でテストを整理し、pytest-cov でカバレッジを確認するという流れがおすすめです。
さらに先へ
- fixture → テストの前準備(DBの初期化など)を共通化する
-
モック(
unittest.mock)→ 外部APIやファイルI/Oを擬似的に差し替える - CI連携 → GitHub ActionsなどでPushのたびに自動テストを走らせる