テスト・レビュー学習記事全11節Verify

承認可否の判定関数にテストケースを作り、期待値と照合する

この記事で身につくこと期待値、境界値、正常・異常の入力を用意して処理単位で確認する目的と、「動いた」と「正しい」の違いを説明できる。

記事内目次(24項目)
この記事の管理情報(該当工程・関連要件・成果物の閲覧と保存・実行記録)
記事ID
09-01
該当工程
09-01 個々の処理を確認する(関連:08-01、02-04)
関連要件
REQ-003、REQ-004
到達目標
期待値、境界値、正常・異常の入力を用意して処理単位で確認する目的と、「動いた」と「正しい」の違いを説明できる
主要な成果物
確認環境と検証状況
本文のテストと検出力の確認は examples/09-01-unit-tests/ を正本とし、Claude Codeが2026-09-20にローカルで実行して stdout・stderr・終了コードを記録済み(macOS 26.6.2、Node.js v24.18.0)。検証対象は canApprove のみ。第5.3節の指示文と初稿は説明用の例。人による実読・内容確認は未実施。
未検証の範囲
保存先への反映、通信を介した場合の動作、複数の利用者が同時に操作する場合の動作、08-02のガードと08-04の部分失敗の扱い、5通り以外の壊れ方に対する検出力は、この記事の時点では確認していない。
実行記録

正本の原稿にある管理表(記事ID・先に読むと理解しやすい記事・成果物パス・最終更新日など)は、この下の本文冒頭に「原稿の管理表」として開閉できる形で残しています。

原稿の管理表(13項目:記事ID・カテゴリ・要件ID・先に読むと理解しやすい記事・成果物パス・対象ツール・検証状況・最終更新日など)
管理項目内容
記事ID09-01
主な中カテゴリ09-01 個々の処理を確認する
関連中カテゴリ08-01、02-04、09-02
関連要件IDREQ-003、REQ-004
先に読むと理解しやすい記事08-01(最小の処理を動かす)、02-04(例外と完成条件を決める)を読んでいると理解しやすい。未読でも本文で必要な内容は説明する
到達目標期待値、境界値、正常・異常の入力を用意して処理単位で確認する目的と、「動いた」と「正しい」の違いを説明できる
3層の配置理解する=第3〜4節/作って確かめる=第5〜7節/設計を判断する=第8節
成果物パスtemplates/09-01-unit-tests_template.md、templates/09-01-unit-tests_example.md、examples/09-01-unit-tests/(approval.js、test_cases.js、run_tests.js、mutants.js、run_mutation_tests.js)、evidence_public/phase5b2b-2026-09-20/09-01/run_log.md
出典第11節参照
対象ツール・バージョン教材演習用にNode.js v24.18.0(node:assertのみ使用)
検証状況本文のテストと検出力の確認は examples/09-01-unit-tests/ を正本とし、Claude Codeが2026-09-20にローカルで実行して stdout・stderr・終了コードを記録済み(第6〜7節、evidence_public/phase5b2b-2026-09-20/09-01/run_log.md)。第5.3節の指示文と初稿は説明用の例であり、実際にAIへ送信・観測した記録ではない。人による実読・内容確認は未実施
残課題第11節参照
最終更新日2026-09-20(新規作成。同日、Phase 5B-2B:受入監査の「REVIEWが弱い」「全件PASSだけ」「検出力に触れていない」「08-04の修正を検証するとしながら対象がcanApproveだけ」指摘に対し、検証対象を canApprove(08-01)に限定して明記し、第5.4節「テストの検出力を確かめる」(mutants.js、新規)と第6節の実測、第3.3節「PASSで分かること・分からないこと」、第5.3節「AIにテストを頼む」と第6.2節のレビューを追加。テストケース・期待値・approval.js は不変)

この記事の会社・人物・データはすべて架空の設定です。実在する会社や案件とは関係ありません。本文のテストと実行結果は examples/09-01-unit-tests/ の正本と同じもので、2026-09-20にローカル環境で実際に実行して記録したものです(第6〜7節、実行記録は第9節)。第5.3節の指示文と第6.1節の初稿は説明用の例で、実際のAI出力を観測したものではありません。

この記事は何の話か。08-01で作った「承認可否」を判定する関数 canApprove に対し、正常・異常・境界の入力パターンでテストケースを作り、実行結果を期待値と照合し、さらに「そのテストは壊れた実装を本当に見つけられるか」(検出力)を確かめる話です。

