マガジン

テストがない既存コードをClaude Codeで触る前に:現状を固定するテストの作り方

AIを使いこなす

Claudeで執筆し、GPTで自動校閲・修正しています。

紙のチェックリストに入力例と出力値を書き留め、既存コードの現状を記録している様子
記事の内容をもとに制作したAI生成イメージです。実際の製品画面や検証結果の写真ではありません。

テストがないコードをClaude Codeで直す前にやることは、「いまの挙動をそのまま記録するテスト」を、小さな関数1つから先に作ることです。このテストは、コードが正しいかどうかを判定するものではありません。「変更前はこう動いていた」を残し、リファクタ後に差が出たらすぐ気づけるようにします。ソフトウェア開発では、こうしたテストを特性テスト(characterization test)と呼びます。 ポイントは3つです。 • 期待値は、Claudeの推測ではなく、実際にコードを実行して得た値にする • 気になる挙動(おそらくバグ)も、まず「現状」として記録する。直すのは別の変更にする • テストが本当に挙動を捉えているかは、人が確認する この記事では、対象関数の選び方、そのまま使える依頼文、確認の手順を順に示します。

この記事の目次
  1. 1. Claudeに任せることと、人が決めること
  2. 2. 最初に触る関数の選び方
  3. 3. 依頼文の例
  4. 4. テストの形(説明用の例)
  5. 5. 人が確認すること
  6. 進め方のまとめ

1. Claudeに任せることと、人が決めること

Claude Codeは、既存のテストファイルを読んで、プロジェクトで使っているフレームワークや書き方に合わせたテストを書けます。公式ドキュメントでも、テストを頼むときは「確かめたい挙動を具体的に伝える」ことが勧められています(Common workflows: Work with tests)。リファクタについても、小さく検証できる単位で進めるよう助言されています。

役割分担は次のとおりです。

  • 関数の読解、入力パターンの洗い出し、Claude:任せる、人:対象の関数を指定する
  • テストコードの下書き、Claude:任せる、人:書き方の方針(フレームワーク、配置先)を渡す
  • 期待値の取得、Claude:実行して値を記録させる、人:意外な値がないか目を通す
  • 「これはバグでは?」の判断、Claude:候補を挙げさせる、人:直すか残すかを決める
  • テストが機能するかの確認、Claude:手伝わせてもよい、人:最終的に自分で見る

2. 最初に触る関数の選び方

最初から重要で複雑な関数を選ぶと、テストが膨らんで挫折します。次の基準で、小さくて確認しやすいものから選びます。

  • 入出力、向いている:引数を受けて値を返す、後回しにする:DB・ファイル・ネットワークを直接触る
  • 副作用、向いている:ほぼない、後回しにする:グローバル変数や状態を書き換える
  • 非決定要素、向いている:ない、後回しにする:現在時刻、乱数、外部APIに依存する
  • 大きさ、向いている:数十行まで、後回しにする:数百行、分岐が深い
  • 変更予定、向いている:これからリファクタする箇所、後回しにする:触る予定のない箇所

最後の行が大事です。「全体にテストを付ける」のではなく、「これから触る関数の周りだけ」を固定します。時刻や乱数に依存する関数は、まず依存する値を引数で渡せるかを検討し、難しければ別の関数から始めてください。

3. 依頼文の例

以下は、手順ごとの依頼文の例です。calc_shipping_fee と src/shipping.py は説明用の名前なので、自分のプロジェクトに置き換えてください。

手順1:まず読むだけにする(計画モード)

Claude Codeには、調査と計画に使う計画モード(plan mode)があります。CLIでは Shift+Tab で切り替えるか、claude --permission-mode plan で起動できます。ただし計画モードでも、自動承認される操作は利用環境によって異なります(Choose a permission mode)。いきなりテストを書かせず、まず洗い出しだけ頼みます。

