第10回までで、
- CoffeeLog / FishingLog クラスを作り
- メソッドで「お気に入り判定」「50アップ判定」「コメント生成」などをできるようにし
to_dict()/from_dict()でCSVとの橋渡しも覚えました。
ここまで来ると、ふとこんな不安がよぎります。
- 「ちょっとコードを直したら、前に動いていたところが壊れてないかな?」
- 「スコア4.5のとき、本当にあのコメントが返ってきてる?」
- 「50アップ判定、本当に50以上だけTrueになってる?」
毎回、手で試してもいいけれど、
“いつでも一発で” 動作確認できる小さな安心装置
があると、ツールを育てるのがずっと楽になります。
今回のテーマは、
pytestというテストツールを使って、
CoffeeLog / FishingLog がちゃんと動いているか自動チェックできるようにすること。
ついでに、pytestを入れるタイミングで
- 仮想環境(venv)
- requirements.txt
という “大人のPythonの育て方” にも軽く触れておきます。
この回のゴール
第11回で目指すゴールは次の4つです。
- pytestが何をしてくれるツールなのかイメージできる
test_*.pyにassertを書いて、テストを実行できる- CoffeeLog / FishingLog のメソッドや
to_dict()/from_dict()をテストできる - pytestを入れるための 簡単なvenv+requirements.txtの使い方の雰囲気 をつかむ
細かい理屈よりも、
- 「
pytestコマンドを打つと、赤か緑かで教えてくれる」
こういう“安心スイッチ”が1つ増えることを感じてもらえたら十分です。
pytestを使うためのシンプル環境づくり(venv+requirements)
pytest を使う為の事前準備をします。
pytest モジュールは Python とは別にインストールする必要があります。
そこで、Python 製の仮想環境(venv)を構築して、そこに pytest をインストールする流れになります。
これをすると、今何のモジュールがインストールされているか?など管理することが容易になってきます。
仮想環境(venv)とは?
仮想環境(venv)は、
「このフォルダ専用のPython環境(ライブラリの箱)」
のようなものです。
- 別のプロジェクトとライブラリのバージョンが混ざらない
- 必要なくなったら、そのフォルダごと消せばきれいに片づく
というメリットがあります。