なぜ必要か。関数が例外を出さずに動いたとしても、すべての入力パターンで正しい結果を返すとは限りません。あらかじめ期待値を決めておき、実行結果と照合することで、「動いた」と「正しい」の違いを確認できます。しかし「全件PASS」は、そのテストが見ている範囲だけの話です。テストが壊れた実装でも通ってしまうなら、PASSは何も証明しません。

この記事を読むとできるようになること。

  • 期待値、境界値、正常・異常の入力を用意して処理単位で確認する目的を説明できる
  • 承認可否の判定関数に対し、複数の入力パターンでテストケースを作り、実行結果を期待値と照合して記録できる
  • テストが壊れた実装を検出できるかを確かめ、「PASSで分かること」「PASSでも分からないこと」を分けて書ける
  • 処理単位の確認で分かることと分からないことを整理し、次の確認段階へ回す範囲を判断できる

読む順路。この記事は3つの深さで読めます。全部を順に読む必要はありません。

  • 基本だけ理解する:第1節→第3節(「動いた」と「正しい」の違い、PASSで分かること・分からないこと)→第4節(判断すること)→第5.1節のテストケース表→第6.3節の検出力の表→第7節の「確かめていないこと」→第9節。コードを読まなくても、期待値を先に決める理由と、全件PASSが何を意味するかが分かります。
  • 作って確かめる:上に加えて第5.2節(実行スクリプト)→第5.3節(AIにテストを頼む)→第5.4節(壊した版と検出力の確認)→第6節(実行結果とレビュー)→第7節。
  • 設計判断を深める:上に加えて第8節(処理単位の確認の限界、検出力をどこまで確かめるか、適用限界)。

1. 今回の業務課題と完成するもの

この記事でやることは、08-01で作った承認可否の判定関数(canApprove)に対し、提出済み・下書き・承認済みの状態と、割当承認者・非割当・本人の組合せでテストケースを作り、実行結果を期待値と照合して記録することです。あわせて、条件を1つずつ壊した版を用意し、テストがそれを検出できるかを確かめます。

08-01では入力を変えて動かし、期待結果との照合を人が表で行いました。この記事では、期待値をコードに書いて機械的に照合する仕組みを作り、さらにその仕組み自体を疑います。

この記事の検証対象は canApprove(08-01)だけです。 08-02の保存処理と提出前のガード、08-04の部分失敗の扱いは、この記事のテストの対象ではありません(第4節・第7節)。

この記事を読み終えると、次のものが手元にできます。

  • テストケースと実行スクリプト、壊した版と検出力の確認スクリプト(examples/09-01-unit-tests/)
  • テストケース・実行結果(ID、対象処理、入力、期待値、実行結果、判定、関連要件ID)と、検出力の記録

2. 前提知識と読む順路

この記事で使う言葉を先に説明します。

テストケース

一言でいうと、「この入力なら、こういう結果になるはず」という組合せを1つにまとめたものです。たとえば、TC-01は「提出済み・割当承認者・本人でない」という入力に対し「承認できる(true)」という結果を期待します。

期待値

一言でいうと、実行する前に「こうなるはずだ」と決めておく結果です。実行してから決める値ではありません。

境界値

一言でいうと、条件の分かれ目にあたる値です。たとえば、状態が空文字(想定していない値)の場合にどう振る舞うかは、境界値の確認にあたります。

これがないと、「たまたま動いた入力」だけを見て正しいと判断してしまい、見落としに気づけません。

テストの検出力

一言でいうと、実装が壊れたときに、テストが本当にFAILになるかどうかです。

たとえば、割当承認者の判定を削った実装に対してTC-04がFAILになれば、そのテストには「割当の壊れ方」に対する検出力があります。

これがないと、「全件PASS」が「実装が正しい」ことの証拠にならず、AIが書いたテストをAIが通す構図に気づけません(記事B、09-03)。

読む順路:判定関数は08-01、受入条件は02-04(記事AのAC-001〜004が対応)が該当します。この記事のテストケースは、記事B(09-02)の要件・テスト対応表に統合します。

3. 理解する:「動いた」と「正しい」の違い

3.1 期待値を先に決める理由

実行してから「まあ、こんなものだろう」と結果を見て判断すると、誤りを正しいと思い込む可能性があります。この記事では、実行する前にテストケースと期待値を書き出し(第5.1節)、実行後に照合します(第6節)。期待値の出所は実装ではなく、記事Aの受入条件(AC-001〜004)と05-02・05-03の設計です。

