Loading

ROUTE

ルートゼロの
アクティビティ

AIが書き換えたJavaScriptの動作を守るには?Node.js標準テストで確認する方法

3

AIにJavaScriptを直してもらった後、「見た目はすっきりしたけれど、本当に前と同じ動きなのか」と迷うことがあります。そんなときは、修正前に入力と期待する出力をテストとして保存し、修正後に同じテストを実行します。この記事では、架空の学習記録の集計機能を使い、追加ライブラリなしで確かめる方法を紹介します。

「読みやすくなった」と「正しく動く」は別の確認

リファクタリングとは、利用者から見える動作を保ちながら内部のコード構造を見直すことです。画面の機能追加や計算結果の変更まで含める場合は、単なる内部整理とは区別します。

ここで確認するのは、実行に成功したかだけではありません。「完了済みの学習記録だけを集計する」という決めごとが、書き換え後も守られているかです。テストは入力例ごとに期待結果を明示できるため、勘ではなく比較で判断できます。

用語解説:回帰テスト
修正で過去に動いていた機能が壊れていないかを確かめるテスト。すべての不具合を見つけられる保証ではなく、用意した条件の範囲で異常を検知します。

(リファクタリングの意味については『Martin FowlerによるRefactoringの解説』をご参照ください)

練習する機能と、変えてはいけない約束

架空の学習メモを考えます。1件のデータには「学習時間」と「完了したかどうか」が入っています。`done`が真偽値の`true`になっている記録だけを数え、その時間を合計します。ここでは`minutes`が0以上の数値で渡されることを前提にします。

入力の状態 数える件数 合計時間
25分と15分が完了、50分は未完了 2件 40分
未完了だけ 0件 0分
記録がない 0件 0分
完了した記録の時間が0 1件 0分
`done`が文字列の`true` 対象外 対象外

この約束を後から変更するなら、期待結果も見直す必要があります。AIが気を利かせて「`done`が文字列でも完了と扱う」よう変更すると、今回は元の仕様から外れます。

準備:Node.jsで動く小さな関数を保存する

Node.jsはJavaScriptをブラウザ以外でも実行できる環境です。Node.js 22以降を用意し、ターミナルで`node –version`を実行して確認してください。仕事用のファイルを直接書き換えず、空の練習フォルダに`study-summary.cjs`を作ります。

function summarizeStudy(records) {
  let minutes = 0;
  let completed = 0;
  for (const record of records) {
    if (record.done === true) {
      completed += 1;
      minutes += record.minutes;
    }
  }
  return { completed, minutes };
}
module.exports = { summarizeStudy };

この処理は配列を先頭から読み、`done === true`のときだけ件数と時間を増やします。戻り値は`{ completed, minutes }`という2つの数字を持つオブジェクトです。

テストを書く:最初に期待する答えを固定する

同じフォルダに`study-summary.test.cjs`を作り、次のコードを保存します。`node:test`はNode.jsに組み込まれたテスト機能で、`node:assert/strict`は実際の値と期待値が一致するか確かめるための機能です。別途`npm install`する必要はありません。

const test = require('node:test');
const assert = require('node:assert/strict');
const { summarizeStudy } = require('./study-summary.cjs');

test('完了済みだけを合計する', () => {
  const records = [
    { done: true, minutes: 25 },
    { done: false, minutes: 50 },
    { done: true, minutes: 15 },
  ];
  assert.deepStrictEqual(summarizeStudy(records), { completed: 2, minutes: 40 });
});

test('すべて未完了なら0件・0分', () => {
  assert.deepStrictEqual(summarizeStudy([{ done: false, minutes: 20 }]),
    { completed: 0, minutes: 0 });
});

test('空の配列なら0件・0分', () => {
  assert.deepStrictEqual(summarizeStudy([]), { completed: 0, minutes: 0 });
});

test('完了0分も1件として数える', () => {
  assert.deepStrictEqual(summarizeStudy([{ done: true, minutes: 0 }]),
    { completed: 1, minutes: 0 });
});

test('文字列のdoneは完了とみなさない', () => {
  assert.deepStrictEqual(summarizeStudy([
    { done: 'true', minutes: 99 }, { done: true, minutes: 10 },
  ]), { completed: 1, minutes: 10 });
});