1.プロジェクト用フォルダに移動する
例として、これまでのコードを置いているフォルダが
celestial-python/
とします。ターミナルで:
cd celestial-python
2.venv を作る
python -m venv .venv
.venv というフォルダに、専用のPython環境が作られます。
3.venv を有効化する(macOS / Linux)
Macを使っているなら、まずこれをメインで書いてOK:
source .venv/bin/activate
ターミナルの先頭に (.venv) などと表示されればOKです。
※Windowsを使っているなら:
.venv\Scripts\activate
4.pytest をインストールする
仮想環境が有効な状態で:
pip install pytest
これで、このプロジェクト内で pytest コマンドが使えるようになります。
5.requirements.txt に書き出しておく(任意だけどおすすめ)
pip freeze > requirements.txt
requirements.txt を開くと、pytest==... などが書かれているはずです。
別の環境で同じセットを用意したいときは:
pip install -r requirements.txt
とするだけで、同じバージョンのライブラリをインストールできます。
💡 まとめ
- 「pytestを入れるあたりから、余力があれば venv+requirements に慣れていくのがおすすめ」
- 「venvを飛ばして
pip install pytestだけでもOK」
pytestとは?— assert が全部まとめてチェックされる
pytestは、
「
assertを書いた関数たちを、まとめて自動で実行してくれるツール」
です。
test_*.pyというファイルを用意し- 中に
test_〜という関数を書き - その中で
assertで「こうなっていてほしい」を書く
たったこれだけで、pytest コマンドから一斉にチェックできます。
最小のサンプル:足し算関数をテストする
math_utils.py を作る
# math_utils.py
def add(a, b):
return a + b
test_math_utils.py を作る
# test_math_utils.py
from math_utils import add
def test_add_simple():
assert add(2, 3) == 5
def test_add_zero():
assert add(0, 10) == 10
pytestを実行する
ターミナルで(venvが有効な状態で):
pytest
すると、こんな感じの出力が出ます:
============================= test session starts =============================
collected 2 items
test_math_utils.py .. [100%]
============================== 2 passed in 0.03s ==============================
.が1つ=テスト1個成功2 passed=2つのテストが全部通った
この感覚を一度味わっておくと、
「CoffeeLog / FishingLog の動きも、同じようにチェックしたくなる」はず。
前提:CoffeeLog クラス(第10回のもの)
CoffeeLog を pytest でテストしてみる
ファイル:models.py に CoffeeLog があるとします(第10回のものから少し抜粋)。
# models.py
class CoffeeLog:
def __init__(self, name, roast, memo, score):
self.name = name
self.roast = roast
self.memo = memo
self.score = float(score)
def is_favorite(self, threshold=4.0):
return self.score >= threshold
def comment(self):
if self.score >= 4.5:
return "かなり特別な一杯。記念すべきレベル。"
elif self.score >= 4.0:
return "安定して美味しい。リピートしたい。"
elif self.score >= 3.0:
return "普通に美味しい。気分次第でまた飲むかも。"
else:
return "好みとは少し違うかもしれない。"
CoffeeLog用のテストを書く
ファイル:test_coffee_log.py
# test_coffee_log.py
from models import CoffeeLog
def test_is_favorite_true_when_score_high():
log = CoffeeLog("エチオピア浅煎り", "浅煎り", "朝の湖に合う", 4.5)
assert log.is_favorite() is True
assert log.is_favorite(threshold=4.5) is True
def test_is_favorite_false_when_score_low():
log = CoffeeLog("ブレンド", "中煎り", "普通においしい", 3.0)
assert log.is_favorite() is False
def test_comment_for_various_scores():
high = CoffeeLog("スペシャル", "浅煎り", "特別な一杯", 4.6)
mid = CoffeeLog("いつもの", "中煎り", "安定の味", 4.0)
normal = CoffeeLog("カフェ", "深煎り", "仕事の相棒", 3.2)
low = CoffeeLog("微妙", "深煎り", "好みじゃない", 2.5)
assert "特別" in high.comment()
assert "リピート" in mid.comment()
assert "普通に美味しい" in normal.comment()
assert "好みとは少し違う" in low.comment()
ここでやっていることはシンプルで:
- CoffeeLogのインスタンスをいくつか作り
is_favorite()やcomment()の結果が「期待どおりか?」をassertで確認する
だけです。
前提:FishingLog クラス(第10回のもの)
FishingLog を pytest でテストする
# models.py の続き
class FishingLog:
def __init__(self, date, lake, fish_type, length_cm, is_released):
self.date = date
self.lake = lake
self.fish_type = fish_type
self.length_cm = int(length_cm)
self.is_released = bool(is_released)
def is_50up(self):
return self.length_cm >= 50
def is_brown(self):
return self.fish_type == "ブラウントラウト"
def summary(self):
base = f"{self.date} {self.lake} {self.fish_type} {self.length_cm}cm"
if self.is_released:
base += "(リリース)"
return base
FishingLog用テスト
ファイル:test_fishing_log.py
# test_fishing_log.py
from models import FishingLog
def test_is_50up_true_when_length_50_or_more():
log = FishingLog("2025/05/01", "中禅寺湖", "ブラウントラウト", 50, True)
assert log.is_50up() is True
log2 = FishingLog("2025/05/02", "中禅寺湖", "ブラウントラウト", 60, False)
assert log2.is_50up() is True
def test_is_50up_false_when_under_50():
log = FishingLog("2025/05/03", "中禅寺湖", "ブラウントラウト", 49, True)
assert log.is_50up() is False
def test_is_brown():
brown = FishingLog("2025/05/01", "中禅寺湖", "ブラウントラウト", 45, True)
lake = FishingLog("2025/05/01", "中禅寺湖", "レイクトラウト", 60, False)
assert brown.is_brown() is True
assert lake.is_brown() is False
def test_summary_includes_basic_info():
log = FishingLog("2025/05/01", "中禅寺湖", "ブラウントラウト", 52, True)
summary = log.summary()
assert "2025/05/01" in summary
assert "中禅寺湖" in summary
assert "ブラウントラウト" in summary
assert "52cm" in summary
assert "リリース" in summary
CoffeeLog の変換が壊れていないか確認する
to_dict() / from_dict() のテスト
# models.py(CoffeeLogに to_dict / from_dict を追加済みとする)
class CoffeeLog:
...
def to_dict(self):
return {
"name": self.name,
"roast": self.roast,
"memo": self.memo,
"score": self.score,
}
@classmethod
def from_dict(cls, row):
return cls(
name=row["name"],
roast=row["roast"],
memo=row["memo"],
score=float(row["score"]),
)
テスト:test_coffee_log_dict.py
# test_coffee_log_dict.py
from models import CoffeeLog
def test_to_dict_and_from_dict_roundtrip():
original = CoffeeLog("エチオピア浅煎り", "浅煎り", "朝の湖に合う", 4.5)
data = original.to_dict()
restored = CoffeeLog.from_dict(data)
assert restored.name == original.name
assert restored.roast == original.roast
assert restored.memo == original.memo
assert restored.score == original.score
こうしておけば、
- CSVへ書き出し(dict)
- CSVから読み込み(dict)
- クラスへ戻し
の流れが壊れていないか、一瞬で確認できます。
pytest.raises を使って「ちゃんと例外が出る」をテスト
例外処理もテストしてみる(軽く)
第9回で validate_score() のような関数を考えました。
def validate_score(score):
if not (0.0 <= score <= 5.0):
raise ValueError(f"スコアが範囲外です: {score}")
これをpytestでテストするときは pytest.raises を使います。
# test_validation.py
import pytest
from models import validate_score
def test_validate_score_ok():
validate_score(0.0)
validate_score(4.5)
validate_score(5.0) # ここまでは例外が出ないはず
def test_validate_score_raises_for_invalid_value():
with pytest.raises(ValueError):
validate_score(-0.1)
with pytest.raises(ValueError):
validate_score(5.1)
「このコードは例外を投げてほしい」というケースも
テストで表現できるのが、pytestの良いところです。
pytestの実行と、よくあるつまずき
テストの実行コマンド
フォルダ構成の一例:
celestial-python/
.venv/
models.py
math_utils.py
test_math_utils.py
test_coffee_log.py
test_fishing_log.py
test_coffee_log_dict.py
test_validation.py
requirements.txt
この状態で、プロジェクトルート(celestial-python/)で:
pytest
だけでOKです。test_*.py が自動ですべて拾われます。
よくあるつまずき
pytestが見つからないと言われる
- 仮想環境を有効化していない
- あるいはインストールしていない
- →
source .venv/bin/activate→pip install pytest
importエラーになる(modelsが見つからないなど)
test_*.pyとmodels.pyが同じフォルダにあるかチェック- 別フォルダに分けた場合は、パス設定が必要(発展ネタ)
テストが1件も見つからない
- ファイル名が
test_〜.pyになっているか - 関数名が
test_〜で始まっているか
今日のまとめ – pytestは「湖畔ツールの見守り役」
今回は、
- pytestとは何か(
assertを自動でまとめてチェックしてくれるツール) - 最小サンプル:足し算関数のテスト
- CoffeeLog / FishingLog のメソッドをテストする方法
to_dict()/from_dict()の往復テスト- 例外処理(validate_score)のテスト
- pytest導入のタイミングでの venv+requirements.txt の軽い導入
を見てきました。
pytestが1つ入るだけで、
- 「コードを少し変えても、
pytestで一発チェック」 - 「昔書いたCoffeeLog / FishingLogの挙動が、今も約束どおりかすぐ分かる」
という状態になります。
湖畔で釣りをするとき、
ちゃんと整備されたタックルがあると安心して投げられるように、
pytestは あなたのPythonツールを静かに見守ってくれる存在 になります。



コメント