3.2 よくある誤解

「1つのパターンで動けば十分」。08-01では代表的な入力で動作を確認しましたが、それだけでは割当外や自己承認のような異常系を見落とす可能性があります。正常・異常・境界の3種類を用意します(第5.1節)。

「テストが通れば、アプリ全体が正しく動く」。この記事で確認できるのは、この関数単体の入出力だけです。保存・通信・同時操作を含めた確認は、08-02以降・09-02以降で扱います(第8.1節)。

「全件PASSなら、テストは十分」。テストが見ていない条件は、壊れても通ります。壊した版で確かめない限り、PASSの数は安心の根拠になりません(第3.3節・第5.4節)。

3.3 PASSで分かること・分からないこと

結論から言うと、この記事の6件がPASSして分かるのは「canApprove が、この6通りの入力で期待どおりの値を返す」ことだけです。

テスト何を証明したいかPASSで分かることPASSしても分からないこと
TC-01〜05(正常・異常)状態・割当・本人の3条件が、記事AのAC-001〜004どおりに働く6通りの入力に対する戻り値が期待どおり保存した依頼から読み出した値が正しく渡されるか(08-02)。画面や通信を通ったときの動き(08-03)
TC-06(境界)想定外の状態(空文字)で安全側(false)に倒れる空文字でfalseになるnull・undefined・全角の「提出済み」など、他の想定外の入力
検出力の確認(第5.4節)3条件のどれかが壊れたら、少なくとも1件がFAILになる5通りの壊れ方を検出できる5通り以外の壊れ方(例:条件の順序の入れ替えで戻り値が変わらない壊れ方は、この方法では検出できない)

右の列が、第7節の「確かめていないこと」の元になります。

4. 判断すること

処理単位のテストを作るときに人が判断することは、次の5つです。

判断することなぜ判断が必要か仮置きしてよいか間違えた場合の影響
何を検証対象にするか(この記事は canApprove だけ)対象を曖昧にすると、「テストした」範囲が実際より広く見える不可。対象と対象外を明記する08-04の部分失敗まで「09-01でテスト済み」と誤って引き継がれる
期待値をどこから取るか実装から取ると、実装の誤りを追認する不可。記事Aの受入条件と05-02・05-03から取る割当外でtrueを返す実装に合わせて期待値がtrueになり、PASSする
何をPASS条件にするか(戻り値の一致だけでよいか)判定関数は戻り値だけだが、保存を伴う処理では保存先の中身も見る必要がある(記事B)可(この記事は戻り値の一致。保存を伴う処理は記事Bで)戻り値は正しいが保存先が壊れている実装を見逃す
どこまで確認すれば十分か(境界値をどこまで足すか)境界は無限にある。失敗の影響と確認の費用で決める可(この記事は空文字1件。追加候補は第7節)全角の状態名のような入力で承認が通る
テストの検出力をどう確かめるか検出力を確かめないテストは、壊れても通る可能性がある可(この記事は条件を1つずつ壊した5通り。壊し方を増やすかは第8.2節)検出力のないテストのPASSを安全の根拠にする

勝手に決めてはいけないこと:05-02の状態遷移表、05-03の権限表にない条件を、この記事でテストケースとして追加しません。この関数単体のテストが通ったことを、アプリ全体や本番の安全性の証明として扱いません。この記事のテストが08-02・08-04を検証したかのように書きません。

AIに任せる範囲:テストケースをコードの形に整えること、実行スクリプトの下書き、壊した版の候補出し、実行して出力をそのまま返すことはAIに任せられます。任せてはいけないのは、期待値を決めること、検証対象の範囲を決めること、検出力の確認結果を読んで「テストが十分か」を判断することです。AIにテストを書かせてAIに通させると、期待値が実装から逆算されていても気づけません(第6.2節、09-03)。

5. 作って確かめる:テストケースを作り実行する

5.1 テストケース一覧

期待値は、実行する前に受入条件と設計から決めています。

