この記事のポイント

  • unittestのテストクラス設計をレビュー視点で整理できる
  • フィクスチャ責任の整理技法を学べる
  • 実務でのテスト構造整理の成長ステップを体系化できる
  • PlantUMLでテスト構造肥大化と整理イメージを可視化できる

そもそもunittestとは

Python標準ライブラリのunittestxUnit系統のテストフレームワークで、標準機能のみで次のようなテストが実現できます。

  • テストケースのクラス構造化(TestCase)
  • アサーションAPI提供(assertEqual、assertRaises等)
  • フィクスチャ機能(setUp/tearDown)
  • テストディスカバリ標準搭載
unittest基本例

import unittest

class TestMath(unittest.TestCase):
    def test_add(self):
        self.assertEqual(1 + 2, 3)

if __name__ == "__main__":
    unittest.main()

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

unittest設計は「初期は手軽、肥大後に崩壊しやすい」構造を持ちます。レビューアーは以下の視点を持つ必要があります。

レビューアー視点

  • テストクラス設計が責務分離できているか
  • フィクスチャ(setUp/tearDown)が膨張していないか
  • テスト名・粒度が読み取り可能か
  • 依存順序が発生していないか
  • テスト独立性が維持できているか

開発者視点

  • 手軽に書き始められるためスコープ拡大を軽視しがち
  • テストクラスに複数責務混在させがち
  • setUp肥大を後追い整理できなくなる
  • テスト依存順序を発生させがち

テスト構造崩壊の典型設計臭

崩壊しやすいパターン
  • テストクラス1つに機能横断のテストを集中
  • setUpで全フィクスチャ一括初期化
  • 名前付けが「test_正常系」「test_異常系」レベル
  • サブテストの過剰使用で構造が隠蔽
  • テスト順序依存で副作用発生

崩壊例:フィクスチャ集中型

setUp肥大化例

import unittest

class TestUserService(unittest.TestCase):
    def setUp(self):
        self.db = MockDatabase()
        self.user_service = UserService(self.db)
        self.mailer = MailerMock()
        self.payment_gateway = PaymentGatewayMock()
        self.logger = LoggerMock()

    def test_register_user(self):
        result = self.user_service.register("alice")
        self.assertTrue(result)

    def test_payment_process(self):
        result = self.payment_gateway.charge("alice", 100)
        self.assertTrue(result)
@Reviewer
setUpに全機能依存フィクスチャが混在しています。責務毎にクラス分離整理してください。

問題点

  • テストクラスが複数責務を同居
  • setUpで不要フィクスチャも毎回作成
  • 読解コストと保守性悪化

崩壊構造モデル:肥大集中構造

UML Diagram

良い設計整理アプローチ

unittestの整理文化は以下の段階が有効です。

① 責務別テストクラス分離

  • 機能単位・API単位でクラス分離

② 最小フィクスチャ原則

  • setUpは当該テストクラス最小単位で限定初期化

③ 明示命名ルール統一

  • test_機能_条件_期待結果 の3要素命名

④ サブテスト最小利用文化

  • テーブルテストは補助、設計臭の隠蔽に利用しない

改善例:責務分離版

責務別整理版

import unittest

class TestUserRegistration(unittest.TestCase):
    def setUp(self):
        self.db = MockDatabase()
        self.user_service = UserService(self.db)

    def test_register_user_success(self):
        result = self.user_service.register("alice")
        self.assertTrue(result)

    def test_register_user_duplicate(self):
        self.user_service.register("alice")
        with self.assertRaises(UserAlreadyExistsError):
            self.user_service.register("alice")

class TestPaymentProcessing(unittest.TestCase):
    def setUp(self):
        self.payment_gateway = PaymentGatewayMock()

    def test_payment_success(self):
        result = self.payment_gateway.charge("alice", 100)
        self.assertTrue(result)
「テストはクラスで分ける文化・フィクスチャを最小にする文化」

レビューアーはsetUp読解コストゼロ文化を常に推進すると健全性が維持できます。

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

UML Diagram

良くない実装例: ケース1(テーブル隠蔽依存)

サブテスト依存例

def test_user_registration(self):
    for name in ["alice", "bob", "charlie"]:
        with self.subTest(name=name):
            result = self.user_service.register(name)
            self.assertTrue(result)
@Reviewer
サブテスト乱用で失敗粒度が読みにくくなります。個別テスト分離を優先してください。

問題点

  • 失敗時原因特定コスト上昇
  • レビュー時の仕様判読困難化

良くない実装例: ケース2(依存順序型)

順序依存例

def test_create_user(self):
    self.user_service.create("alice")

def test_duplicate_user(self):
    with self.assertRaises(UserAlreadyExistsError):
        self.user_service.create("alice")
@Reviewer
テスト間順序依存が発生しています。事前状態初期化で完全独立化してください。

問題点

  • 実行順序依存バグ
  • 並列実行破綻

改善例:初期化整理版

完全独立版

def test_duplicate_user(self):
    self.user_service.create("alice")
    with self.assertRaises(UserAlreadyExistsError):
        self.user_service.create("alice")
  • 各テストで状態初期化明示
  • 実行順序非依存

観点チェックリスト

まとめ

unittestの設計は「初期は楽、肥大は地獄」という典型構造を持っています。
レビューアーは「責務分離文化・フィクスチャ最小文化・完全独立文化」を徹底教育することで、長期保守に耐える健全テスト設計を支援できます。