CHAPTER 9 ・ AIと一緒に、中級者として / Lesson 35
AIにテストを書いてもらう
関数の仕様を伝えて、テストを作ってもらう。足りない場面を見抜く目と、AIの答えの検算法です。
このレッスンが終わったら
「AIに書かせたテストも、期待値が正しいかを自分の目で確かめる。境界の値と、変な入力が入っているかを見る」と言える。
こんな場面、ありませんか?
請求書の税込金額を求める関数を作りました。Lesson 28 で学んだテストを書きたいのですが、テストケース(入力と、期待する結果の組)を考える手間も、書く手間もかかります。そこで、AIに頼むことにしました。
ところが、AIは、渡されたコードを読んで、「このコードがこう動くなら、結果はこうなるはず」と、テストを書くことがあります。税抜金額と税率(%)から税込金額を返す関数と、AIが書いたテストの表を見てください。
▶ 実行を押すと、ここに結果が出ます
全部合格です。安心してよいでしょうか。実は、サクラ商事のルールは「1円未満は切り捨て」です。999円の10%税込は、999 x 1.1 = 1098.9 なので、1098円が正しい答えです。関数は四捨五入(round)で1099円を返し、テストも1099円を期待していました。間違った関数と、間違った期待値が、お互いに合っていたので、合格してしまったのです。
この回では、AIにテストを書いてもらう方法と、そのテストを信じてよいか確かめる方法を学びます。
仕様を伝えて、テストを書いてもらう
原因の1つは、AIに「コード」を見せたことです。コードの動きに合わせた期待値が書かれてしまいます。そこで、コードではなく、仕様(その関数が何をすべきかの約束)を伝えます。
テストを頼むときの文章(例)
次の関数の仕様から、unittest のテストを書いてください。 【関数】price_with_tax(price, rate) 【目的】税抜金額から、税込金額を求める(請求書用) 【入力】price: 税抜金額(整数・円)、rate: 税率(% の整数。8 か 10 だけ) 【出力】税込金額(整数・円)。1円未満の端数は、切り捨てる 【例外】price がマイナスなら ValueError。rate が 8 と 10 以外なら ValueError 【お願い】 - 普通の値のほかに、境界の値(0円・1円・端数が出る金額・出ない金額)も入れる - 例外のテストも入れる - 期待値は、計算の根拠をコメントに書く(例:999 x 1.1 = 1098.9 → 切り捨てて 1098) - 関数の中身は渡しません。仕様だけを見て書く
コードも渡したいときは、「仕様とコードが食い違っていたら、教えてください」と添えます。次が、AIの返答の例です。仕様どおりの関数と一緒に動かします。
▶ 実行を押すと、ここに結果が出ます
6件のテストが、すべて通りました。最後の3行は、Lesson 28 で学んだ、ブラウザでテストを動かす決まり文句に、結果を日本語で出す工夫を足したものです。
AIのテストを、読む
テストを読むときは、「入力」と「期待値」の組を探します。self.assertEqual(実際の結果, 期待値) の形が基本です。price_with_tax(999, 10) が実際の結果、1098 が期待値で、この期待値が正しいかどうかが勝負になります。
with self.assertRaises(ValueError):この中で、ValueError が出ることを期待する price_with_tax(-1, 10)マイナスの金額で、関数を呼ぶ▶ 実行を押すと、ここに結果が出ます
AssertionError: 1099 != 1098 は、「左が1099、右が1098で、等しくない」という意味です。さて、失敗したとき、間違っているのは関数でしょうか、それとも期待値でしょうか。 AIが書いた期待値も、間違うことがあります。次の流れで判断します。
- ① 失敗を読む期待値と実際の値
- ② 自分で検算別の方法で計算
- ③ どちらが違う?関数か、期待値か
- ④ 直して再実行全部のテストを
次のコードで、検算を体験します。AIが書いた期待値の表を、別の方法(Lesson 04 の Decimal)で計算した答えと見比べます。
▶ 実行を押すと、ここに結果が出ます
3行目が失敗です。関数の答えは1078で、検算も1078。つまり、間違っているのは期待値(1079)です。999 x 1.08 = 1078.92 を、AIが四捨五入してしまいました。ここで関数のほうを1079になるよう直すと、本物の間違いを作ってしまいます。
テストの「強さ」を試す
テストが通っても、そのテストが弱ければ、安心はできません。確かめる方法は、関数をわざと間違った形に変えて、テストが気づくかを見ることです。間違いを見つけられないテストは、足りない部分があります。
▶ 実行を押すと、ここに結果が出ます
round_all(四捨五入)は、見つかりました。ところが、round_only_8(8%のときだけ四捨五入)は、すり抜けて合格しています。表に、「8%で端数が出る」テストがないためです。足りないテストは、こうして見つけます。
見落としやすい境界値(ぎりぎりの値)は、次のとおりです。
| 見る場所 | 例 |
|---|---|
| 0、最小の値、端数の境目 | 0円、1円、90円(99円ちょうど)と91円(100.1円) |
| 税率や区分ごと | 10% と 8% のそれぞれで、端数が出る値 |
| 変な入力 | マイナス、仕様にない税率 |
最後の「変な入力」は、例外のテストにします。仕様どおりの関数で、動かしてみましょう。
▶ 実行を押すと、ここに結果が出ます
自分で書く
お題① 次の mutant は、8%のときだけ四捨五入する、間違った関数です。いまの表では、この間違いに気づけません。表に1件足して、「見つけた」と表示されるようにしてください。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
8%で、端数が出て、切り上がる金額を足します。たとえば (999, 8, 期待値) です。期待値は、999 x 1.08 = 1078.92 を切り捨てた値にします。
答えの例を見る
CASES = [(1000, 10, 1100), (1500, 8, 1620), (999, 8, 1078)]
def mutant(price, rate):
if rate == 8:
return round(price * 108 / 100)
return price * (100 + rate) // 100
caught = any(mutant(p, r) != e for p, r, e in CASES)
print("見つけた" if caught else "すり抜けた")お題② 次は、AIが書いた期待値の表です。関数は仕様どおり(切り捨て)で、期待値が2件、間違っています。検算して、表を直してください。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
画面に出た、合わないものを、電卓かExcelで検算します。333 x 1.1 = 366.3、19 x 1.08 = 20.52 です。切り捨てた値に、表を書き換えます。
答えの例を見る
def price_with_tax(price, rate):
return price * (100 + rate) // 100
AI_CASES = [(250, 8, 270), (333, 10, 366), (19, 8, 20), (5000, 10, 5500), (7, 10, 7), (80, 8, 86)]
bad = [(p, r, e) for p, r, e in AI_CASES if price_with_tax(p, r) != e]
print("全件合格" if not bad else f"期待値が合わないもの: {bad}")仕事ならどう使う?
理解チェックAIが書いたテストが、全部合格しました。次にすることは、どれでしょう?
理解チェック仕様は『1円未満は切り捨て』です。テスト『999円・税率8%』の期待値は1079、関数の結果は1078でした。どうしますか?