この記事のポイント

  • mockを使った副作用遮断設計の責任範囲をレビュー視点で整理できる
  • mock対象の選定基準・整理技法を学べる
  • 実務での副作用管理文化を体系化できる
  • PlantUMLで副作用依存構造の整理イメージを可視化できる

そもそもmockとは

Pythonのunittest.mock(またはpytest-mock)は「外部依存の疑似置換」を提供するモジュールです。

  • 外部API・DB・ファイルIOなどの副作用遮断
  • 振る舞いの検証(呼び出し確認)
  • 戻り値の制御
  • 例外発生の疑似注入
mock基本例

from unittest import mock

def external_call():
    return "real data"

def logic():
    data = external_call()
    return data + " processed"

def test_logic():
    with mock.patch("__main__.external_call", return_value="mock data"):
        assert logic() == "mock data processed"
  • mock.patch()で対象を差し替え
  • 本物の外部依存を実行せず、テストで疑似制御

なぜこれをレビューするのか

mock活用は「副作用遮断=設計境界の可視化」に直結します。
レビューアーは以下の観察視点を持つ必要があります。

レビューアー視点

  • mock対象が適切な副作用単位に整理されているか
  • mockが過剰/不足になっていないか
  • mock依存でテストがブラックボックス化していないか
  • mock経路の指定が設計境界を反映しているか
  • mock副作用発生が抑制できているか

開発者視点

  • どこをmockすべきか曖昧になりがち
  • 便利なので上位層までmockしてしまいがち
  • 結果として意味の薄い「存在確認テスト」になる
  • 実質的なロジック検証が空洞化しがち

mock設計崩壊の典型設計臭

崩壊しやすいパターン
  • インフラ層より上位ロジック層までmock多用
  • 結果比較ではなくmock呼出回数だけ検証
  • mock依存が肥大し、実ロジック未検証化
  • mock対象のモジュール名指定が曖昧化
  • 上位APIのmock内ネストが複雑化

崩壊例:ロジック空洞化型

呼出検証偏重型

from unittest import mock

def send_mail(address):
    # 実際のメール送信
    pass

def register_user(user_id, email):
    # ユーザ登録ロジック
    send_mail(email)

def test_register_user():
    with mock.patch("__main__.send_mail") as mock_mail:
        register_user("alice", "[email protected]")
        mock_mail.assert_called_once_with("[email protected]")
@Reviewer
呼出確認のみで登録ロジック自体は未検証です。結果検証主体に整理しましょう。

問題点

  • send_mail呼出しか確認しておらず登録成否は未検証
  • 意味の薄い「mock存在確認テスト」になる

崩壊構造モデル:mock偏重依存

UML Diagram

mock整理の設計原則

mockは「設計責務境界の写像」で使います。
以下の整理文化が有効です。

① 副作用遮断目的の明示化

  • 通信・DB・ファイルIO・時間依存
  • 外部API契約部のみ遮断

② ロジック層はmockせず実行

  • 内部純粋計算は本物で通す

③ 戻り値制御 vs 呼出検証のバランス

  • 戻り値制御で正常系/異常系を網羅
  • 呼出検証は仕様契約部に限定

④ モジュール境界準拠の指定文化

  • mock.patch("パス")の指定はimport構造準拠

⑤ ネスト抑制文化

  • mock入れ子指定は可能な限り浅く制御

改善例:責務分離版

mock整理版

from unittest import mock

def send_mail(address):
    # メール送信処理
    pass

def register_user(user_id, email):
    # DB登録ロジック
    if email:
        send_mail(email)
    return True

def test_register_user_success():
    with mock.patch("__main__.send_mail"):
        result = register_user("alice", "[email protected]")
        assert result

def test_register_user_without_email():
    result = register_user("alice", None)
    assert result
「ロジック実行は本物、外部だけ差し替え文化」

レビューアーは「mock境界文化」を徹底指導すると設計崩壊を抑止できます。

改善構造モデル:責任分離整理

UML Diagram

良くない実装例: ケース1(過剰ネスト)

過剰ネスト例

with mock.patch("__main__.send_mail") as mail_mock, \
     mock.patch("__main__.create_user") as user_mock, \
     mock.patch("__main__.update_profile") as profile_mock:
    ...
@Reviewer
mock入れ子が過剰です。mock範囲を設計責務単位に縮小整理してください。

問題点

  • 読解難易度激増
  • テスト意図不透明化

良くない実装例: ケース2(境界崩壊)

境界違反例

with mock.patch("external_api.send_mail") as mail_mock:
    ...
@Reviewer
import経路に準拠せず直接外部パスを指定しています。呼出元モジュールパスに合わせてください。

問題点

  • patch対象が正しく解決されない
  • メンテ負荷増大

改善例:import経路準拠

import構造準拠版

# main.pyで import external_api
from external_api import send_mail

# テスト時は
with mock.patch("main.send_mail"):
    ...
  • テスト側もimport経路準拠
  • patch対象の解決が安定

観点チェックリスト

まとめ

mock設計は「責務境界の写像精度で崩壊度が決まる」領域です。
レビューアーは「mock文化・副作用遮断文化・設計境界準拠文化」を育成指導することで、長期保守に耐える安全なテスト設計を築けます。