text
src/shipping.py の calc_shipping_fee を、これからリファクタします。
まだ何も変更せず、次を教えてください。
1. この関数が受け取る引数と戻り値
2. 分岐ごとの入力パターン(境界値を含む)
3. 時刻・乱数・外部I/Oへの依存があるか
4. 挙動が不自然に見える箇所(修正はしない)
既存のテストの置き場所と書き方も確認してください。

手順2:現状を記録するテストを書かせる

計画を確認したら、編集を許可してテストだけ作らせます。

text
calc_shipping_fee の現状確認テストを tests/ に追加してください。
- 目的は「正しさの検証」ではなく「現状の挙動の記録」です
- 期待値は推測せず、実際に関数を実行して得た値を使ってください
- 不自然な挙動も、現状のまま期待値にし、コメントで「要確認」と書いてください
- src/ 以下のコードは変更しないでください
- 既存のテストと同じフレームワークと命名にしてください
書いたらテストを実行し、結果を見せてください。

「src/ は変更しない」という一文は重要です。テストを通すために本体のほうを書き換えてしまう事態を防げます。

手順3:不足を埋めさせる

text
今のテストで、分岐のうちカバーされていないものはありますか。
あれば同じ方針(実行して得た値を記録)で追加してください。

4. テストの形(説明用の例)

下は、実行して確認したものではなく、完成形のイメージを示す説明用の例です。

python
# tests/test_shipping_characterization.py(説明用の例)
import pytest
from src.shipping import calc_shipping_fee

# 現状の挙動を記録したもの。正しさの保証ではない。
@pytest.mark.parametrize("weight_kg, region, expected", [
    (0.5, "local", 500),
    (1.0, "local", 500),    # 境界:1.0kg は下の区分
    (1.01, "local", 800),
    (5.0, "remote", 2200),
    (0, "local", 500),      # 要確認:重さ0でも基本料金が出る
])
def test_current_behavior(weight_kg, region, expected):
    assert calc_shipping_fee(weight_kg, region) == expected

数値は架空です。注目してほしいのは、境界値と「おそらく不自然な値」が、判断を加えずにそのまま入っている点です。

5. 人が確認すること

Claudeが「テストが通りました」と報告しても、そのテストが役に立つかは別問題です。次の3点は自分で見てください。

1. 期待値がどこから来たかテストが「実行して得た値」で書かれているかを確認します。Claudeがコードを読んで計算した値だと、現状とずれている可能性があります。

2. テストを壊して、落ちるかを見る本体の判定を一時的に1か所変えて(たとえば < を <= に)、テストが失敗するかを確かめます。落ちなければ、そのテストは挙動を捉えていません。確認後は必ず元に戻します。

3. 変更を戻せる状態にしてから進めるテストが固まったら、いったんコミットしてからリファクタに入ります。Claude Codeのチェックポイントは、Claude Codeのファイル編集ツールによる変更を記録します。ファイルがBashコマンドだけで変更された場合は追跡されず、戻すにはGitなどのバージョン管理を使います(Checkpointing)。履歴の土台はGitに置いてください。

不自然な挙動が見つかったら

先ほどの「重さ0でも基本料金が出る」のような箇所は、すぐ直さず、現状として記録します。直す判断は、仕様を知っている人が行います。リファクタの途中で挙動を変えると、テストが落ちたときに「リファクタのミスか、意図した修正か」が区別できなくなるからです。修正する場合は、テストを先に更新するか、別のコミットに分けます。

進め方のまとめ

  1. 副作用の少ない小さな関数を1つ選ぶ
  2. 計画モードで入力パターンと不自然な箇所を洗い出す
  3. 「本体は変えず、実行して得た値を記録する」条件でテストを書かせる
  4. 本体を一時的に壊して、テストが落ちることを確かめる
  5. コミットしてから、リファクタを依頼する

この流れで、リファクタ後に run tests を頼めば、結果が現状と同じか違うかを判断できます。

出典・参考資料