偽装、モック、スタブの違いを例を用いたコードで解説

2024-10-01

偽装、モック、スタブの日本語解説

プログラミングにおける単体テスト、モック、言語非依存の文脈において、「偽装 (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



ビットシフト演算子の具体的なコード例と解説

ビットシフト演算子とは、プログラミングにおいて、整数値のビットパターンを左または右にシフトする操作を行う演算子です。この操作は、特定のビットを抽出したり、値を効率的に乗除算したりするために使用されます。ビットシフト演算子の種類:左シフト演算子 (<<):オペランドを指定されたビット数だけ左にシフトします。左にシフトされたビットは0で埋められます。これは、元の値を2の指定されたべき乗で乗算する効果があります。例: x << 2 は、x を 4 倍します。...


フレームワークとライブラリの比較:コード例による解説

フレームワークとライブラリは、プログラミングにおいてよく使われる用語ですが、その違いは明確ではありません。ここでは、日本のプログラミングコミュニティで一般的な理解に基づいて、両者を比較してみます。定義: プログラムの骨格を提供するソフトウェア部品の集合体です。アプリケーション開発の基礎となる構造やルールを定め、開発者があらかじめ決められた手順に従ってプログラムを構築することができます。...


メソッドと関数の違いを理解するための代替的な説明方法

**OOP(オブジェクト指向プログラミング)**の文脈で、言語に依存しない用語として、「メソッド」と「関数」の違いを説明します。オブジェクトに属する手続きです。オブジェクトの内部状態にアクセスまたは変更することができます。オブジェクトの振る舞いを定義します。...


引数とパラメーターの違いを理解するための代替的な説明方法

プログラミングにおける「引数」と「パラメーター」の違いを、日本語で説明します。パラメーター (parameter):関数やメソッドが受け取る値の変数名です。関数の定義時に指定されます。関数の入力を定義します。パラメーター (parameter):...


正規表現によるURL検証

正規表現は、文字列のパターンをマッチさせるための強力なツールです。有効なURLを検出するための正規表現は、言語に依存しない一般的なパターンを使用することができます。URLの一般的な構成要素は、プロトコル(http、https)、ホスト名、パス、クエリパラメータ、アンカー(#)などで構成されています。...



unit testing mocking language agnostic

ラムダ関数以外の関数定義方法 (日本語解説)

ラムダ関数 (lambda function) は、無名関数 (anonymous function) とも呼ばれ、名前を付けずに定義される関数のひとつです。この関数は、主に関数型プログラミングで広く使用されていますが、多くのプログラミング言語でもサポートされています。


Tail Recursion in Japanese: 末尾再帰

末尾再帰 (matebi saiki) は、プログラミングにおける再帰関数の特殊なケースです。再帰関数とは、自身が呼び出しの中で自分自身を呼び出す関数のことで、末尾再帰では、関数の最後の操作が自身への再帰呼び出しであることが特徴です。末尾再帰は、関数呼び出しスタックのオーバーフローを防ぐことができるため、大きなデータセットを処理する際に効率的です。これは、再帰呼び出しが関数の最後の操作であるため、関数の戻り値がそのまま再帰呼び出しの結果として返されるからです。


「継承よりも合成を優先する」の日本語解説

**「継承よりも合成を優先する」**という原則は、オブジェクト指向プログラミングにおいて、継承よりも合成を使用することを推奨する設計原則です。定義: あるクラスが別のクラスから特性やメソッドを継承し、そのクラスのサブクラスになる関係。利点: コードの再利用が可能になり、共通の機能を簡単に実装できる。


プログラミングにおける「お気に入りのプログラマー漫画」という質問への代替的なアプローチ

解説:「プログラミング言語に依存しない」: この部分は、特定のプログラミング言語に特化していないという意味です。つまり、どの言語を使っているかによらず、プログラマーの一般的な体験や思考をテーマにした漫画を指します。例文:「どの言語を使っているプログラマーでも楽しめる漫画はありますか?」


依存性注入 (Dependency Injection) の日本語解説

依存性注入 (Dependency Injection) とは、プログラミングにおける設計パターンの一つで、オブジェクトの依存関係を外部から注入することによって、コードの柔軟性とテスト可能性を高める手法です。依存関係: オブジェクトが他のオブジェクトの機能に依存している状態。