0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

レガシーWebシステムを改修する技術 (3) - 仕様化テスト・TDD実践と足場のクリーンアップ編

0
Posted at

この記事は Qiita Tech Festa 2026 の投稿記事です。

【連載】レガシーWebシステムを改修する技術:Feature Toggle, シャドーイング, リファクタリング

はじめに

第3回では、テスト手法と本番移行について解説します。

安全な改修には、コードの挙動を保証するテストが不可欠です。また、改修は新コードを稼働させて終わりではなく、「不要になった旧コードと足場を撤去する」ところまでが1つのタスクです。

実際の修正フローとの相違
トピックごとの説明にまとめた都合上、テストコードを順番に書いていますが、実際にはテストを作成で失敗させ、第2回の実装をしてテストの成功を確認するという変更のサイクルを繰り返してRed-Green TDDの形式で進めていきます

本題・実装:テストの実装

1. 既存コードへの外付けパッチテスト(仕様化テスト)

既存コードを変えずに pytest-mock (mocker フィクスチャ) を用いて、外部への副作用(print等)や戻り値のパターンの確認をします。

web_server/tests/test_characterization.py
from app.services import OldDataProcessingService

def test_old_service_characterization(mocker):
    service = OldDataProcessingService()
    
    # 副作用を mocker でキャプチャしつつ、現在の振る舞い(仕様)を確定させるテスト
    mock_print = mocker.patch('builtins.print')
    payload = {"data": "hello"}
    result = service.execute_pipeline(user_id=42, payload=payload)
    
    # 現在の戻り値の構造をアサーションして保護(仕様化テスト)
    assert result == {"status": "success", "legacy_id": 1142, "score": 50}
    mock_print.assert_called_once_with("[SideEffect] Writing to Legacy DB for user 42")

ここだけは既存のコードがあるのでコード->テストの順番で作成し、テストを書くことで仕様を把握をすることになります。

2. 新コードの Red-Green TDD

typing.Protocol を定義したおかげで、テスト時には外部依存を容易にモックやフェイクに差し替えて、テストを実行しやすくなりました。

web_server/tests/test_new_logic.py
import pytest
from app.services import NewDataProcessingService, DataProcessingService

def test_new_service_conforms_to_protocol():
    # 静的型チェッカー(mypy等)に適合しているかを明示的に示すアサーション代わり
    # NewDataProcessingService が Protocol を満たしているかチェック
    service: DataProcessingService = NewDataProcessingService()
    assert service is not None

def test_new_service_logic_with_tdd():
    # 新コードの単体テスト。外部依存がないので容易に Red -> Green のサイクルを回せる
    service = NewDataProcessingService()
    
    # 正常系のテスト
    res1 = service.execute_pipeline(user_id=1, payload={"data": "abc"})
    assert res1["score"] == 30
    
    # 境界値・異常系のテスト (例: 空文字の時の仕様変更などをTDDで追加)
    res2 = service.execute_pipeline(user_id=1, payload={"data": ""})
    assert res2["score"] == 0

typing.Protocol を使うことで既存のコードの継承関係などを変更することなく、シグネチャの一致をさせることである程度の対応保証ができるので後からの追加や設計の変更にも対応が比較的しやすいと思います(最初から要件が分かっていれば abc.ABC で作っておくのももちろんよいと思いますが)。

アプリケーションのエントリポイント

FastAPIでリクエストを受け付けるルーター部分です。ここからは具体的な旧サービス・新サービスを意識せず、トグル付きのラッパーサービスを1つ呼び出す形に収めます。

web_server/app/main.py
import os
from fastapi import FastAPI
from app.services import FeatureToggledDataProcessingService

app = FastAPI(title="Safe Refactoring Web Server")

TOGGLE_URL = os.getenv("TOGGLE_SERVER_URL", "http://localhost:8001/toggles/use-new-service")

# ラップされたサービスをグローバルまたは依存注入(DI)で保持
processing_service = FeatureToggledDataProcessingService(toggle_url=TOGGLE_URL)

@app.post("/process/{user_id}")
def process_endpoint(user_id: int, payload: dict):
    # 呼び出し箇所は一層Wrapされているため、コントローラーはリファクタリングの影響を受けない
    result = processing_service.execute_pipeline(user_id=user_id, payload=payload)
    return result

最終ステップ:安定稼働後の正式コード化(クリーンアップ)

シャドウモードやフラグONでの本番稼働において、ログに [Mismatch Detected] が一切出なくなり、不具合がないことを十分確認できたら、以下のクリーンアップを行います。

  1. OldDataProcessingService クラスを丸ごと削除
  2. FeatureToggledDataProcessingService を撤去し、NewDataProcessingService を直接呼ぶか、トグルを介さないシンプルな構成に書き換え
  3. トグルサーバーから該当フラグの定義を削除

この 「足場の解体作業」までを「1つの修正タスク」として組み込む ことが重要です。

この対応パターンにおいて工程が多い分時間がかかりがちという弱点以上に、 使い終わった部分の削除処理が行われないために、以降のコードベースに不要なコードが残り続ける という負債化しがちな要素があります。

前回の FeatureToggledDataProcessingService もそれなりに複雑なコードでしたし、これを残したまま NewDataProcessingService の改修が入ってしまったり、同じ個所で複数のフラグを使うとフラグの削除でトラブルになってしまうこともあります。

ここはプロセスの重さと改修箇所が一回で修正して失敗した場合のリスクとの比較での決定だと思います。

おわりに

ここまで全3回にわたり、レガシーシステムを無停止で改修するためのリファクタリング手法の「土台作り」から「クリーンアップ」までを解説しました。

  • typing.Protocol による抽象化
  • Feature Toggle、シャドーイング
  • テスト

これらを組み合わせることで、安心感を持ってコードを改善できます。

次回の 第4回(最終回) では、「LLM Agent」を活用した場合、今回の対応はどのようなプロンプトやフローで対応が可能かについて書こうと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?