ID対象処理入力期待値区分根拠
TC-01canApprove提出済み・割当承認者・本人でないtrue正常AC-001
TC-02canApprove下書き・割当承認者・本人でないfalse異常AC-004(状態外)
TC-03canApprove承認済み・割当承認者・本人でないfalse異常05-02(承認済みからの承認は無い)
TC-04canApprove提出済み・非割当・本人でないfalse異常AC-002(割当外)
TC-05canApprove提出済み・割当承認者・本人false異常AC-003(自己承認)
TC-06canApprove状態が空文字・割当承認者・本人でないfalse境界想定外の入力は安全側(承認しない)
// examples/09-01-unit-tests/test_cases.js
// 教材演習用:canApprove関数のテストケース一覧(正常・異常・境界を含む)
module.exports = [
  { id: "TC-01", kind: "正常", input: { state: "提出済み", isAssignedApprover: true, isSelf: false }, expected: true, note: "割当承認者が他人の提出済み依頼を承認できる" },
  { id: "TC-02", kind: "異常", input: { state: "下書き", isAssignedApprover: true, isSelf: false }, expected: false, note: "下書き状態は承認できない" },
  { id: "TC-03", kind: "異常", input: { state: "承認済み", isAssignedApprover: true, isSelf: false }, expected: false, note: "承認済みの依頼を再度承認できない" },
  { id: "TC-04", kind: "異常", input: { state: "提出済み", isAssignedApprover: false, isSelf: false }, expected: false, note: "割り当てられていない承認者は承認できない(REQ-002)" },
  { id: "TC-05", kind: "異常", input: { state: "提出済み", isAssignedApprover: true, isSelf: true }, expected: false, note: "自己承認はできない(REQ-003)" },
  { id: "TC-06", kind: "境界", input: { state: "", isAssignedApprover: true, isSelf: false }, expected: false, note: "状態が空文字(想定外の入力)の場合は承認できない" },
];

5.2 実行スクリプト

// examples/09-01-unit-tests/run_tests.js
// 教材演習用:テストケースを実行し、期待値と実行結果を照合して記録する(node:assertのみ使用)
const assert = require("node:assert");
const { canApprove } = require("./approval.js");
const cases = require("./test_cases.js");

console.log(`実行環境: ${process.platform}, Node.js ${process.version}, ${new Date().toISOString()}`);
console.log("");

let passCount = 0;
for (const tc of cases) {
  const { state, isAssignedApprover, isSelf } = tc.input;
  const actual = canApprove(state, isAssignedApprover, isSelf);
  let judged;
  try {
    assert.strictEqual(actual, tc.expected);
    judged = "PASS";
    passCount++;
  } catch (e) {
    judged = "FAIL";
  }
  console.log(`${tc.id} [${tc.kind}] 入力=${JSON.stringify(tc.input)} 期待値=${tc.expected} 実行結果=${actual} 判定=${judged}(${tc.note})`);
}

console.log("");
console.log(`合計: ${cases.length}件、PASS: ${passCount}件、FAIL: ${cases.length - passCount}件`);

5.3 AIにテストを頼む

テストケースをコードにする作業は、AIに頼めます。ただし期待値は人が先に決めて渡します。渡す情報と渡さない情報を先に決めます。

渡す情報内容渡さない情報と理由
検証対象canApprove(state, isAssignedApprover, isSelf) の呼び方(入力3つ、戻り値は真偽値)関数の実装(approval.js の中身)。渡すと、実装から期待値を逆算したテストになりやすい
期待値(人が決めたもの)第5.1節の6件の入力と期待値、根拠(AC-001〜004、05-02)—
返してほしい形テストケースを配列で持つファイルと、実行して PASS/FAIL と件数を表示するスクリプト。標準機能(node:assert)のみ—

指示文は次のとおりです。これは説明のために書いた例で、実際にAIへ送信した記録ではありません。

承認可否を判定する関数 canApprove(state, isAssignedApprover, isSelf)(戻り値は true/false)に対するテストを、JavaScript(Node.js 標準機能 node:assert のみ)で作ってください。関数の実装は渡しません。

テストケース(期待値は要件から決めたものです。実装に合わせて変えないでください):
- TC-01 提出済み・割当承認者・本人でない → true
- TC-02 下書き・割当承認者・本人でない → false
- TC-03 承認済み・割当承認者・本人でない → false
- TC-04 提出済み・割当外・本人でない → false
- TC-05 提出済み・割当承認者・本人 → false
- TC-06 状態が空文字・割当承認者・本人でない → false

返し方:
- test_cases.js(上の6件を配列で)と run_tests.js(各件を実行し、PASS/FAIL と合計を表示)の2ファイル。
- run_tests.js を実行した出力をそのまま貼ってください。

