tanaka101

テストを書く

Vitestでバリデーション関数とAPIエンドポイントのテストを書きます。

このレッスンで学ぶこと

テスト を書いて、コードが正しく動くことを自動で検証する方法を学びます。

今までは curl で手動テストしていましたが、機能が増えるにつれて毎回全てを手動で確認するのは現実的ではありません。テストを書いておけば、コマンド1つで全ての検証を自動実行できます。

なぜテストを書くのか

メリット説明
バグの早期発見コードを変更したときに、既存の機能が壊れていないかすぐに分かる
検証を自動化できる一度書けば何度でもコマンド1つで繰り返し実行できる

テストは「正しさの証明」ではありません

テストが全て通っていても、テストケース自体が間違っていればバグは見逃されます。 テストが保証するのは「テストが想定した範囲で、期待通りに動いている」ということだけです。

Step 1: Vitestをインストールする

テストフレームワークとして Vitest を使います。

npm install -D vitest

Vitest は高速で使いやすいテストフレームワークです。設定がほぼ不要で、TypeScriptもそのまま使えます。

Step 2: テストスクリプトを追加する

package.jsonscripts にテスト用のコマンドを追加します。

"scripts": {
  // 省略
  "test": "vitest run",
  "test:watch": "vitest"
}
コマンド説明
npm testテストを1回実行する
npm run test:watchファイル変更時に自動でテストを再実行する

Step 3: 最初のテストを書く

バリデーションのテストから始めましょう。

テストファイルは validation.test.ts のように、テスト対象のファイル名に .test.ts を付けて作成します。Vitestはデフォルトで .test.ts.spec.ts を含むファイルをテストファイルとして自動検出します。

src/validation.test.ts を作成してください。

src/validation.test.ts
import { describe, it, expect } from 'vitest';
import { isValidDate, isValidTime, isClockOutAfterClockIn } from './validation.js';
 
describe('isValidDate', () => {
  it('正しい日付形式を受け付ける', () => {
    expect(isValidDate('2026-01-28')).toBe(true);
    expect(isValidDate('2026-12-31')).toBe(true);
  });
 
  it('不正な形式を拒否する', () => {
    expect(isValidDate('abc')).toBe(false);
    expect(isValidDate('2026/01/28')).toBe(false);
    expect(isValidDate('28-01-2026')).toBe(false);
  });
 
  it('存在しない日付を拒否する', () => {
    expect(isValidDate('2026-13-01')).toBe(false);
    expect(isValidDate('2026-00-01')).toBe(false);
  });
});
 
describe('isValidTime', () => {
  it('正しい時刻形式を受け付ける', () => {
    expect(isValidTime('09:00')).toBe(true);
    expect(isValidTime('23:59')).toBe(true);
    expect(isValidTime('00:00')).toBe(true);
  });
 
  it('不正な形式を拒否する', () => {
    expect(isValidTime('abc')).toBe(false);
    expect(isValidTime('9:00')).toBe(false);
    expect(isValidTime('25:00')).toBe(false);
    expect(isValidTime('12:60')).toBe(false);
  });
});
 
describe('isClockOutAfterClockIn', () => {
  it('退勤が出勤より後ならtrueを返す', () => {
    expect(isClockOutAfterClockIn('09:00', '18:00')).toBe(true);
  });
 
  it('退勤が出勤より前ならfalseを返す', () => {
    expect(isClockOutAfterClockIn('09:00', '08:00')).toBe(false);
  });
 
  it('同じ時刻ならfalseを返す', () => {
    expect(isClockOutAfterClockIn('09:00', '09:00')).toBe(false);
  });
});

テストの構造

describe('isValidDate', () => {    // テストグループの名前
  it('正しい日付形式を受け付ける', () => {  // 個別のテストケース
    expect(isValidDate('2026-01-28')).toBe(true);  // 期待する結果
  });
});
役割
describe()テストをグループにまとめる
it()1つのテストケースを定義する
expect()テスト対象の値を指定する
.toBe()期待する結果と一致するか検証する

it() の第1引数には「何をテストしているか」を日本語で書くと読みやすくなります。テストが失敗したときに、どのケースが失敗したのか一目で分かります。

Step 4: テストを実行する

npm test

全てのテストが通れば、以下のような出力が表示されます。

テストの実行結果

全てグリーン(合格)です。

※簡易実装なので合格にならないパターンはいくらかあります

Step 5: テストを失敗させてみる

テストの価値を体感するために、わざとバリデーションを壊してみましょう。

src/validation.tsisValidTime を以下のように変更します。

export function isValidTime(time: string): boolean {
  return true; // 常にtrueを返す(壊れた状態)
}

テストを実行すると:

npm test
テストの実行結果(失敗)

テストが失敗し、どこが壊れたか一目で分かります。

確認したら、isValidTime を元に戻してください。

Step 6: チャレンジ - データ操作のテストを書く

余裕がある方は、src/record-repository.ts のテストも書いてみましょう。

ヒント: どんなテストを書くか
- getRecords() が配列を返すこと
- getRecordById() で存在するIDを指定すると記録が返ること
- getRecordById() で存在しないIDを指定すると undefined が返ること
- addRecord() で記録を追加すると、getRecords() の結果に含まれること
- deleteRecord() で存在するIDを指定すると true が返ること
- deleteRecord() で存在しないIDを指定すると false が返ること
答え: src/record-repository.test.ts
import { describe, it, expect } from 'vitest';
import { getRecords, getRecordById, addRecord, deleteRecord } from './record-repository.js';
import { AttendanceRecord } from './types.js';
 
describe('getRecords', () => {
  it('配列を返す', () => {
    const records = getRecords();
    expect(Array.isArray(records)).toBe(true);
  });
 
  it('初期データが含まれている', () => {
    const records = getRecords();
    expect(records.length).toBeGreaterThan(0);
  });
});
 
describe('getRecordById', () => {
  it('存在するIDで記録を取得できる', () => {
    const record = getRecordById('1');
    expect(record).toBeDefined();
    expect(record?.id).toBe('1');
  });
 
  it('存在しないIDでundefinedを返す', () => {
    const record = getRecordById('999');
    expect(record).toBeUndefined();
  });
});
 
describe('addRecord', () => {
  it('記録を追加できる', () => {
    const newRecord: AttendanceRecord = {
      id: '100',
      employeeName: 'テストユーザー',
      date: '2026-01-28',
      clockIn: '09:00',
      clockOut: null,
      note: null,
    };
 
    addRecord(newRecord);
 
    const found = getRecordById('100');
    expect(found).toBeDefined();
    expect(found?.employeeName).toBe('テストユーザー');
  });
});
 
describe('deleteRecord', () => {
  it('存在するIDを削除するとtrueを返す', () => {
    const result = deleteRecord('100');
    expect(result).toBe(true);
  });
 
  it('存在しないIDを削除するとfalseを返す', () => {
    const result = deleteRecord('999');
    expect(result).toBe(false);
  });
});

現在のフォルダ構造

      • data.ts
      • record-repository.test.ts
      • index.ts
      • record-repository.ts
      • types.ts
      • validation.ts
      • validation.test.ts← 作成するファイル
    • package.json
    • package-lock.json
    • tsconfig.json

次のステップ

テストを書いて、コードの品質を自動で検証する方法を学びました。 あらゆるシステム開発でテストは必ずと言っていいほど実施されます。 ごとに文法が異なる場合もありますが、基本的な考え方は共通です。

次のステップでは、このコースで学んだことを振り返ります。