Day 1: Jestの世界へようこそ
今日学ぶこと
- テストとは何か、なぜ必要なのか
- テストピラミッドの概念
- Jestとは何か、その特徴と利点
- Jestのインストールとセットアップ
- 最初のテストを書いて実行する
なぜテストが必要なのか
コードを書いたら、ブラウザで動かして確認する。ボタンを押して、フォームに入力して、結果を目で確認する——多くの開発者がこの「手動テスト」を繰り返しています。
しかし、この方法には限界があります。
flowchart LR
subgraph Manual["手動テスト"]
M1["コードを書く"]
M2["ブラウザで確認"]
M3["別の機能も確認"]
M4["見落としが発生"]
end
subgraph Auto["自動テスト"]
A1["コードを書く"]
A2["テストを実行"]
A3["全機能を自動チェック"]
A4["問題を即座に検出"]
end
M1 --> M2 --> M3 --> M4
A1 --> A2 --> A3 --> A4
style Manual fill:#ef4444,color:#fff
style Auto fill:#22c55e,color:#fff
| 手動テストの問題 | 自動テストによる解決 |
|---|---|
| 毎回同じ操作を繰り返す必要がある | テストコマンド一発で全チェック |
| 確認漏れが起きやすい | すべてのテストケースを網羅的に実行 |
| コード変更のたびに全体を確認する負担 | 変更が既存機能を壊していないか自動検証 |
| チームメンバーによって確認手順が異なる | テストコードが確認手順の「仕様書」になる |
「10日で覚えるJavaScript」や「10日で覚えるReact」で書いたコードを思い出してください。機能を追加するたびに、以前の機能が壊れていないか心配になりませんでしたか?自動テストはその不安を解消してくれます。
テストピラミッド
ソフトウェアテストには複数のレベルがあり、それぞれ異なる目的を持っています。これを体系的に表したのがテストピラミッドです。
flowchart TB
subgraph Pyramid["テストピラミッド"]
E2E["E2Eテスト\n(少数・遅い・高コスト)"]
INT["インテグレーションテスト\n(中程度)"]
UNIT["ユニットテスト\n(多数・速い・低コスト)"]
end
E2E --> INT --> UNIT
style E2E fill:#ef4444,color:#fff
style INT fill:#f59e0b,color:#fff
style UNIT fill:#22c55e,color:#fff
| テストの種類 | 対象 | 速度 | 例 |
|---|---|---|---|
| ユニットテスト | 関数・コンポーネント単体 | 非常に速い | add(2, 3) が 5 を返すか |
| インテグレーションテスト | 複数モジュールの連携 | 中程度 | APIからデータを取得して表示できるか |
| E2Eテスト | アプリケーション全体 | 遅い | ユーザーがログインして商品を購入できるか |
ピラミッドの原則
- ユニットテストを最も多く書く:高速で安定しており、問題の原因を特定しやすい
- E2Eテストは重要なフローに絞る:実行が遅く、メンテナンスコストが高い
- 各層が補完し合う:ユニットテストだけでは見つからない問題をインテグレーションテストやE2Eテストが検出する
このシリーズとの関連: 「10日で覚えるCypress」「10日で覚えるPlaywright」ではE2Eテストを学びました。この本で学ぶJestは、ピラミッドの土台となるユニットテストとインテグレーションテストを担当するツールです。
Jestとは
Jestは、Meta(旧Facebook)が開発したJavaScriptテスティングフレームワークです。React、Vue、Angular、Node.jsなど、あらゆるJavaScriptプロジェクトで使用できます。
Jestの特徴
flowchart TB
subgraph Jest["Jestの主要機能"]
F1["ゼロ設定\nZero Config"]
F2["高速実行\nParallel Testing"]
F3["モック機能\nBuilt-in Mocking"]
F4["スナップショット\nSnapshot Testing"]
F5["カバレッジ\nCode Coverage"]
F6["ウォッチモード\nWatch Mode"]
end
style Jest fill:#99425b,color:#fff
| 特徴 | 説明 |
|---|---|
| ゼロ設定 | インストールしてすぐ使える。複雑な設定ファイルは不要 |
| 高速実行 | テストファイルを並列実行し、変更されたファイルのみ再テスト |
| オールインワン | テストランナー、アサーション、モックが全て組み込まれている |
| スナップショットテスト | UIの出力を記録し、意図しない変更を検出 |
| コードカバレッジ | 追加ツールなしでカバレッジレポートを生成 |
| ウォッチモード | ファイル変更を監視して関連テストを自動再実行 |
なぜJestを選ぶのか
JavaScriptのテスティングフレームワークは他にもあります(Mocha、Vitest、Jasmineなど)。Jestが広く使われている理由を見てみましょう。
| フレームワーク | 特徴 | Jestとの違い |
|---|---|---|
| Mocha | 柔軟性が高い | アサーションやモックは別途インストールが必要 |
| Vitest | Viteベースで高速 | Viteプロジェクト向け。APIはJest互換 |
| Jasmine | Jestの元になったフレームワーク | 機能がJestより少ない |
Jestは**バッテリー同梱(batteries included)**の思想で設計されています。テストランナー、アサーションライブラリ、モックライブラリ、カバレッジツールがすべて一つのパッケージに含まれているため、初心者でも迷わずに始められます。
Jestのインストールとセットアップ
前提条件
- Node.js 18以上がインストールされていること
- npm が使えること
「10日で覚えるJavaScript」のDay 1でNode.jsをインストール済みであれば準備完了です。
プロジェクトの作成
# プロジェクトディレクトリを作成
mkdir my-jest-project
cd my-jest-project
# package.json を初期化
npm init -y
# Jestをインストール
npm install --save-dev jest
package.json の設定
package.json の scripts にテスト実行コマンドを追加します。
{
"name": "my-jest-project",
"version": "1.0.0",
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"devDependencies": {
"jest": "^30.0.0"
}
}
TypeScript環境のセットアップ
TypeScriptを使う場合は、追加のパッケージが必要です。
# TypeScript関連パッケージをインストール
npm install --save-dev typescript ts-jest @types/jest
# TypeScript設定ファイルを生成
npx tsc --init
Jest設定ファイル(jest.config.js)を作成します。
/** @type {import('jest').Config} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
JavaScript版の場合は
jest.config.jsは不要です。Jestはデフォルトで.test.jsや.spec.jsファイルを自動検出します。
最初のテストを書こう
テスト対象の関数を作成
まず、テストする関数を作りましょう。math.js というファイルを作成します。
// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
module.exports = { add, subtract, multiply, divide };
TypeScript版:
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
export function divide(a: number, b: number): number {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
テストファイルを作成
次に、テストファイルを作成します。Jestは以下のパターンのファイルを自動的にテストとして認識します。
| パターン | 例 |
|---|---|
*.test.js / *.test.ts |
math.test.js |
*.spec.js / *.spec.ts |
math.spec.js |
__tests__/ ディレクトリ内のファイル |
__tests__/math.js |
math.test.js を作成しましょう。
// math.test.js
const { add, subtract, multiply, divide } = require('./math');
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
test('subtracts 5 - 3 to equal 2', () => {
expect(subtract(5, 3)).toBe(2);
});
test('multiplies 3 * 4 to equal 12', () => {
expect(multiply(3, 4)).toBe(12);
});
test('divides 10 / 2 to equal 5', () => {
expect(divide(10, 2)).toBe(5);
});
test('throws error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
TypeScript版:
// math.test.ts
import { add, subtract, multiply, divide } from './math';
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
test('subtracts 5 - 3 to equal 2', () => {
expect(subtract(5, 3)).toBe(2);
});
test('multiplies 3 * 4 to equal 12', () => {
expect(multiply(3, 4)).toBe(12);
});
test('divides 10 / 2 to equal 5', () => {
expect(divide(10, 2)).toBe(5);
});
test('throws error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
テストを実行
npm test
実行結果:
PASS ./math.test.js
✓ adds 1 + 2 to equal 3 (2 ms)
✓ subtracts 5 - 3 to equal 2
✓ multiplies 3 * 4 to equal 12
✓ divides 10 / 2 to equal 5
✓ throws error when dividing by zero (1 ms)
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 0.5 s
すべてのテストが緑色(PASS)で表示されれば成功です。
テストコードの解説
最初のテストコードを詳しく見てみましょう。
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
flowchart LR
subgraph TestAnatomy["テストの構造"]
TEST["test(name, fn)\nテスト関数"]
EXPECT["expect(value)\n実際の値"]
MATCHER["toBe(expected)\nマッチャー"]
end
TEST --> EXPECT --> MATCHER
style TEST fill:#3b82f6,color:#fff
style EXPECT fill:#8b5cf6,color:#fff
style MATCHER fill:#22c55e,color:#fff
| 要素 | 説明 | 例 |
|---|---|---|
test(name, fn) |
テストケースを定義する。name はテストの説明 |
test('adds 1 + 2 to equal 3', ...) |
expect(value) |
テスト対象の値を指定する | expect(add(1, 2)) |
.toBe(expected) |
期待値と一致するか検証する(マッチャー) | .toBe(3) |
テストが失敗するとどうなるか
試しにテストを壊してみましょう。add 関数の実装を変更します。
function add(a, b) {
return a - b; // intentional bug
}
テストを実行すると:
FAIL ./math.test.js
✗ adds 1 + 2 to equal 3 (3 ms)
● adds 1 + 2 to equal 3
expect(received).toBe(expected) // Object.is equality
Expected: 3
Received: -1
1 | const { add } = require('./math');
2 | test('adds 1 + 2 to equal 3', () => {
> 3 | expect(add(1, 2)).toBe(3);
| ^
4 | });
Jestは**何が期待されていたか(Expected: 3)と実際の結果(Received: -1)**を明確に示してくれます。さらに、問題が起きたコードの行をハイライトしてくれるので、バグの原因を素早く特定できます。
ウォッチモード
開発中は、ファイルを保存するたびにテストが自動的に再実行されるウォッチモードが便利です。
npm run test:watch
ウォッチモードでは、以下の操作が可能です。
| キー | 動作 |
|---|---|
a |
すべてのテストを実行 |
f |
失敗したテストのみ再実行 |
p |
ファイル名でフィルタ |
t |
テスト名でフィルタ |
q |
ウォッチモードを終了 |
flowchart TB
subgraph Watch["ウォッチモードの動作"]
EDIT["ファイルを編集・保存"]
DETECT["Jestが変更を検知"]
RUN["関連テストを自動実行"]
RESULT["結果を即座に表示"]
end
EDIT --> DETECT --> RUN --> RESULT --> EDIT
style Watch fill:#3b82f6,color:#fff
プロジェクト構成のベストプラクティス
テストファイルの配置には主に2つのパターンがあります。
パターン1: テストファイルを隣に置く(推奨)
src/
├── math.js
├── math.test.js
├── utils/
│ ├── format.js
│ └── format.test.js
└── components/
├── Button.jsx
└── Button.test.jsx
パターン2: tests ディレクトリにまとめる
src/
├── math.js
├── utils/
│ └── format.js
├── components/
│ └── Button.jsx
└── __tests__/
├── math.test.js
├── format.test.js
└── Button.test.jsx
| パターン | メリット | デメリット |
|---|---|---|
| 隣に置く | テスト対象とテストの対応が明確 | ディレクトリ内のファイル数が増える |
| tests | テストが一箇所にまとまる | テスト対象との対応がわかりにくい |
多くのプロジェクトではパターン1が採用されています。テスト対象のファイルのすぐ隣にテストがあることで、テストの存在に気づきやすく、メンテナンスもしやすくなります。
まとめ
| 概念 | 説明 |
|---|---|
| 自動テスト | コードの動作を自動的に検証する仕組み |
| テストピラミッド | Unit → Integration → E2E の3層構造 |
| Jest | Meta開発のオールインワンテスティングフレームワーク |
test() |
テストケースを定義する関数 |
expect().toBe() |
値が期待通りかを検証する |
| ウォッチモード | ファイル変更時にテストを自動再実行 |
重要ポイント
- 自動テストはコードの品質を守る安全ネット
- Jestはインストールするだけで、設定なしですぐに使える
test()+expect()+ マッチャーがテストの基本構造- ウォッチモードで開発効率を大幅に向上できる
練習問題
問題1: 基本
以下の greet 関数のテストを書いてください。
function greet(name) {
return `Hello, ${name}!`;
}
期待する動作:
greet('Alice')は'Hello, Alice!'を返すgreet('World')は'Hello, World!'を返す
問題2: 応用
以下の isEven 関数のテストを書いてください。複数のケースをテストしましょう。
function isEven(num) {
return num % 2 === 0;
}
チャレンジ問題
以下の clamp 関数のテストを書いてください。境界値(min、max、範囲内)のすべてのケースをカバーしましょう。
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
参考リンク
次回予告: Day 2では「テストの構造と基本パターン」について学びます。describe によるテストのグループ化、beforeEach / afterEach によるセットアップ、AAA(Arrange-Act-Assert)パターンなど、保守しやすいテストを書くための技法をマスターしましょう!