してはいけないこと:
- 期待値を変えないでください。
- 上に無いテストケースを足さないでください。足すべき候補があれば、コードには入れず別に列挙してください。
- 「全件PASSしたので正しい」と結論しないでください。テストが何を見ていないかも書いてください。

5.4 テストの検出力を確かめる

「全件PASS」の重さは、テストが何を見逃すかで決まります。canApprove の条件を1つずつ壊した版を5通り用意し(mutants.js。メモリ上の関数で、正本の approval.js は変えません)、同じテストケースを当てて、どの壊れ方をどのテストがFAILにできるかを表にします(run_mutation_tests.js)。09-03がファイルをコピーして1行を削る方法だったのに対し、ここでは壊れ方を複数並べて「どのテストが、どの壊れ方を受け持つか」を見ます。

// examples/09-01-unit-tests/mutants.js
// 教材演習用:テストの検出力を確かめるための「故意に壊した版」の一覧(説明用。正本 approval.js は変えない)。
// それぞれ canApprove の条件を1つずつ壊してある。テスト(test_cases.js)がこれらを FAIL にできれば、
// そのテストは「その壊れ方」を検出できる。すべて PASS してしまう壊し方があれば、テストに穴がある。
module.exports = [
  { id: "M1", description: "状態の判定を削除(どの状態でも承認できる)",
    canApprove: (state, isAssignedApprover, isSelf) => { if (!isAssignedApprover) return false; if (isSelf) return false; return true; } },
  { id: "M2", description: "割当承認者の判定を削除(割当外でも承認できる)",
    canApprove: (state, isAssignedApprover, isSelf) => { if (state !== "提出済み") return false; if (isSelf) return false; return true; } },
  { id: "M3", description: "自己承認の判定を削除(本人でも承認できる)",
    canApprove: (state, isAssignedApprover, isSelf) => { if (state !== "提出済み") return false; if (!isAssignedApprover) return false; return true; } },
  { id: "M4", description: "状態の比較を「提出済み以外なら承認できる」に反転",
    canApprove: (state, isAssignedApprover, isSelf) => { if (state === "提出済み") return false; if (!isAssignedApprover) return false; if (isSelf) return false; return true; } },
  { id: "M5", description: "状態が空文字のときだけ承認できてしまう(境界の壊れ方)",
    canApprove: (state, isAssignedApprover, isSelf) => { if (state !== "提出済み" && state !== "") return false; if (!isAssignedApprover) return false; if (isSelf) return false; return true; } },
];
// examples/09-01-unit-tests/run_mutation_tests.js
// 教材演習用:テストの検出力を確かめる。
// 正本 approval.js の代わりに、故意に壊した版(mutants.js)に同じテストケース(test_cases.js)を当て、
// どの壊れ方をどのテストが検出(FAIL)できるかを表にする。正本には手を加えない。
// 終了コード: すべての壊れ方を少なくとも1件のテストが検出できれば 0、検出できない壊れ方があれば 1。
const cases = require("./test_cases.js");
const mutants = require("./mutants.js");

console.log(`テストケース: ${cases.length}件、壊した版: ${mutants.length}通り`);
console.log("");

let undetected = 0;
for (const m of mutants) {
  const failed = [];
  for (const tc of cases) {
    const { state, isAssignedApprover, isSelf } = tc.input;
    const actual = m.canApprove(state, isAssignedApprover, isSelf);
    if (actual !== tc.expected) failed.push(tc.id);
  }
  const detected = failed.length > 0;
  if (!detected) undetected++;
  console.log(`${m.id} ${m.description}: ${detected ? "検出できた" : "検出できず"}(FAIL になったテスト: ${detected ? failed.join(", ") : "なし"})`);
}

console.log("");
if (undetected === 0) {
  console.log(`${mutants.length}通りすべての壊れ方を検出できた。このテストは、少なくともこれらの壊れ方には検出力がある`);
} else {
  console.log(`${undetected}通りの壊れ方を検出できなかった。テストケースの追加を検討する`);
}
process.exit(undetected === 0 ? 0 : 1);

期待結果は「5通りすべてで、少なくとも1件がFAILになる」です。もし検出できない壊れ方があれば、その条件を見ているテストが無いということで、テストケースを足します。

6. 実行結果(テストケース・実行結果)

6.1 説明用の初稿(AIの初稿という設定)

