AI エージェント(自分のパソコンのファイルを読み書きしながら開発を進める AI)は、コードを書いたあとで「実装しました。動作を確認しました」のように報告します。この報告は、間違っていることがあります。確かめたと言っていても、コードを読み返しただけで動かしていないことがあります。動かしていても、ふつうの場合だけを試して、変わった場合を試していないことがあります。

そこで、AI が書いたコードは自分でも確かめます。この記事では、次の3つを順に練習します。

  1. 自分で動かして、結果を見る
  2. 変わった所を読んで、頼んでいない変更が無いかを見る
  3. テスト(コードが決めたとおりに動くかを自動で調べる小さなプログラム)を頼んで、自分で走らせる

確かめる対象を用意する

送料を計算する小さな関数(決まった入力から決まった答えを返す、ひとまとまりのコード)を使います。次の仕様をエージェントに頼んで書いてもらった、という場面を考えます。

買い物の合計金額を受け取って、送料を返す関数 shippingFee を作ってください。
合計が 5000 円以上なら送料は無料(0 円)、5000 円未満なら 500 円です。

頼むたびに書き方は変わるので、ここでは、よくある間違いを1つ含んだコードを用意して使います。練習用のフォルダ(名前は何でもかまいません)を作って VS Code で開き、shipping.js というファイルに次を書いて保存してください。

function shippingFee(total) {
  if (total > 5000) {
    return 0;
  }
  return 500;
}

module.exports = { shippingFee };

最後の module.exports = { shippingFee }; は、このファイルの shippingFee をほかのファイルから使えるように外へ出す書き方です。あとで作るテストのファイルが、これを読み込みます。

1つ試して「動いた」と思う

ターミナル(文字でパソコンに命令を出す画面)で練習用のフォルダに入り、次を打ちます。

node -e "console.log(require('./shipping.js').shippingFee(6000))"
  • node は、JavaScript のコードをパソコンの上で動かす道具です。Node.js が入っていれば使えます
  • -e は、後ろに書いた文をその場で動かす指定です
  • require('./shipping.js') は、さっきのファイルを読み込みます。.shippingFee(6000) で、合計 6000 円のときの送料を計算します
  • console.log(...) は、結果を画面に出します

画面には 0 と出ます。6000 円なら送料無料なので仕様どおりです。3000 円も試すと 500 と出て、これも仕様どおりです。ここで「動いた、できた」と思ってやめるのが、最も見逃しやすい確かめ方です。

境界の値で動かす

仕様に「5000 円以上なら」と書いてあります。「以上」と「より大きい」は、5000 円ちょうどのときだけ答えが変わります。このように、答えが切り替わる所の値を境界の値と呼びます。

境界の値は、AI も人も間違えやすい所です。「以上」は >=、「より大きい」は > と書きますが、1文字の違いなので取り違えます。3000 円や 6000 円のように境界から離れた値では、どちらで書いても同じ答えになるので、間違いに気づけません。境界の値は、ちょうどとその前後を試します。

試す値 仕様から決まる答え
4999 500(5000 円未満)
5000 0(5000 円以上)
5001 0

数直線の上に 3000・4999・5000・5001・6000 が並び、5000 の所に境界の線が引かれている。3000 と 6000 は線から遠いので間違いに気づけず、4999・5000・5001 を試すと間違いが見つかる、と書いてある

5000 円ちょうどで動かします。

node -e "console.log(require('./shipping.js').shippingFee(5000))"

仕様では 0 になるはずですが、500 と出ます。コードは total > 5000(5000 より大きい)になっていて、5000 ちょうどは送料無料になりません。AI の報告が「仕様どおり実装しました」でも、ここで間違いが見つかります。

境界のほかに、0 や空っぽ(何も買っていない)、マイナスの数や数字ではない文字、極端に大きい数も試します。これらは仕様に答えが書かれていないことがあります。そのときは AI に黙って決めさせず、自分で答えを決めて頼みます。たとえば「合計がマイナスのときは、エラーにしてください」と伝えます。

変わった所を読む

直してもらったとき、機能を足してもらったときは、どこが変わったかを自分で読みます。エージェントの報告に書かれていない変更が入っていることがあるからです。

Git(ファイルの変更の履歴を残す道具)で戻れる地点を作っておくと、前と今の違いを見られます。練習用のフォルダでまだ Git を使っていないときは、次の3つを打ちます。

git init
git add .
git commit -m "AI が書いた状態"
  • git init は、このフォルダで Git を使い始めます
  • git add . は、フォルダの中の全部のファイルを、記録する対象に入れます
  • git commit -m "..." は、今の状態を、名前を付けて記録します。-m のあとの文字が名前です

