privateがない言語で壊れにくいテストをどう設計するか
リード
最近、Pythonのユニットテストでモック地獄に陥りました。
単純なリファクタリングが壊れやすいテストによって阻害されることを痛感しました。
Pythonでユニットテストを書くとき、Java/C#のメンタルモデル(クラス=単位、private=守る)をそのまま持ち込むと、すぐに違和感にぶつかります。
Pythonはクラス前提ではなく、アクセス修飾子も慣習ベース。
だからこそ 「ユニット=関数/モジュール/公開API」へと再定義 し、実装詳細から距離を取る設計が鍵になります。
本稿は、古典派(Chicago)とロンドン派(London)という二つのテスト観を土台に、Pythonでモック地獄に陥らず、リファクタに強いテスト設計を実現するための考え方をまとめました。
原則・判断軸・アンチパターン・運用に絞って整理します。
この記事のゴール
- 「ユニット」をクラス前提から解放し、関数/モジュール/公開APIとして捉え直す
- 古典派とロンドン派の使い分け方を、Pythonの文脈に馴染むハイブリッド戦略として理解する
- 良いメソッドはテストしやすいを設計の指針に据え、依存の外出し(DI)・公開境界の明確化を進める
- アンチパターンを見抜き、軽量な運用とチーム合意で回せるようにする
想定読者
- Pythonでユニットテストを書いている/これから書くアプリケーションエンジニア
- Java/C#由来の設計観でPythonに違和感を覚えている方、もしくは覚える前の方
- テストの量ではなく質と保守性を高めたいテックリード/レビュアー
1. なぜPythonのテストは「難しく感じる」のか
- クラス中心でない:Pythonでは、関数やモジュールを基礎的な設計単位として扱う。
- アクセス修飾子が慣習:内部を“隠す”より「公開面を明確にする」文化。
ユニットテストでどこをテストするかの基準を設けず、privateメソッドのテストをどのように実現するかに固執すると、テストが実装詳細に密着し、リファクタで壊れやすくなります。
2. 原則:良い関数・モジュールはテストしやすい
テスト容易性は、設計の良し悪しを客観的に測る目安になります。
単一責任、明瞭な入出力、外部依存を切り出して注入、副作用の隔離が整っていれば、自然とテストは軽く、読めばわかる形になります。
※ただしDB/HTTP/ファイル等の境界は、単体で完璧を求めず、契約テスト/統合テストと組み合わせるのが現実的。
3. ユニットの再定義:関数/モジュール/公開API
「ユニット=クラス/メソッド」ではなく、振る舞いの面(外から見える契約)を単位にする。
- 関数:副作用を持たず、同じ入力なら同じ出力を返す計算の単位
- モジュール:関連関数のまとまり(内部と公開の境界を引ける)
- 公開API:利用者の視点での契約。内部詳細に依存しないテストが組める
4. 公開境界をはっきりさせるという設計(概念としての __all__)
Pythonでは「隠す」よりも**「どれを公開するか」の明示が大切です。
公開=契約。契約に基づいてテストを書けば、内部の入れ替えに強くなります。
内部ロジックは内部のまとまりとして分離し、必要なら内部だけの小さなテスト**で支える、という二層構造が安定します。
5. 依存を外に出す:DI思考と非決定要素の扱い
時刻、乱数、環境変数、ネットワーク、ストレージ ―― 非決定要素は注入して制御可能に。
- 設計で差し替えやすさを作る
- 入力として受け取れば、テストは状態検証に集中でき、モック過多を避けられる
6. 二つの学派:古典派とロンドン派の要点
-
古典派(Chicago):
状態ベースで結果を検証、依存は本物か軽いフェイク。リファクタ耐性が高い。 -
ロンドン派(London):
相互作用(呼び出し)を検証、依存はモック/スタブ。失敗の局所化に強い。
古典派とロンドン派を、目的とレイヤで使い分ける。
7. 古典派とロンドン派のハイブリッド戦略
- コア計算:古典派(状態検証/広い入力・不変条件)
- 境界(DB/HTTP/時刻):ロンドン派(契約確認/相互作用の最小検証)
- 中心は純粋なロジック、外周は現実との接点として設計する。
8. よくあるアンチパターンと抜け道
-
モック地獄:相互作用の網を張りすぎ、名前や順序の変更でテストが粉々
- → 契約の最小限に絞り、状態検証へ寄せる
-
“private不安”からのテスト肥大:すべてをクラスに押し込み、テスト対象が増殖
- → 内部のまとまりを分離し、公開APIから検証
-
パッチ乱用:モジュール内実装に密着してしまう
- → 設計で依存を表に出す(先に差し替え可能にする)
9. チームに広げる:レビュー基準と運用
- 公開面の明確化をレビュー項目に入れる(何を契約として見せるか)
- テストの粒度を揃える(コアは状態、境界は契約)
- アンチパターンのレッドフラグを共有(モック密度、パッチ依存、巨大クラス化)
- 小さく回す:小さなリファクタ+小さなテスト改善のサイクルでチーム合意を育てる
10. まとめ
Pythonの前提(クラス前提ではない/アクセス修飾子は慣習)に合わせて、ユニットの定義を更新しよう。
良いメソッドはテストしやすい――この実務的な指針を中心に、古典派×ロンドン派のハイブリッドで壊れにくいテストへ。
次にやるべきことは一つ。毎回結果が変わりうる要素を外に出す。そこから設計もテストも、驚くほど軽くなります。
Discussion