第5.3節の指示に対して、次のような初稿が返ってきた、という設定にします(実際のAI出力を観測したものではなく、教えるために不備を入れた説明用の例です)。

  • TC-06(状態が空文字)の期待値が true に変わっていた(「空文字は状態の判定を通らないので、他の条件がそろっていれば承認できるはず」という注記つき)
  • 6件に加えて、TC-07「状態が null」の期待値を false として勝手に追加していた
  • 実行結果の末尾に「6件すべてPASSしたので、canApprove は正しく実装されています」と書いていた

6.2 見つけ方と直し方

見るところは3つです。期待値が、渡した6件と一致しているか。ケースが増減していないか。「PASS」の報告に「何を見ていないか」が添えてあるか。

問題点なぜ問題かどう見つけるか誰が判断するかどう直したか
TC-06の期待値が true になっている期待値は要件から決めたもの。AIが「実装ではこう動くはず」と推測して期待値を変えると、テストは実装の追認になる。空文字で承認できてよい業務上の理由は無い生成された expected を、第5.1節の表と1件ずつ照合する期待値を決めた人(架空:検証担当)false に戻した。実装の動きに合わせて期待値を変えない、と修正指示に書いた
TC-07が勝手に足されている候補としては有用だが、コードに入ると「人が決めた期待値」と「AIが決めた期待値」が混ざるケース数が渡した数と一致するかを見る検証担当コードから外し、追加候補として第7節の「確かめていないこと」に移した
「全件PASSしたので正しい」PASSはテストが見ている範囲の話。見ていない範囲(保存、通信、他の境界)は分からない(第3.3節)結論の文に「見ていないこと」が添えてあるかを見る検証担当結論を「6件の入力で期待どおり」に直し、検出力の確認(第5.4節)と確かめていないこと(第7節)を添えた

AIへの修正指示は次のとおりです(説明用の例)。

指摘:
1. TC-06 の期待値を false に戻してください。期待値は要件から決めたもので、実装の動きに合わせて変えません。
2. TC-07(null)はコードから外してください。追加候補として別に列挙するにとどめてください。
3. 「全件PASSしたので正しい」という結論を削除し、このテストが見ていない範囲を書いてください。

変えないこと:
- 6件の入力と、実行スクリプトの表示形式は変更しないでください。

6.3 修正後の実行結果

examples/09-01-unit-tests/ で実行した stdout と終了コードです(整形・省略なし。stderr への出力はありませんでした。1行目の日時は実行のたびに変わります)。

$ node run_tests.js
実行環境: darwin, Node.js v24.18.0, 2026-09-20T14:33:34.609Z

TC-01 [正常] 入力={"state":"提出済み","isAssignedApprover":true,"isSelf":false} 期待値=true 実行結果=true 判定=PASS(割当承認者が他人の提出済み依頼を承認できる)
TC-02 [異常] 入力={"state":"下書き","isAssignedApprover":true,"isSelf":false} 期待値=false 実行結果=false 判定=PASS(下書き状態は承認できない)
TC-03 [異常] 入力={"state":"承認済み","isAssignedApprover":true,"isSelf":false} 期待値=false 実行結果=false 判定=PASS(承認済みの依頼を再度承認できない)
TC-04 [異常] 入力={"state":"提出済み","isAssignedApprover":false,"isSelf":false} 期待値=false 実行結果=false 判定=PASS(割り当てられていない承認者は承認できない(REQ-002))
TC-05 [異常] 入力={"state":"提出済み","isAssignedApprover":true,"isSelf":true} 期待値=false 実行結果=false 判定=PASS(自己承認はできない(REQ-003))
TC-06 [境界] 入力={"state":"","isAssignedApprover":true,"isSelf":false} 期待値=false 実行結果=false 判定=PASS(状態が空文字(想定外の入力)の場合は承認できない)

合計: 6件、PASS: 6件、FAIL: 0件
終了コード: 0
$ node run_mutation_tests.js
テストケース: 6件、壊した版: 5通り

M1 状態の判定を削除(どの状態でも承認できる): 検出できた(FAIL になったテスト: TC-02, TC-03, TC-06)
M2 割当承認者の判定を削除(割当外でも承認できる): 検出できた(FAIL になったテスト: TC-04)
M3 自己承認の判定を削除(本人でも承認できる): 検出できた(FAIL になったテスト: TC-05)
M4 状態の比較を「提出済み以外なら承認できる」に反転: 検出できた(FAIL になったテスト: TC-01, TC-02, TC-03, TC-06)
M5 状態が空文字のときだけ承認できてしまう(境界の壊れ方): 検出できた(FAIL になったテスト: TC-06)

