AIが生成したJavaScriptを自分で検証する|仕様・境界値・テストの実践手順
執筆・監修: 徐 聖博/株式会社シンシア 代表取締役社長
AIが出したコードを信じる前に、期待する動作を表にし、境界値・型・空配列をテストします。Nodeの標準機能だけで動く16件のテストと実行結果、わざと作った誤実装が「普通の値」では合格に見えてしまう例、テストが通っても確認が必要な範囲を解説します。
目次
AIが出したコードは「動いた」では合格にしない。期待値を先に書いて、手元で確かめる
AIが出したコードは、実行できただけでは正しいと判断できません。AIに「このコードは正しいですか」と聞き直しても、返ってくるのはAI自身の評価です。「何を受け取り、何を返すべきか」を人が先に決め、その期待値と実行結果を比べて、仕様に合う証拠を手元に残します。
ここでは「点数の配列から合格点だけを取り出す」という小さな関数を使います。特定のAIが間違えたという実験ではありません。説明のために意図して作った誤実装と修正例を使う教材です。AIのアカウント、APIキー、受講者の個人データは使いません。必要なのはNode.jsだけです。
先に結果を書いておきます。仕様から作った16件のテストを、修正例に当てると16件すべて成功しました。誤実装に当てると、比較用の1件を除く15件のうち11件が失敗しました。ただし残りの4件は、誤実装でも成功します。「普通の値」「空の配列」だけを試していたら、この誤実装は合格に見えていたということです。
1. コードを読む前に、期待する動作を固定する
「80点以上を取り出して」だけでは、文字列の「90」や101点をどう扱うかが決まっていません。まず次の仕様を置きます。これは教材用の設計判断であり、点数を扱うすべてのシステムに共通する規則ではありません。
| 確認項目 | この教材の仕様 |
|---|---|
| 入力全体 | 配列。配列以外はTypeErrorにする |
| 有効な点数 | 0〜100の有限な数値。小数は許可する |
| 合格点 | 指定したしきい値以上。既定は80 |
| しきい値 | 0〜100の有限な数値。範囲外や文字列はRangeErrorにする |
| 不正な配列要素 | 文字列、null、NaN、無限大、範囲外の値は結果から除外する |
| 戻り値 | 元の順序と重複を保った別の配列 |
| 元データ | 入力配列を変更しない |
業務によっては、不正な要素を黙って除くのではなく、取り込み全体をエラーにすべき場合もあります。その場合はコードだけでなく、この仕様表とテストも変えます。先に選択を言葉にすることが大切です。
次に、コードを見ずに期待する結果を書きます。
| 入力 | 期待する結果 | 確認すること |
|---|---|---|
[79, 80, 81] |
[80, 81] |
境界の80を含む |
["90", 90] |
[90] |
文字列を勝手に数値へ変えない |
[-1, 0, 100, 101] |
[100] |
範囲外の点数を除く |
[] |
[] |
空でも処理できる |
[100, 80, 100] |
[100, 80, 100] |
順序と重複を維持する |
条件式の読み方が不安なときは、if文と条件の作り方に戻ってから進めてください。
2. 「動くけれど間違う」例を確かめる
次は教材用の誤実装です。外部のAIが実際に出したコードではありません。
export const selectPassingScores = (scores, threshold = 80) =>
scores.filter((score) => score > threshold);
[79, 80, 81] を渡すと [81] になり、80点が落ちます。仕様は「以上」なのに、条件は「より大きい」だからです。さらに ['90', 90] を渡すと、意図していない文字列の値も通ります。JavaScriptは '90' > 80 を比べるときに文字列を数値へ変えるためです。
例外が発生しなくても、期待値と違えば不合格です。エラー表示がある場合の調べ方はJavaScriptのエラーの読み方で確認できますが、エラーが出ないミスは仕様と照合して探します。
3. 修正例を仕様と対応させる
次のコードを ai-code-review.mjs という名前で保存します。
export function selectPassingScores(scores, threshold = 80) {
if (!Array.isArray(scores)) {
throw new TypeError('scores must be an array');
}
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 100) {
throw new RangeError('threshold must be a finite number between 0 and 100');
}
return scores.filter((score) =>
Number.isFinite(score) && score >= 0 && score <= 100 && score >= threshold
);
}
入力全体の検査、しきい値の検査、各点数の検査を分けました。Number.isFinite は文字列を数値へ変換せず、有限な数値かを調べるために使っています。filter は条件を満たす要素を抽出する処理です。詳細な言語仕様は、MDNのNumber.isFiniteとArray.filterを確認してください。
>= threshold だけ直して終わらず、0〜100という有効範囲や、入力配列を壊さないことも確認します。「目についたバグを1つ直した」と「定義した仕様を確認した」は別です。
4. Nodeの標準テストで、仕様表を16件のテストにする
Nodeを実行できる自分の作業フォルダで試します。まず node --version でバージョンを記録してください。この記事の実行結果は、Node v24.22.1(macOS)で2026年10月27日に実行したものです。別の環境では、その環境で再実行した結果を残します。
同じフォルダに ai-code-review.test.mjs を保存します。仕様表の行ごとに、テストを1件以上対応させています。
import test from 'node:test';
import assert from 'node:assert/strict';
import { selectPassingScores } from './ai-code-review.mjs';
test('通常値: 80以上を入力順で返す', () => {
assert.deepEqual(selectPassingScores([70, 85, 90]), [85, 90]);
});
test('境界値: 79は不合格、80と81は合格', () => {
assert.deepEqual(selectPassingScores([79, 80, 81]), [80, 81]);
});
test('文字列の90を数値へ暗黙変換しない', () => {
assert.deepEqual(selectPassingScores(['90', 90]), [90]);
});
test('null・undefined・真偽値を無視する', () => {
assert.deepEqual(selectPassingScores([null, undefined, true, false, 80]), [80]);
});
test('NaNと無限大を除外する', () => {
assert.deepEqual(selectPassingScores([NaN, Infinity, -Infinity, 99]), [99]);
});
test('有効範囲の外を除外する', () => {
assert.deepEqual(selectPassingScores([-1, 0, 100, 101]), [100]);
});
test('空配列には空配列を返す', () => {
assert.deepEqual(selectPassingScores([]), []);
});
test('重複と入力順を維持する', () => {
assert.deepEqual(selectPassingScores([100, 80, 100, 90]), [100, 80, 100, 90]);
});
test('入力配列を変更せず別配列を返す', () => {
const source = Object.freeze([90, 70, 80]);
const result = selectPassingScores(source);
assert.deepEqual(source, [90, 70, 80]);
assert.notEqual(result, source);
});
test('しきい値0を指定できる', () => {
assert.deepEqual(selectPassingScores([-1, 0, 1, 100, 101], 0), [0, 1, 100]);
});
test('しきい値100を指定できる', () => {
assert.deepEqual(selectPassingScores([99, 100, 101], 100), [100]);
});
test('小数の数値も仕様の範囲で判定する', () => {
assert.deepEqual(selectPassingScores([79.9, 80, 80.1]), [80, 80.1]);
});
test('配列以外はTypeError', () => {
for (const value of [null, undefined, 80, '80', {}]) {
assert.throws(() => selectPassingScores(value), TypeError);
}
});
test('不正なしきい値はRangeError', () => {
for (const threshold of [-1, 101, NaN, Infinity, '80', null]) {
assert.throws(() => selectPassingScores([80], threshold), RangeError);
}
});
test('配列の穴は値として返さない', () => {
const source = new Array(3); source[1] = 80;
assert.deepEqual(selectPassingScores(source), [80]);
});
test('教材用の誤実装が境界値と型の仕様を破ることを確認', () => {
// 説明用にわざと作った誤実装。AIの実際の出力ではない
const wrong = (scores, threshold = 80) => scores.filter((score) => score > threshold);
assert.notDeepEqual(wrong([79, 80, 81]), [80, 81]);
assert.notDeepEqual(wrong(['90', 90]), [90]);
});
実行コマンドは次のとおりです。
node --test ai-code-review.test.mjs
修正例(3節のコード)で実行した結果の末尾です。
ℹ tests 16
ℹ suites 0
ℹ pass 16
ℹ fail 0
比較の仕組みはNodeのassert公式文書で確認できます。
テストを書くときの注意は、期待値を、検証対象の関数を使って計算しないことです。間違った関数から「正解」を作ると、実装とテストが同じ間違いを共有します。1節の表を基に、期待値は独立して書いてください。
誤実装に同じテストを当てると、どこで落ちるか
ai-code-review.mjs の中身を2節の誤実装に差し替えて、同じコマンドを実行しました。最後の1件は誤実装そのものを確かめる比較用なので、残りの15件で見ます。
| 結果 | 件数 | テスト |
|---|---|---|
| 失敗 | 11 | 境界値、文字列の90、null・真偽値、NaN・無限大、範囲外、重複と順序、しきい値0、しきい値100、小数、不正なしきい値、配列の穴 |
| 成功 | 4 | 通常値、空配列、入力配列を変更しない、配列以外はTypeError |
成功した4件に注目してください。通常値 [70, 85, 90] には境界の80が含まれず、空配列はどんな条件でも空のまま返ります。「配列以外はTypeError」も、誤実装が入力を検査したからではありません。配列でない値に filter を呼ぼうとして、JavaScript自身がTypeErrorを出しただけです。
AIが書いたコードを、AIが出した「普通の例」だけで試すと、このような偶然の成功を合格と取り違えます。境界の値、型の違う値、仕様の範囲外の値を、自分で選んで足してください。
5. テストが通っても、確認していないことは残る
このテストで分かるのは、点数抽出関数が決めた仕様と入力例に合うことです。画面、データベース、認証、外部API、個人情報の保護、アクセス集中時の動作は確認していません。
例えば、ログインした利用者だけが見られる画面にこの関数を置いても、関数の試験だけでアクセス制御が正しいとは言えません。実際のサービスへ組み込むときは、そのサービスの受入条件と承認手順を別に確認します。AIに書かせたコードの認可漏れ・秘密情報・依存パッケージの確かめ方は、AI時代のセキュリティ講座で扱っています。
学習でのゴールは「何でも安全だと言えること」ではありません。「どの仕様を、どの入力で、どの結果まで確かめたか」を説明できることです。
6. 検証の記録を、成果物の説明に使う
次のように記録すると、あとから確認し直せます。この記事の実行で埋めると、こうなります。
対象: selectPassingScores
仕様: 0〜100の有限な数値だけを対象に、しきい値以上を抽出する
環境: Node v24.22.1(macOS)
実行: node --test ai-code-review.test.mjs
結果: 修正例 16件成功・0件失敗/誤実装 15件中11件失敗(比較用の1件を除く)
修正: > を >= に変更し、型と範囲の検査を追加
未確認: UI、認証、DB、外部API、性能
自分で試すときは、まだ実行していないのに成功数だけ埋めないでください。AIを使った部分、仕様を決めた部分、自分で確認した部分も分けて書きます。記録を応募用の成果物にまとめるときは、ポートフォリオとREADMEの作り方を使えます。
よくある質問
AIにテストも作らせれば、それで十分ですか?
十分とは限りません。実装とテストが同じ仕様の取り違えを含む可能性があるため、入力と期待値を先に人が確認します。AIのテスト案は、しきい値を79・80・81に変える、数値を文字列にする、配列を空にする、元データが変わらないかを見る、といった観点の抜けを探す補助として使ってください。
テストは何件あれば合格ですか?
件数だけでは決まりません。この記事では仕様表の行ごとにテストを置いた結果が16件でした。4節のとおり、件数があっても「普通の値」ばかりなら誤実装が通ります。実サービスでは、確認すべき振る舞いとリスクから項目を決めます。
エラーが表示されなければ、実装は正しいのではありませんか?
この教材の誤実装は、エラーを出さずに80点を落とします。実行できることと、仕様に合うことを分けて確認してください。
次に進むために
まず仕様表を1つ作り、境界値・型違い・空データの3ケースを自分で確かめてください。条件分岐や配列の基礎で手が止まるなら、JavaScript練習問題10問で、まず基礎の練習問題を確かめてから戻ってきてください。結果をREADMEに残したら、何を判断して直したのかを説明します。足りない基礎は無料講座の一覧から選べます。
参考資料
- Node.js「Assert」 https://nodejs.org/api/assert.html
- Node.js「Test runner」 https://nodejs.org/api/test.html
- MDN「Number.isFinite()」 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite
- MDN「Array.prototype.filter()」 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
無料の学習講座で、いまから手を動かす
AI開発からWeb開発まで、未経験でも進められる講座を無料で公開しています。
会員登録(無料) すると、気になる求人の保存と講座の受講履歴の記録ができます。
この順番で合っているか、確認しませんか
学習の順番は、目指す職種によって変わります。いま考えている進め方を書いて送ってもらえれば、無料講座の内容と実際の求人要件に照らして返答します。
相談を書いて送る(無料・会員登録なし)返答までお時間をいただくことがあります。
新着記事
PSM1の勉強方法は?日本語受験と合格ライン85%の対策
PSM I(Professional Scrum Master I)の勉強方法を、2026年11月に公式で確かめた形式(80問・60分・85%・200米ドル・日本語可)と2020年版スクラムガイドの変更点から解説。無料講座の例題3問つき。
ISO42001内部監査員研修とは?27001との違いと統合監査の要点
ISO/IEC 42001の内部監査員研修に共通の試験はありません。研修の日数と修了条件の幅、箇条4〜10と附属書Aの38の管理策、27001との違いと統合監査、国内の認証14件という現状、無料講座の例題3問を解説します。
ISMS内部監査員の資格とは?ISO27001研修の受け方と修了の違い
ISMS内部監査員は国家資格ではありません。研修の修了・研修機関の試験・JRCAの登録の違い、研修の日数と修了条件の幅、ISO/IEC 27001:2022の箇条と93の管理策、2026年の変更点、無料講座の例題3問を解説します。
CompTIA Security+の難易度は?V8切り替えと日本語試験
Security+の難易度を、2026年11月に公式で確かめた形式から解説。V8(SY0-801)は11月17日ごろ開始で英語のみ、日本語のSY0-701は2027年8月13日まで。勉強の順番、無料講座の例題、求人データも紹介します。