概要
テスト可能性は設計品質の指標である。
ユニットテストは、コードが“正しい”ことを確認するための行為ではなく、コードが“明確な責任を持っている”ことを保証するための構造設計である。
本稿では、以下の観点からユニットテストとその周辺の設計を体系的に解説する:
- ユニットテストの責務と粒度
- テストしやすい構造とは何か
- 副作用の排除と依存の注入
- Jestによるテスト記述パターン
- 命名とテスト分類の指針
- テストが設計に与える影響
1. ユニットテストとは何か?
- ✅ 関数・モジュール・クラスなど、“単位(Unit)”に閉じたテスト
- ✅ 外部の状態に依存せず、純粋な入力 → 出力を検証
- ✅ 副作用(DOM操作・通信・ストレージ)を持たない or モックする
2. テストしやすいコードとは?
// ❌ テスト困難:副作用が内包
function saveUser(user) {
localStorage.setItem('user', JSON.stringify(user));
}
// ✅ テスト可能:依存注入
function saveUser(user, storage = localStorage) {
storage.setItem('user', JSON.stringify(user));
}
→ ✅ 外部依存は引数・コンテキストで注入
→ ✅ 単体で実行できるコードをユニットにする
3. Jestの基本構文
describe('isValidEmail', () => {
test('正しい形式のメールを通す', () => {
expect(isValidEmail('test@example.com')).toBe(true);
});
test('不正なメールを弾く', () => {
expect(isValidEmail('not-email')).toBe(false);
});
});
- ✅
describe()
で関数・機能単位のグループ化 - ✅
test()
orit()
でシナリオ記述 - ✅
expect()
+ マッチャー で検証 (toBe
,toEqual
,toThrow
など)
4. テスト命名と分類の指針
✅ テストは入力 → 条件 → 期待結果を表現すべき
test('数値が偶数のときtrueを返す', () => {
expect(isEven(4)).toBe(true);
});
- ユースケースベースが望ましい
- 英語圏では “should X when Y” 形式が主流
5. モックとスタブの設計
const mockStorage = {
setItem: jest.fn(),
getItem: jest.fn()
};
saveUser({ name: 'Toto' }, mockStorage);
expect(mockStorage.setItem).toHaveBeenCalledWith('user', '{"name":"Toto"}');
- ✅ 副作用はモック関数で隔離
- ✅ jest.fn() により呼び出し検証が可能
6. 失敗するテストの価値
- ✅ テストが失敗したとき「何が、なぜ、どの入力で失敗したのか」が即座にわかるように
- ✅ 曖昧な期待値やブラックボックス的なテストはテスト自体が信用されなくなる
7. ユニットテストと設計の相互作用
- ❌ 「後からテストを書く」ではなく
- ✅ 「テストを書けるように設計する」
// 非テスト可能な構造
export const config = {
endpoint: '/api/user',
retry: true
};
// ✅ テスト可能にする
export function getConfig(env) {
return env === 'prod'
? { endpoint: '/api/user', retry: true }
: { endpoint: '/mock/user', retry: false };
}
設計判断フロー
① 関数は入出力が純粋? → ユニットテスト可能
② 外部依存を持つ? → モック or 注入できる構造へ
③ どのレベルで責務を分けている? → describeで設計反映
④ テストの粒度は明確か? → 複数の“期待結果”は分割する
⑤ 期待結果が曖昧でないか? → テスト自体に明確な意味を持たせる
よくあるミスと対策
❌ 1つのテストで複数の期待結果
→ ✅ テストは1ケース1アサーションが理想
❌ 内部実装に依存したテスト
→ ✅ 入出力の仕様に基づくテスト設計を
❌ テスト名が曖昧
→ ✅ 「〜のとき、〜を返す」「〜を実行したら、〜が呼ばれる」形式を徹底
結語
ユニットテストは“バグを防ぐ”ためのものではない。
それは、“設計を確認可能な構造に変える”ための道具である。
- テストは仕様の写像であり、
- テストしやすい構造は責任が明確であり、
- テストが壊れないコードは設計が安定している
良いテストは、良いコードを生む。
そして、良い設計は“テストが自然に書ける構造”として現れる。