ターミナルで次を実行します。テスト対象とテストファイルは同じフォルダに置いてください。

node --test study-summary.test.cjs

このサンプルでは5件のテストがPASSすることを確認済みです。画面上の表示形式はNode.jsのバージョンで多少異なります。重要なのは、失敗したテストがないことと、対象の入力・期待結果を自分で読めることです。

(テストランナーの構文については『Node.js公式Test runner』をご参照ください)

内部を書き換え、同じテストをもう一度動かす

`study-summary.cjs`の内容だけを、次のコードに置き換えます。完了済みの記録を`filter`で選び、`reduce`で分数を合計する実装に変更しました。テストファイルと期待結果は変えません。

function summarizeStudy(records) {
  const finished = records.filter((record) => record.done === true);
  return {
    completed: finished.length,
    minutes: finished.reduce((total, record) => total + record.minutes, 0),
  };
}
module.exports = { summarizeStudy };

`reduce`の最後にある`0`は合計の初期値です。これがあるため、完了した記録が0件でも合計を0として返せます。ここを省くと空の配列に対する処理で失敗するため、見た目だけで削除しないでください。

node --test study-summary.test.cjs

この書き換え後も、用意した5件のテストがPASSすることを実行確認しました。これは「今回の5条件で結果が一致した」という証拠であり、あらゆる入力の動作を保証するものではありません。

AIの変更が原因でテストが落ちたらどうする?

例えば、`record.done === true`を`record.done`に変えると、真偽値ではない`"true"`という文字列も完了として数えられることがあります。見た目はわずかな違いでも、集計件数が変わります。

const finished = records.filter((record) => record.done);

本例の「文字列のdoneは完了とみなさない」というテストは、そのような変更を検出します。意図的にこの誤った条件にした際、5件中1件がFAILすることも実行確認しました。

  1. まず失敗したテスト名と期待値・実際の値を読む。
  2. どの条件を変えたか、該当するコード行を見つける。
  3. 仕様変更の依頼がなければ、もとの意味を保つよう修正して再実行する。

仕様そのものを変える必要がある場合は、変更理由と新しい期待結果を別に検討します。「テストが落ちたから期待値を書き換える」だけでは、問題の見落としにつながります。

通った後に見ることと、テストだけでは分からないこと

  • 同じ入力で同じ出力になったか。
  • 入力配列を途中で書き換えていないか(この例の契約では入力は読み取り専用)。
  • 既存の画面やAPIから呼び出した場合も問題ないか。
  • 意図しないログ出力や性能劣化がないか。

テストを実施する前に期待値が誤っていた場合、PASSでも正しさは証明されません。今回のコードは入力の型チェックや巨大なデータの性能まで検証していません。そこは実際に必要な仕様が明確になった段階で、別のテストを追加してください。

(一般的なテスト設計の考え方については『Route Zeroの記事:テストケース作成の基本』をご参照ください)

よくある疑問:最小限のテストでも意味はある?

テストを5件通せば、AIの変更は安全ですか?

今回の5条件に対する正しさを確かめたことになります。別の値や利用環境で問題が起きない保証はないため、実際に使う条件に応じてテストを増やします。

AIに修正を依頼するより先にテストを書く必要がありますか?

必ずしも絶対条件ではありませんが、変更前に期待値を固定すると、何を守るべきかを共有しやすくなります。実際の仕様と元の動作を混同しないことが重要です。

次に試すこと:学習記録の表示に広げる

まず自分でテストの「25分」を別の値に変更し、期待値もそれに合わせてみましょう。次に「未完了の記録が1件混ざる」パターンを追加すると、1つの関数を改修する際の確認が習慣になります。

AIの提案を採用する基準は、コードの短さではなく必要な動作が説明でき、確かめられることです。テストが通った小さな改修から始めると、何が変わったのかを追いやすくなります。

ルートゼロの採用情報を見る

RANKINGranking-icon

すべてを開示している会社です

ポジティブな面もネガティブな面も、包み隠さず誠実にお答えします。
気になることがあれば、面談の際にすべてオープンにお話します。