5通りすべての壊れ方を検出できた。このテストは、少なくともこれらの壊れ方には検出力がある
終了コード: 0

検出力の表から分かること:

壊れ方検出したテスト意味
M1(状態の判定なし)TC-02、03、06状態の異常系が3件あるので、状態の壊れ方は複数のテストで受け止められる
M2(割当の判定なし)TC-04だけ割当の壊れ方を見ているのはTC-04だけ。TC-04を消すと検出できなくなる
M3(自己承認の判定なし)TC-05だけ同様に、TC-05だけが自己承認を守っている
M4(状態の比較を反転)TC-01、02、03、06正常系TC-01も検出に効く
M5(空文字だけ通る)TC-06だけ境界値TC-06が無ければ、この壊れ方は通る

M2・M3・M5は「1件だけが守っている」壊れ方です。テストを減らすときに、これらの1件を消すと、その条件は誰も見ていない状態になります。

7. 確認結果

実行環境:macOS 26.6.2、Node.js v24.18.0(node:assertのみ使用)、2026-09-20に実行。教材演習用のフォルダ(examples/09-01-unit-tests/)の中で行い、正本 approval.js は変えていません。stdout・stderr・終了コードをそのまま記録した実行記録は第9節の run_log.md にあります。

完了条件との照合:

完了条件確認結果
正常・異常・境界の3種類が含まれている第5.1節のTC-01(正常)、TC-02〜05(異常)、TC-06(境界)に記載
期待値が実行前に書かれている第5.1節の表・コードに記載(受入条件と設計から決めたもの。根拠の列)
実行結果に環境と日時がある第6.3節の1行目に記載

この記事で確かめたこと:canApprove が6通りの入力で期待どおりの値を返すこと。条件を1つずつ壊した5通りの版を、テストが検出できること。

この記事で確かめていないこと:

範囲理由どこで確かめるか
保存先への反映(保存した依頼から読み出した値で判定が動くか、保存後の状態と履歴)関数単体の入出力だけを見ている08-02(実行確認)、記事B(保存先の中身まで照合するテスト)
08-02の提出前のガード(存在しない依頼、状態外の提出)この記事のテスト対象外08-02の実行確認、09-03の状態条件
08-04の部分失敗の扱い(状態だけ変わって履歴が無い)この記事のテスト対象外。関数単体のテストでは保存先の途中失敗を起こせない08-04の実行確認、記事BのT6(トランザクション)
通信を介した場合の待ち時間や失敗関数を直接呼んでいる08-03
複数の利用者が同時に操作した場合1つのプロセスで順に実行しているだけ09-03
他の想定外の入力(null、undefined、全角の状態名)境界値は空文字1件だけ追加を検討(第6.2節のTC-07候補)
5通り以外の壊れ方条件の削除・反転・境界の3種類だけ壊し方を増やすかは第8.2節の判断

残る制限:この確認は、関数単体の入出力についてのものです。第5.3節の指示文と第6.1節の初稿は説明用の例であり、実際にAIへ送信・観測した記録ではありません。この記事の本文はClaude Codeが作成したもので、業務担当者による内容確認は行っていません。

8. 設計を判断する

8.1 処理単位の確認で分かることと分からないこと

分かること分からないこと
関数への入力と出力の対応が期待どおりか保存先に正しく反映されるか(08-02で扱う)
異常な入力に対して安全側(false)に倒れるか通信を介した場合の待ち時間や失敗(08-03で扱う)
テストが、条件の欠落・反転を検出できるか複数の利用者が同時に操作した場合の動作(09-03で扱う)
—保存の途中で失敗したときのデータの整合性(08-04・記事Bで扱う)

処理単位の確認は、関数そのものの正しさを確かめるものであり、アプリ全体の正しさを保証するものではありません。分からないことは、09-02以降の確認段階に回します。

8.2 検出力をどこまで確かめるか

選択肢費用分かること適する場面
確かめない0何も分からない。PASSが実装の追認でも気づけない適する場面はない(この記事で問題として扱う)
条件を1つずつ壊す(この記事、5通り)小さい(壊した版を書くだけ)各条件を少なくとも1件のテストが見ていること。「1件だけが守っている」条件が分かる条件が少ない関数。テストを減らすときの判断材料
壊し方を網羅的に増やす大きいより多くの壊れ方に対する検出力失敗の影響が大きい処理(権限・金額)。ツールで自動化できる段階

