はじめに
AIにコードを書かせるとき、以前の私は「できるだけ詳しいプロンプトを書いた方がよい」と考えていました。
その結果、プロンプトはどんどん長くなりました。
このファイルを編集してください。
この関数を追加してください。
関数名は〇〇にしてください。
pandasを使ってください。
出力先はこのディレクトリにしてください。
既存コードには触れないでください。
空データにも対応してください。
エラー処理を追加してください。
最後にレポートを生成してください。
テストも追加してください。
さらに、実装が失敗するたびに注意事項を追記します。
ただし、既存の設定ファイルは変更しないでください。
CSVの列順を変えないでください。
一時ファイルを残さないでください。
既存のCLIと同じ形式にしてください。
実行していない処理を完了したと報告しないでください。
気が付けば、1回の依頼に100行近いプロンプトを書いていました。
しかし、そこまで細かく書いても、AIコーディングは安定しませんでした。
- 一部の条件を読み落とす
- 既存コードと似た処理を新しく作る
- 指定したファイルは作るが、実際の処理から呼ばれていない
- 正常系では動くが、空データで落ちる
- 新しい機能は動くが、既存機能を壊す
- 「テストしました」と報告するが、一部のテストしか実行していない
そこで、実装方法を細かく指定するのをやめました。
代わりに、AIへ「満たすべきテスト」を渡すようにしました。
例えば、100行の実装指示を書く代わりに、次のような10個の条件を渡します。
- 正常な入力で期待する結果になる
- 入力が0件でも異常終了しない
- 必須列が不足していれば明示的に失敗する
- 不正な値を受け入れない
- 未承認の項目があれば後続処理を止める
- 元データを書き換えない
- CSVとMarkdownの内容が一致する
- レポートに判定理由が記録される
- 新しく追加したテストが通る
- 既存の全テストも通る
この形へ変えてから、AIが自分で実装と修正のループを回しやすくなりました。
もちろん、必ずテストを10個書けばよいという話ではありません。
ここで言いたいのは、100行の自然言語で実装方法を固定するより、少数の検証可能な振る舞いを渡した方が安定したということです。
この記事では、私がAIへコードを書かせるときに使っている、テスト中心の指示方法を紹介します。
100行のプロンプトを書いても安定しなかった
以前は、AIが間違えるたびにプロンプトを詳しくしていました。
例えば、複数のCSVを読み込み、集計結果を出力する機能を追加するとします。
以前なら、次のように依頼していました。
scripts/aggregate_results.pyを作成してください。
resultsディレクトリ以下のCSVファイルをすべて読み込んでください。
status列がsuccessの行だけを抽出してください。
categoryごとに件数とvalue列の平均値を計算してください。
出力先はproducts/tables/summary.csvです。
pandasを使ってください。
処理は次の4関数に分割してください。
- load_results
- filter_success
- aggregate_results
- save_summary
main関数を作成してください。
if __name__ == "__main__":から実行してください。
CSVファイルが存在しない場合はエラー終了してください。
CSVが空の場合はヘッダーだけ出力してください。
既存コードには変更を加えないでください。
pytestのテストも追加してください。
一見すると、かなり明確です。
しかし、この書き方には問題がありました。
人間が実装方法を決めすぎていた
このプロンプトでは、次の内容を人間側が決めています。
- 使用するライブラリ
- ファイル名
- 関数名
- 関数の分割方法
- CLIの構成
- エラー処理の方法
ところが、実際のリポジトリには、すでに共通のCSV読み込み関数があるかもしれません。
既存のCLIフレームワークがあるかもしれません。
集計処理をクラスとして実装する設計になっているかもしれません。
人間が実装方法を細かく指定すると、AIはリポジトリを理解するよりも、プロンプトを再現することを優先します。
その結果、既存コードと似た処理が増えます。
プロンプトがすぐに古くなった
開発を進めると、ディレクトリ構成やデータ形式は変わります。
例えば、出力先が次のように変わったとします。
products/tables/summary.csv
から、
products/tables/step7_summary.csv
へ変更されたとします。
長いプロンプトの一部に古い出力先が残っていると、AIは古い情報と現在のコードのどちらを優先すべきか迷います。
細かい指示が多いほど、現在のリポジトリと矛盾する可能性も増えます。
「コードを書いた」と「機能が完成した」を混同していた
AIは、指定したファイルや関数を作ることには成功します。
しかし、それだけでは機能が完成したとは言えません。
- 新しい関数が実際の処理から呼ばれていない
- 実データの列名と異なる
- 空データで例外が発生する
- 不正値を正常値として処理する
- 出力ファイルは存在するが中身が間違っている
- 既存の処理を壊している
コードの存在ではなく、期待する振る舞いが実現されているかを確認する必要があります。
実装手順ではなく「通るべきテスト」を渡す
現在は、関数名や処理手順を細かく指定する代わりに、完了条件を振る舞いとして渡しています。
例えば、次のように依頼します。
前段の処理で生成された承認状態のCSVを読み込み、
後続処理へ進める状態か判定する機能を追加してください。
少なくとも、次の振る舞いをテストしてください。
1. すべて承認済みならreadyになる
2. 未承認の項目が1件でもあればblockedになる
3. 入力が0件ならemptyとして処理する
4. 必須列が不足していれば明示的に失敗する
5. authorized列に不正な値があれば失敗する
6. 入力CSVを書き換えない
7. 判定結果をCSVとして出力する
8. 同じ内容をMarkdown表として出力する
9. 判定理由をレポートへ記録する
10. 既存テストを含む全pytestが通る
既存コードを確認し、現在の設計に合う方法で実装してください。
ここでは、次の内容を指定していません。
- 関数名
- クラス構成
- 内部処理の分割
- pandasを使うかどうか
- 共通関数をどこまで利用するか
AIは既存コードを確認し、テストを満たす実装方法を選べます。
人間が決めるのは「どう書くか」ではなく、「どう動けば正しいか」です。
テストはAIにとってのセンサーになる
人間はコードを読んだときに、「この部分は怪しそうだ」と経験的に判断できます。
一方で、AIは生成したコードが実際の環境で動くかを、コード生成だけでは確認できません。
実行すると、さまざまな問題が見つかります。
- import先が存在しない
- fixtureの形式が想定と異なる
- YAMLのキー名が違う
- CSVの型推論によって真偽値が文字列になる
- 相対パスの基準が違う
- 空のDataFrameだけ別の動作になる
- 既存関数の戻り値が想定と違う
自動テストがあれば、AIは次のループを回せます。
- 既存コードを読む
- 実装する
- テストを実行する
- 失敗内容を読む
- 原因を調べる
- 実装を修正する
- 再びテストする
AIにとって、自動テストは単なる品質保証ではありません。
自分の実装と実際の環境のズレを検出するセンサーです。
長いプロンプトでは、人間が事前にすべての問題を予測する必要があります。
自動テストがあれば、予測できなかった問題も実行結果から発見できます。
文章ではなく、実行可能な完了条件にする
次のような指示は、一見すると正しそうです。
正しく実装してください。
既存機能を壊さないようにしてください。
エラー処理も適切に追加してください。
しかし、これらは実装後に達成できたかを判定できません。
「正しい」「壊していない」「適切」の意味が曖昧だからです。
そこで、できるだけテスト可能な条件へ変換します。
曖昧な条件
空データにも適切に対応してください。
テスト可能な条件
入力が0件の場合、例外を発生させず、
statusがemptyの結果を返してください。
曖昧な条件
既存データを壊さないでください。
テスト可能な条件
処理の実行前後で、入力CSVのバイト列が変化しないことを確認してください。
曖昧な条件
分かりやすいレポートを生成してください。
テスト可能な条件
レポートには、少なくとも次を含めてください。
- 最終判定
- 対象件数
- 正常件数
- 異常件数
- 判定理由
- 残っている手動作業
自然言語を完全になくす必要はありません。
重要なのは、曖昧な表現を、実行後に確認できる条件へ変換することです。
最初に書く10個のテスト
実際には、機能ごとに必要なテストは異なります。
それでも、多くの処理で共通して確認できる観点があります。
私が最初に考えるのは、次の10項目です。
1. 正常系
期待する入力を渡したとき、期待する結果になるかを確認します。
def test_all_authorized_rows_are_ready():
rows = [
{"target": "A", "authorized": True},
{"target": "B", "authorized": True},
]
result = evaluate_authorization(rows)
assert result.status == "ready"
assert result.total_count == 2
assert result.unauthorized_count == 0
2. 一部だけ異常な場合
すべてが正常なケースだけでは、判定条件の境界を確認できません。
def test_one_unauthorized_row_blocks_next_step():
rows = [
{"target": "A", "authorized": True},
{"target": "B", "authorized": False},
]
result = evaluate_authorization(rows)
assert result.status == "blocked"
assert result.unauthorized_count == 1
3. 空入力
0件の入力は、実際のデータ処理で頻繁に発生します。
def test_empty_input_returns_empty_status():
result = evaluate_authorization([])
assert result.status == "empty"
assert result.total_count == 0
空入力を正常な特殊ケースとするか、エラーとするかは機能によって異なります。
重要なのは、どちらにするかを明示することです。
4. 必須項目不足
入力形式が不完全な場合に、処理を続けないことを確認します。
import pytest
def test_missing_required_column_raises_error():
rows = [{"target": "A"}]
with pytest.raises(ValueError, match="authorized"):
evaluate_authorization(rows)
5. 不正値
真偽値を想定している列に、文字列や数値が入る可能性があります。
def test_invalid_authorized_value_raises_error():
rows = [
{"target": "A", "authorized": "unknown"},
]
with pytest.raises(ValueError, match="invalid authorized value"):
evaluate_authorization(rows)
6. 境界値
件数、文字列長、時刻、数値範囲などの境界を確認します。
例えば、対象件数に上限があるなら、上限ちょうどと上限を超えた場合を分けてテストします。
7. 入力を変更しないこと
読み取り専用であるべきファイルが変更されていないかを確認します。
def test_source_file_is_not_modified(tmp_path):
source = tmp_path / "source.csv"
source.write_text(
"target,authorized\nA,true\n",
encoding="utf-8",
)
before = source.read_bytes()
run_validation(source)
after = source.read_bytes()
assert after == before
8. 出力ファイルの中身
ファイルが存在するだけでは不十分です。
def test_output_contains_expected_rows(tmp_path):
output = run_validation_and_write_output(tmp_path)
assert output.exists()
text = output.read_text(encoding="utf-8")
assert "ready" in text
assert "A" in text
可能であれば、文字列の部分一致ではなく、CSVを読み直して期待するデータ構造と比較します。
9. 禁止した処理を実行しないこと
検証だけを行う機能が、後続処理まで実行していないかを確認します。
def test_validation_does_not_execute_next_step(mocker):
execute = mocker.patch("project.next_step.execute")
run_validation()
execute.assert_not_called()
10. 既存機能を壊していないこと
追加した機能のテストだけでなく、リポジトリ全体のテストを実行します。
pytest -q
この10項目を毎回そのまま使うわけではありません。
対象機能に合わせて、必要なものを選びます。
重要なのは、正常系を1件だけ確認して「テスト済み」としないことです。
focused testと全体テストを分ける
私は、対象機能だけのテストと、リポジトリ全体のテストを分けています。
まず、新しく追加した機能のテストを実行します。
pytest tests/test_step7_validation.py -q
これをfocused testと呼んでいます。
focused testでは、今回追加した機能が完了条件を満たしているかを確認します。
次に、全体テストを実行します。
pytest -q
こちらでは、既存機能を壊していないかを確認します。
新しいテストだけ通っていても、共通関数の変更によって既存処理が壊れている可能性があります。
一方で、全体テストだけでは、今回追加した境界条件が十分に検証されているか分かりません。
そのため、AIの最終報告でも結果を分けさせます。
Validation:
- Focused tests: 12 passed
- Full pytest: 428 passed
単に「すべてのテストが通りました」と報告させるより、何を実行したかが分かりやすくなります。
先にテストだけを書かせる
仕様が明確な場合は、本体実装より先にテストを書かせる方法も有効です。
AIには次のように依頼します。
まず、完了条件をpytestとして実装してください。
この段階では本体コードを変更しないでください。
追加したテストが、機能未実装を理由に失敗することを確認してください。
その後、追加したテストを変更せずに本体を実装してください。
作業の流れは次のようになります。
- 既存コードを確認する
- 完了条件をテストへ変換する
- テストが失敗することを確認する
- 本体を実装する
- focused testを通す
- 全体テストを通す
この方法には、AIが自分の実装に合わせてテストを緩めるのを防ぐ効果があります。
例えば、本来は不正値を拒否する仕様だったのに、実装が難しいため、不正値を受け入れるようテストを書き換えてしまうことがあります。
テストを先に固定すると、実装側をテストへ合わせる流れを作れます。
テストだけでは足りない
ここまでテストの重要性を書いてきましたが、AIにテストだけを渡せば十分というわけではありません。
テストで表現しにくい情報もあります。
例えば、次のような情報です。
- この機能がプロジェクト全体のどこに位置するか
- 今回はどこまで実装してよいか
- どの処理は人間の承認が必要か
- 外部ツールを実行してよいか
- どのファイルを読み取り専用として扱うか
- 既存のどの実装を参考にするか
そこで、私はテストに加えて「認識」と「制約」を渡しています。
ただし、主役はあくまでテストです。
認識と制約は、AIが誤った範囲でテストを満たそうとするのを防ぐガードレールとして使います。
認識:AIに現在地を渡す
認識では、プロジェクトの状態と今回の作業の位置付けを伝えます。
このプロジェクトは、入力データから中間生成物、集計表、
図、最終レポートを順番に生成する解析パイプラインです。
処理はstep単位に分かれています。
- scripts/: 実行スクリプト
- src/: 再利用可能な処理
- config/: YAML設定
- products/tables/: CSVとMarkdown表
- products/figures/: 図
- products/reports/: レポート
- tests/: pytest
今回追加する機能は、前段で生成された承認状態を検証し、
後続処理へ進めるかを判定するstepです。
既存stepの命名、CLI、出力形式、テスト構成を確認し、
現在の設計に合わせて実装してください。
重要なのは、単に「READMEを読んでください」と書かないことです。
READMEにはプロジェクト全体の説明はあっても、現在の作業状態までは書かれていないことがあります。
そのため、現在地も渡します。
現在の状態:
- 前段stepは実装済み
- 入力CSVは生成済み
- 自動承認機能は未実装
- 今回も自動承認は実装しない
- 後続処理には外部ツールが必要
- 今回は実行計画の生成までを対象とする
これにより、AIが「せっかくなので後続処理まで実装しました」と作業範囲を広げるのを防げます。
制約:テストを通すための抜け道を塞ぐ
AIは、与えられたゴールを達成しようとします。
そのため、制約がないと、想定外の方法でテストを通す可能性があります。
例えば、テストが失敗したときに、次のような変更をするかもしれません。
- 既存テストを削除する
- テストのassertを弱くする
- 入力データを期待値に合わせて書き換える
- 設定値を変更する
- 未承認の項目を自動承認する
- 実行していない処理の結果を固定値で生成する
そこで、禁止事項を具体的に渡します。
今回の作業では、検証と実行計画の生成だけを行います。
禁止事項:
- 元データの変更
- 後続処理の実行
- 外部ツールによるファイル編集
- 未承認項目の自動承認
- 架空の実行結果の生成
- 既存テストの削除
- assertの削除によるテストの弱体化
- テストを通すための設定値変更
「安全に実装してください」ではなく、禁止する操作を具体的に書くのがポイントです。
抽象的な制約
既存データを壊さないでください。
具体的な制約
products/raw以下のファイルは読み取り専用として扱ってください。
削除、名前変更、内容変更を行ってはいけません。
抽象的な制約
既存機能に影響を与えないでください。
具体的な制約
既存の公開関数について、引数、戻り値、例外の種類を変更しないでください。
変更後は既存テストを含む全pytestを実行してください。
可能な制約は、テストとしても追加します。
私が現在使っている依頼テンプレート
現在は、次のような形式でAIへ依頼しています。
# 依頼
前段の処理で生成された承認状態のCSVを読み込み、
後続処理へ進める状態か判定する機能を追加してください。
# 認識
このプロジェクトはstep単位のデータ解析パイプラインです。
前段stepでは、対象ごとの承認状態をまとめたCSVが生成されています。
今回のstepは、そのCSVを読み込み、後続処理を実行可能か判定します。
既存stepの構成、命名、CLI、表生成、レポート生成を確認し、
既存の設計パターンに合わせてください。
# 制約
このstepでは、検証と実行計画の生成だけを行います。
次の操作は禁止です。
- 元データの変更
- 後続処理の実行
- 外部ツールによるファイル編集
- 未承認対象の自動承認
- 架空の実行結果の生成
- 既存テストの削除または弱体化
# 自動テスト
少なくとも次の振る舞いをテストしてください。
1. すべて承認済みならreadyになる
2. 未承認対象が1件でもあればblockedになる
3. 入力が0件ならemptyになる
4. 必須列不足を検出する
5. 不正な値を拒否する
6. 入力ファイルを書き換えない
7. CSVとMarkdown表の内容が一致する
8. レポートに判定理由が含まれる
9. 後続処理を実行しない
10. 既存テストを含む全pytestが通る
# 作業方法
1. 既存コードとテストを確認する
2. 関連する既存実装を探す
3. 完了条件をテストへ変換する
4. 必要最小限の変更で実装する
5. focused testを実行する
6. 全体テストを実行する
7. git diffを確認する
8. 結果を報告する
# 完了報告
次を報告してください。
- 現在の判定結果
- 変更したファイル
- 生成したファイル
- focused testの実行結果
- 全体テストの実行結果
- 実行していない操作
- 残っている手動作業
以前のプロンプトと比べると、関数名や実装手順はほとんど指定していません。
その代わり、期待する振る舞いと禁止事項を明確にしています。
完了報告にもテスト結果を含める
AIによる開発では、最終報告の形式も決めています。
以前は、次のような報告を受けることがありました。
実装が完了しました。
テストもすべて通っています。
しかし、これだけでは次の点が分かりません。
- 何のテストを実行したのか
- 新しいテストだけなのか
- 全体テストも実行したのか
- 何ファイル変更したのか
- 禁止した処理を実行していないか
- 手動作業が残っていないか
そこで、報告項目を固定します。
Current state:
- Classification: ready_for_manual_review
- Target rows: 9
- Invalid rows: 0
Changed:
- src/project/validation.py
- scripts/step7_validate.py
- tests/test_step7_validation.py
Generated:
- products/tables/step7_status.csv
- products/tables/step7_status.md
- products/reports/step7_report.md
Validation:
- Focused tests: 10 passed
- Full pytest: 428 passed
Boundary preserved:
- No source files modified
- No external commands executed
- No next-step processing performed
- No authorization state changed
Remaining manual work:
- Review and approve the generated authorization table
テスト結果だけでなく、「実行していないこと」も報告させています。
これにより、AI自身が最後に制約を再確認する流れを作れます。
テスト後にgit diffを確認する
テストが通っても、変更内容が適切とは限りません。
例えば、次のような状態でもテストが通る可能性があります。
- 関係ないファイルまで変更している
- 一時ファイルが追加されている
- 入力データが書き換えられている
- 設定値を都合よく変更している
- 大量の自動生成物が追加されている
- テストを削除している
そこで、テスト後にGitの差分を確認させます。
git status --short
git diff --stat
git diff
AIには次のように指示します。
テスト通過後にgit diffを確認してください。
次がないことを確認してから完了報告してください。
- 意図していない変更
- 一時ファイル
- 入力データの変更
- テストの削除または弱体化
- 作業範囲外の設定変更
自動テストは振る舞いを確認します。
Git差分は変更範囲を確認します。
両方を組み合わせると、AIによる予想外の変更を見つけやすくなります。
うまくいかなかったテストの書き方
テストを渡せば必ず安定するわけではありません。
テスト自体が悪いと、AIは間違ったゴールへ向かいます。
ファイルの存在だけを確認する
次のテストでは、空のファイルでも通ります。
def test_output_is_created(tmp_path):
output = run(tmp_path)
assert output.exists()
重要なのは、ファイルの中身です。
def test_output_contains_expected_result(tmp_path):
output = run(tmp_path)
assert output.exists()
rows = read_output(output)
assert rows == [
{
"target": "A",
"status": "ready",
}
]
実装詳細を固定する
次のテストは、内部関数の呼び出し回数を固定しています。
def test_internal_function_called_three_times(mocker):
process = mocker.patch("project.internal._process_row")
run()
assert process.call_count == 3
仕様上重要なのが「3件処理されること」なら、外部から見える結果を確認します。
def test_three_rows_are_processed():
result = run_with_rows(make_rows(3))
assert result.processed_count == 3
内部実装を固定しすぎると、AIがより適切な実装方法を選べなくなります。
正常系しかない
正常な入力だけでテストすると、実データで発生する例外に対応できません。
最低でも、次を検討します。
- 空入力
- 必須項目不足
- 不正値
- 重複
- 境界値
- ファイルなし
- 出力先なし
- 型の揺れ
すべてを毎回テストする必要はありません。
対象機能で実際に起こりそうな失敗を選びます。
AIにテストの変更を自由に許す
実装後にテストが失敗したとき、AIがテスト側を書き換えて通すことがあります。
そのため、重要なテストについては次のように指定します。
追加したテストが仕様と一致していることを確認した後は、
本体実装をテストへ合わせてください。
テストを変更する必要がある場合は、
変更理由を明示してください。
自動テストが難しい作業ではどうするか
すべての作業を完全に自動テストできるわけではありません。
例えば、次のような項目です。
- 図が見やすいか
- レポートが読みやすいか
- GUIが使いやすいか
- 解析結果が科学的に妥当か
- 外部ソフトウェアが正しく操作されたか
それでも、機械的に確認できる部分はあります。
図の場合
- ファイルが生成される
- 画像サイズが一定以上ある
- 軸ラベルが存在する
- データ点数が正しい
- NaNだけの図になっていない
- 必要な条件ごとの図が生成される
レポートの場合
- 必須見出しが存在する
- 判定結果が記載されている
- 表と本文の件数が一致する
- 出力ファイルへの参照が存在する
- 未実行の処理を「実行済み」と書いていない
外部ツールの場合
- 実行コマンドの生成までをテストする
- 外部ツールの呼び出しをモックする
- dry-runモードを用意する
- 実行ログの形式をテストする
- 実際の実行を人間の承認後に分離する
完全に自動化できない作業でも、検証可能な境界まではテストできます。
一度に大きな作業を渡さない
テストがあっても、作業範囲が大きすぎると不安定になります。
例えば、次の依頼です。
解析パイプライン全体を完成させてください。
これでは、AIが途中で行う判断が多すぎます。
代わりに、検証可能な単位へ分割します。
- 入力データの検査
- 中間表の生成
- 判定ロジックの実装
- レポートの生成
- 後続処理用の計画生成
- 外部ツールによる実処理
それぞれに、入力、出力、制約、テストを持たせます。
AIにとって扱いやすい単位は、人間にとってレビューしやすい単位でもあります。
人間が決めること、AIに任せること
テスト中心に切り替えてから、人間とAIの役割分担も整理されました。
人間が決めること
- 何のために機能を作るか
- どの状態を成功とするか
- どこまで自動化してよいか
- どこから人間の判断が必要か
- 失敗した場合に何が起きるか
- 絶対に実行してはいけない操作は何か
AIに任せること
- 既存コードに合ったファイル配置
- 関数やクラスの分割
- 共通処理の再利用
- CLIの実装
- 例外処理の具体化
- テストコードの実装
- リファクタリング
- ドキュメントの更新
人間が疑似コードを書くところまで設計すると、AIはそれをコードへ変換するだけになります。
一方で、人間が目的、正しさ、境界を決めれば、AIは既存コードを読んで実装方法を選べます。
まとめ
以前の私は、AIが間違えるたびにプロンプトを長くしていました。
しかし、100行のプロンプトを書いても、次の問題は残りました。
- 条件の読み落とし
- 既存設計との衝突
- 正常系だけ動く実装
- 既存機能の破壊
- 不十分なテスト
- 作業範囲の拡大
そこで、実装方法を細かく指示する代わりに、満たすべきテストを渡すようにしました。
特に効果があったのは、次の3点です。
- 正常系だけでなく、空入力、不正値、境界条件もテストする
- focused testと全体テストを分ける
- 禁止事項も可能な限りテストする
さらに、テストを正しい範囲で機能させるために、プロジェクトの「認識」と作業上の「制約」を渡します。
私が現在AIへ渡しているのは、細かな実装手順ではありません。
- 認識:このプロジェクトが何で、現在どこにいるか
- 制約:何をしてよく、何をしてはいけないか
- 自動テスト:どう動けば正しいのか
AIにとって、自動テストは最後に実行する確認作業ではありません。
自分の実装と実際の環境のズレを検出し、修正ループを回すためのセンサーです。
100行の自然言語で「正しい実装方法」を説明するより、10個のテストで「正しい振る舞い」を定義する。
私の環境では、その方がAIコーディングは安定しました。