偽装、モック、スタブの違いを例を用いたコードで解説
偽装、モック、スタブの日本語解説
プログラミングにおける単体テスト、モック、言語非依存の文脈において、「偽装 (faking)」、「モック (mocking)」、「スタブ (stubbing)」の違いについて解説します。
偽装 (Faking)
- 特徴:
- テスト対象の依存関係を単純化し、テストコードの複雑さを軽減する。
- 実際の挙動を完全に置き換えるため、テストの信頼性が高まる。
- 例:
- テスト対象が外部APIに依存している場合、そのAPIを偽装して、常に特定のレスポンスを返すようにする。
- テスト対象がデータベースにアクセスする場合、データベースへのアクセスを偽装し、インメモリデータベースを使用する。
モック (Mocking)
- 特徴:
偽装、モック、スタブの違いを例を用いたコードで解説
先ほどの解説に続き、具体的なコード例を用いて、偽装、モック、スタブの違いをより深く理解しましょう。ここでは、JavaScriptのテストフレームワークであるJestを例に説明します。
偽装は、外部システムや複雑なロジックをシンプルなオブジェクトに置き換えることで、テスト対象のコードを孤立させます。
// 実際のコード (Userクラス)
class User {
async fetchUserData(userId) {
const response = await fetch(`https://api.example.com/users/${userId}`);
return response.json();
}
}
// テストコード (Userクラスのテスト)
jest.mock('./User'); // Userモジュールをモック化
const User = require('./User');
test('fetchUserDataが呼び出されたときに、期待するデータが返される', async () => {
// fetchをグローバルにモック化
global.fetch = jest.fn().mockResolvedValueOnce({
json: jest.fn().mockResolvedValue({
id: 1,
name: 'John Doe',
}),
});
const user = new User();
const data = await user.fetchUserData(1);
expect(data).toEqual({ id: 1, name: 'John Doe' });
});
この例では、fetch
関数全体をモック化し、常に特定のJSONデータを返すようにしています。これにより、実際のAPI呼び出しをせずに、fetchUserData
メソッドの動作をテストすることができます。
モックは、特定のオブジェクトの振る舞いを期待通りに設定し、テスト対象との相互作用を検証します。
// 実際のコード (Calculatorクラス)
class Calculator {
add(a, b) {
return a + b;
}
}
// テストコード (Calculatorクラスのテスト)
const Calculator = require('./Calculator');
const { jest } = require('@jest/globals');
jest.mock('./Calculator');
test('addメソッドが正しく計算される', () => {
const calculator = new Calculator();
const spy = jest.spyOn(calculator, 'add');
const result = calculator.add(2, 3);
expect(result).toBe(5);
expect(spy).toHaveBeenCalledWith(2, 3);
});
この例では、add
メソッドが期待通りの引数で呼び出され、期待通りの結果を返すかを検証しています。jest.spyOn
でメソッドをスパイし、toHaveBeenCalledWith
で呼び出し状況を確認しています。
スタブ (Stubbing)
スタブは、モックと似ていますが、より単純な振る舞いを定義します。通常、メソッドの戻り値を固定したり、特定の例外をスローさせたりします。
// 実際のコード (UserServiceクラス)
class UserService {
getUserById(id) {
// 実際にはデータベースからユーザーを取得する
return { id, name: 'John Doe' };
}
}
// テストコード (UserServiceクラスのテスト)
const UserService = require('./UserService');
const { jest } = require('@jest/globals');
jest.mock('./UserService');
test('getUserByIdが呼び出されたときに、特定のユーザーが返される', () => {
const userService = new UserService();
userService.getUserById.mockReturnValueOnce({ id: 1, name: 'Jane Doe' });
const user = userService.getUserById(1);
expect(user).toEqual({ id: 1, name: 'Jane Doe' });
});
この例では、getUserById
メソッドが常に特定のユーザーを返すようにスタブを設定しています。データベースへの実際のアクセスを模倣する必要がないため、テストが簡潔になります。
- 偽装: 外部システムや複雑なロジック全体を置き換える
- モック: 特定のオブジェクトの振る舞いを期待通りに設定し、検証する
- スタブ: メソッドの戻り値を固定したり、例外をスローさせたりする
どの手法を使うかは、テストシナリオによって異なります。
- 外部システムとの連携をテストしたい場合: 偽装
- オブジェクト間の相互作用を検証したい場合: モック
- 単純な振る舞いを模倣したい場合: スタブ
これらの手法を適切に組み合わせることで、より効果的な単体テストを作成することができます。
- 上記の例はJestを用いたJavaScriptの例ですが、他の言語やテストフレームワークでも同様の概念が適用できます。
- モックやスタブの作成には、専用のライブラリ(例えば、Jestの
jest.fn
など)を利用することが一般的です。 - テストコードは、変更しやすいコードであるべきです。過度にモックやスタブに依存しすぎると、テストコードの保守性が低下する可能性があります。
より詳しく知りたい場合は、以下のキーワードで検索してみてください。
- 単体テスト
- モック
- スタブ
- テストダブル
- Jest
偽装、モック、スタブ以外の代替手法
偽装、モック、スタブは、単体テストでよく用いられる手法ですが、これ以外にもテストを支援する様々な手法が存在します。ここでは、それらについて解説します。
スパイ (Spy)
- 特徴:
- メソッドが何回呼び出されたか、どのような引数で呼び出されたかなどを記録します。
- モックと似ていますが、期待する振る舞いを設定するのではなく、実際の呼び出し状況を記録します。
- 利用シーン:
- メソッドが何回呼び出されたかを確認したい場合
- 特定の引数で呼び出されたかを確認したい場合
フェイク (Fake)
- 特徴:
- モックよりも詳細な実装を持つことがあります。
- 複雑なロジックを持つオブジェクトを、テストしやすいシンプルな実装に置き換えることができます。
- 利用シーン:
- 複雑なロジックを持つオブジェクトをテストしたい場合
- データベースへのアクセスをテストしたい場合(インメモリデータベースなどを使用)
ダミー (Dummy)
- 特徴:
- 利用シーン:
パッチ (Patch)
- 特徴:
- モックと似ていますが、より広範囲なコードの振る舞いを変更できます。
- テスト対象のコードだけでなく、外部ライブラリの関数などもパッチすることができます。
- 利用シーン:
- 外部ライブラリの関数の挙動を制御したい場合
- グローバル変数の値を一時的に変更したい場合
依存性注入 (Dependency Injection)
- 特徴:
- 利用シーン:
テストダブル
- 特徴:
どの手法を選ぶべきか?
どの手法を選ぶかは、テストしたい部分、テストの目的、そしてテスト対象の複雑さによって異なります。
- シンプルな振る舞い: スタブ
- 呼び出し回数や引数を検証: スパイ
- 複雑なロジック: フェイク
- インタフェースのみが必要: ダミー
- 外部ライブラリの振る舞いを制御: パッチ
- 依存関係の管理: 依存性注入
一般的に、以下のような組み合わせが考えられます。
- 単体テスト: モック、スタブ、スパイ
- 統合テスト: フェイク、パッチ
- 大規模なシステム: 依存性注入
重要なポイント
- テストコードは、テスト対象のコードと同じくらい重要です。
- テストコードは、読みやすく、保守しやすいように記述しましょう。
- テストカバレッジを意識して、できるだけ多くのコードをテストしましょう。
偽装、モック、スタブ以外にも、テストを支援する様々な手法が存在します。これらの手法を適切に使い分けることで、より効果的な単体テストを作成することができます。
- テスト駆動開発
- TDD
- テストカバレッジ
- 依存性注入
unit-testing mocking language-agnostic