この記事では、条件が3つの小さな関数なので「1つずつ壊す」で十分と判断します。条件が増えたり、失敗の影響が大きい処理では、壊し方を増やすか、記事Bのように故意の誤変更をファイル単位で用意します。

8.3 適用限界

このテストは、canApproveという1つの関数だけを対象にしています。この関数を呼び出す側(画面や処理側サーバー)の実装が正しいかどうかは、08-02・08-03・記事Bで確認します。

9. 持ち帰る成果物

成果物空欄版記入例
テストケース・実行結果ひな形(ID、対象処理、入力、期待値、実行結果、判定、関連要件ID)templates/09-01-unit-tests_template.mdtemplates/09-01-unit-tests_example.md
テストケース・実行スクリプト・検出力確認一式examples/09-01-unit-tests/(approval.js=08-01のコピー、test_cases.js、run_tests.js、mutants.js、run_mutation_tests.js)同上
実行記録(stdout・stderr・終了コード、整形なし)—evidence_public/phase5b2b-2026-09-20/09-01/run_log.md

自分の業務に使うときの手順は次のとおりです。

  1. 確認対象の処理(関数)を決め、対象外を明記する
  2. 正常・異常・境界の入力パターンと期待値を、実装を見る前に受入条件・設計から書き出す
  3. 実行し、実行結果と期待値を照合して判定を記録する
  4. 条件を1つずつ壊した版を用意し、テストが検出できるかを確かめる
  5. 分かったことと分からないことを整理し、次の確認段階へ回す範囲を書く

10. 次工程への受渡し

次へ渡すもの:テストケース・実行結果、検出力の記録、確かめていないことの一覧(第7節)。

次工程での使われ方:記事B(09-02)では、このケースを要件・テスト対応表に統合し、保存先の中身まで照合するテストに広げます。09-03では、同時操作・重複の確認と、ファイル単位の検出力確認を行います。

前工程へ戻る条件:このテストケースが05-02・05-03の設計と食い違う場合は、それぞれの記事に戻って確認します。検出できない壊れ方が見つかった場合は、テストケースを足してこの記事に戻ります。

11. 根拠と更新情報

  • この記事の会社・人物・データはすべて架空の設定です。
  • 判定関数は08-01の正本と同じものです(このフォルダにコピーを置いています)。期待値の根拠は記事Aの受入条件AC-001〜004と、05-02・05-03の設計です。
  • テストの検出力を故意の誤変更で確かめる考え方は、記事Bと09-03に一致させています。この記事では、壊れ方を複数並べて「どのテストがどの壊れ方を受け持つか」を表にする形にしています。
  • 第5.3節の指示文は、この記事のために書いた指示文の例であり、実際にAIへ送信した記録ではありません。第6.1節の初稿は説明用の失敗例であり、実際のAI出力を観測した記録ではありません。
  • 制作の来歴:本文はClaude Codeが作成しました。第5〜7節のテストは examples/09-01-unit-tests/ を正本とし、2026-09-20にmacOS 26.6.2・Node.js v24.18.0の環境で実際に実行して、stdout・stderr・終了コードを evidence_public/phase5b2b-2026-09-20/09-01/run_log.md に記録しています。人(ユーザー)による実読・内容確認は未実施です。
  • 2026-09-20(Phase 5B-2B):受入監査で「REVIEWが弱い」「全件PASSだけで検出力に触れていない」「08-04の修正を検証するとしながら対象が canApprove だけ」と指摘された。検証対象を canApprove に限定して明記し(08-02・08-04は対象外と第1・4・7節に記載)、第3.3節「PASSで分かること・分からないこと」、第5.3節「AIにテストを頼む」、第5.4節「テストの検出力を確かめる」(mutants.js・run_mutation_tests.js、新規)、第6.1〜6.2節の初稿とレビュー、第7節の確かめていないことの表、第8.2節、CHECKLIST、記事間の参照を追加し、実行結果を再実行の実測に置き換えた。テストケース・期待値・approval.js は変えていない。
  • 未確認事項:保存先への反映、通信を介した場合の動作、複数の利用者が同時に操作する場合の動作、08-02のガードと08-04の部分失敗の扱い、5通り以外の壊れ方に対する検出力は、この記事の時点では確認していません(第7節)。