新規開発プロジェクトで自動テストを行っているのですが、「自動テストにおけるモックとはなにか」について理解が今ひとつできなかったので調べてみました。
モックとはなにか、に踏み込むと関連するややこしい内容が出てきますが、この記事ではできるだけ単純化してみました。
言語は Java を想定していますが、特に言語には依存しない内容になっています。
なお、厳密な用語の定義は参考サイトを参考にしてください。用語は人により定義が異なっているのが現実のようですが、xUnit Test Patterns で定義されているものが有力のようです。
テストダブル
モックについて知りたいのに、調べているといきなり「テストダブル」という用語がでてきます。
といってもさほど難しいことはなく、テスト対象クラスが依存する外部クラスのことになります。
自動テストを行う上で問題になるのが、テスト対象クラスが依存している(持っている)外部クラスの機能をどうやってテストするかというものがあります。
そのために外部クラスの機能をテストフレームワークなどを使用して置き換えるのですが、その際、外部クラスの機能を置き換えるものを「テストダブル」と呼びます。
実際の開発では、データベースの更新機能や実際には呼び出せない機能をテストダブルで置き換えることがあります。
テストダブルには、以下の種類があります。
- テストスタブ
- テストスパイ
- モックオブジェクト
- フェイクオブジェクト
- ダミーオブジェクト
テストスタブ
テストスタブは、外部クラスの機能を想定する結果だけを返すように実装したテストダブルです。
具体的には以下のような実装になります。
public class テスト対象クラス { private 外部クラス 外部クラスのインスタンス = new 外部クラス(); public String テスト対象メソッド() { return 外部クラスのインスタンス.外部メソッド(); } } // これがスタブ public class 外部クラス { public String 外部メソッド() { return "こんにちは"; } } @Test public void テストコード() { String 期待値 = "こんにちは"; テスト対象クラス テスト対象クラスのインスタンス = new テスト対象クラス(); assertEquals(期待値, テスト対象クラスのインスタンス.テスト対象メソッド()); }
テストスパイ
テストスパイは、外部クラスの外部メソッドでやり取りされる値を記録し、テストコードから検証可能にするテストダブルです。
具体的には以下のような実装になります。
public class テスト対象クラス { private 外部クラス 外部クラスのインスタンス = new 外部クラス(); public void テスト対象メソッド() { 外部クラスのインスタンス.外部メソッド("こんにちは"); } } public class 外部クラス { // これがテストスパイとして動作するメソッド public String 外部メソッド(String input) { 検証する値 = input; } } @Test public void テストコード() { String 期待値 = "こんにちは"; テスト対象クラス テスト対象クラスのインスタンス = new テスト対象クラス(); テスト対象クラスのインスタンス.テスト対象メソッド(); assertEquals(期待値, 検証する値); //テストスパイとして記録された検証する値と比較 }
モックオブジェクト
モックオブジェクトは、以下の用途で使用するテストダブルです。
- テスト対象クラスが依存する外部クラスの振る舞いを定義します
- テストコードは、テスト対象クラスを実行するとモックオブジェクトからテスト結果を取得し検証できます
- 呼び出されたメソッドの引数の型、回数を検証できます
- モックオブジェクトはスタブを含むことがあります
スタブとモックオブジェクトの違いが議論されることがありますが、単純化すると以下のようにまとめられそうです。
- スタブは期待される結果だけを返す実装
- モックオブジェクトは、スタブを含み、外部クラスの外部メソッドのテスト結果と適切な引数・回数呼び出しているかを検証できる
その他にも、モックオブジェクトはテストフレームワークを使用しないとできないことなども挙げられるかもしれません。
モックに Mockito を用いた実装例は以下のようになります(import文は省略)。
@RunWith(SpringRunner.class) public class DemoApplicationTests { public class テスト対象クラス { private 外部クラス 外部クラスのインスタンス = new 外部クラス(); public String テスト対象メソッド() { return 外部クラスのインスタンス.外部メソッド(); } } public class 外部クラス { public String 外部メソッド() { return "こんにちは"; } } // モック化するクラスのインスタンスを生成します。 @Mock private 外部クラス mock = new 外部クラス(); // モックを注入するクラスのインスタンスを生成します。 @InjectMocks private テスト対象クラス テスト対象クラスのインスタンス = new テスト対象クラス(); // this(mock)を初期化します。 @Before public void setup() { MockitoAnnotations.initMocks(this); } @Test public void テストコード() { String 期待値 = "こんにちは"; // モックオブジェクトの振る舞いを定義 when(mock.外部メソッド()).thenReturn("こんにちは"); // メソッドの実行 String result = テスト対象クラスのインスタンス.テスト対象メソッド(); // 検証 assertEquals(期待値, result); } }
フェイクオブジェクト
フェイクオブジェクトは、テスト実行中に本物と同じように動作するテストダブルです。
ダミーオブジェクト
ダミーオブジェクトは、テストに影響を与えないテストダブルです。
実装例は以下のようになります。
public class テスト対象クラス { private 外部クラス 外部クラスのインスタンス = new 外部クラス(); public String テスト対象メソッド(Foo foo) { return 外部クラスのインスタンス.外部メソッド(foo); } } public class 外部クラス { public String 外部メソッド(Foo foo) { return "こんにちは"; } } public class Foo { // なにもしない } @Test public void テストコード() { String 期待値 = "こんにちは"; テスト対象クラス テスト対象クラスのインスタンス = new テスト対象クラス(); // これがダミーオブジェクト Foo foo = new Foo(); String result = テスト対象クラスのインスタンス.テスト対象メソッド(foo); assertEquals(期待値, result); }
まとめ
モックとは、テストダブルにおけるモックオブジェクトになります。
モックオブジェクトとは、スタブを含み、外部クラスの外部メソッドのテスト結果と適切な引数・回数呼び出しているかを検証できるテストダブルになります。
厳密な定義をするのなら、参考サイトのような内容になるのでしょうが、モックを使用するだけならこの程度の理解で十分だと思います。