境界の間違いを、エージェントに直してもらいます。エージェントの作業場所を練習用のフォルダにして、次のように頼みます。

shippingFee(5000) が 500 を返しています。仕様では 5000 円以上なら送料無料なので、0 を返すはずです。直してください。直したら、変えた所を教えてください。

直ったら、エージェントの報告を読む前に、次を打ちます。

git diff

git diff は、最後に記録した状態から今までに変わった所を出す命令です。今回はたとえば次のように出ます。エージェントによっては、ほかの行が変わることもあります。

diff --git a/shipping.js b/shipping.js
index eac1929..0dff393 100644
--- a/shipping.js
+++ b/shipping.js
@@ -1,5 +1,5 @@
 function shippingFee(total) {
-  if (total > 5000) {
+  if (total >= 5000) {
     return 0;
   }
   return 500;
  • - で始まる行は消された行、+ で始まる行は足された行です。何も付かない行は変わっていない前後の行です
  • @@ -1,5 +1,5 @@ は、変わった場所が1行目あたりであることを表します
  • この例では > が >= に変わっただけで、頼んだことと変わった所が1対1で対応しています

差分を見るときは、次の4つを確かめます。

見るところ 気を付ける理由
頼んでいないファイルが変わっていないか 頼んだこと以外の所が壊れる原因になる
消された行(-)が多くないか 直すついでに、動いていた機能まで消していることがある
テストが消えたり、条件がゆるくなったりしていないか テストが落ちるので、テストのほうを書き換えて通したことがある
見慣れない部品の追加(新しいファイルや外部のライブラリ)が無いか 頼んでいない道具が増えると、あとで管理できなくなる

意味が分からない行があるときは、エージェントに「この行は何をしていますか。なぜ必要ですか」と聞きます。説明を聞いても納得できなければ、その変更は取り消してかまいません。

テストを頼む

1回ずつ手で動かす確かめ方は、コードが長くなると限界が来ます。確かめたい値が増えますし、1か所を直したあとに前に動いていた所が壊れていないかも、毎回確かめたくなります。そこで、確かめたいことを小さなプログラムにして残します。これがテストです。「この入力ならこの答えになる」という組を並べておき、全部を一度に走らせて、合っているかをコンピュータに調べさせます。

Node.js には、テストを走らせる仕組みが入っています。shipping.test.js というファイルを作って、次を書いてください。

const test = require("node:test");
const assert = require("node:assert");
const { shippingFee } = require("./shipping.js");

test("3000円なら送料は500円", () => {
  assert.strictEqual(shippingFee(3000), 500);
});

test("6000円なら送料は無料", () => {
  assert.strictEqual(shippingFee(6000), 0);
});

test("ちょうど5000円なら送料は無料", () => {
  assert.strictEqual(shippingFee(5000), 0);
});
  • test("名前", () => { ... }) が、テスト1つです。名前は何を確かめるかを日本語で書きます。失敗したときに画面に出るので、読んで何が悪いか分かる名前にします
  • assert.strictEqual(実際の値, 期待する値) は、2つが同じかを調べます。違ったらそのテストは失敗になります
  • () => { ... } は関数の書き方の1つで、これから走らせる中身を渡しています

走らせます。

node --test shipping.test.js

--test は、後ろのファイルをテストとして走らせる指定です。まだ total > 5000 のコードだった場合は、次のように出ます。

✔ 3000円なら送料は500円 (2.8576ms)
✔ 6000円なら送料は無料 (0.5563ms)
✖ ちょうど5000円なら送料は無料 (4.4194ms)
ℹ tests 3
ℹ pass 2
ℹ fail 1

✖ failing tests:

test at shipping.test.js:13:1
✖ ちょうど5000円なら送料は無料 (4.4194ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  500 !== 0

✔ が通ったテスト、✖ が失敗したテストです。最後の 500 !== 0 は、実際の値が 500 で、期待した値は 0 だったという意味です。時間の数字や細かい行は、動かすたびに違います。直したコード(>=)で走らせ直すと、3つとも ✔ になり、fail 0 と出ます。

テストも間違えることがある

コードを書いたのと同じ AI にテストを書かせると、そのコードが返す答えを、そのまま期待する値として書いてしまうことがあります。たとえば total > 5000 のコードを見てからテストを書くと、「5000 円なら 500 円」という仕様に反するテストができて、全部通ってしまいます。

左はコードを見てから AI がテストを書く場合で、コードの間違いがテストにも写って、全部通る。右は仕様から自分で期待値を決める場合で、間違いがあるとテストが失敗する、と書いてある

防ぐ方法は、期待する値を自分で決めることです。仕様の文から、さきほどの境界の値の表を作り、その表をそのまま頼む文に書きます。

shipping.js の shippingFee のテストを、node:test で shipping.test.js に書いてください。
shipping.js の中身は見ないで、次の表だけを根拠にしてください。
- 3000 のとき 500
- 4999 のとき 500
- 5000 のとき 0
- 5001 のとき 0
- 6000 のとき 0
書いたら、node --test shipping.test.js を走らせて、結果を教えてください。

「中身は見ないで」と頼んでも、エージェントがファイルを開くことはあります。できあがったテストの期待する値を、自分の表と見比べます。

もう1つ、テストを書いてもらったら、失敗する所を一度は見ておきます。コードをわざと間違った状態(>)にして走らせ、✖ が出れば、そのテストは間違いを見つけられると分かります。通るところしか見ていないテストは、何も調べていないことがあります。

直してもらったあとは全部走らせ直す

コードを直してもらうと、直した所は動くようになっても、別の所が壊れることがあります。1か所を直してもらったあとは、前に通っていたテストを含めて、全部を走らせ直します。

node --test

ファイルの名前を付けずに node --test と打つと、フォルダの中の .test.js で終わるファイルを全部走らせます。失敗が出たときは、エージェントに次のように伝えます。

node --test を走らせたら、次の失敗が出ました。
(✖ の行と、その下の 500 !== 0 のような行を、そのまま貼る)
shipping.js を直してください。テストのほうは変えないでください。

最後の1文を書かないと、エージェントはテストのほうを書き換えて通すことがあります。テストの期待値は仕様から決めたものなので、変えるのはコードのほうです。直ったら、git diff でテストのファイルが変わっていないことも確かめます。

確かめられないこと

動かして、読んで、テストを走らせても、確かめきれないことが残ります。

  • 試していない値で間違うかもしれません。テストが通ったことは、試した値で合っていたという意味にとどまります
  • 仕様そのものが間違っていたり、足りなかったりすることがあります。「5000 円以上」という仕様がお店の本当のルールと合っているかは、テストでは分かりません
  • 見た目や使いやすさは、テストに書きにくいです。ブラウザで自分の目で見ます
  • 安全性(見られてはいけない情報が漏れないか、など)は、動かしてみただけでは分かりません

どこまで確かめれば十分かは、間違ったときの困り方で決めます。自分だけが使う小さなページなら、動かして見るだけで足ります。お金や個人情報を扱うなら、境界の値のテストを書き、差分を読み、ほかの人にも見てもらいます。

課題:会員割引の関数を確かめる

送料の仕様に、割引を足します。

shippingFee のほかに、商品の合計金額に会員割引をかける関数 discountedTotal(total, isMember) を shipping.js に足してください。
- 会員(isMember が true)のとき、合計から 10% 引く。1円未満は切り捨てる
- 会員でないときは、そのまま

進め方は次のとおりです。

  1. 練習用のフォルダで Git の戻れる地点を作る
  2. 上の頼む文をエージェントに送る
  3. 「できました」の報告を読む前に、自分で期待値の表を作る
  4. 表を頼む文に書いて、テストを書いてもらう。「discountedTotal の中身は見ないで」と添える
  5. node --test を走らせる。失敗があれば git diff でコードの変わった所を読み、直してもらう。テストは変えさせない
  6. 直したあと、前の shippingFee のテストも含めて全部走らせ直す

確かめ方は次の3つです。

  1. 自分で作った表の全部の行が、テストに入っている
  2. node --test の最後が fail 0 になる
  3. git diff で、shippingFee のコードとテストが、頼んでいないのに変わっていない

期待値の表の例

total isMember 期待する値 確かめること
1000 true 900 ふつうの割引
999 true 899 切り捨て(899.1 が 899 になる)
995 true 895 四捨五入との違い(895.5 を切り捨てて 895。四捨五入なら 896)
1 true 0 割引すると 1 円未満になる所(0.9 が 0 になる)
0 true 0 0 円
1000 false 1000 会員でないとき

間違えやすいのは切り捨ての行です。Math.floor(total * 0.9) と書けば 999 は 899 になります。四捨五入の Math.round で書かれていると、899.1 は 899 になるので 999 の行では気づけません。そこで、四捨五入だと答えが変わる 995 を表に入れます。この行があると、Math.round で書かれたコードを見つけられます。

参考にした資料