2019-11-05に投稿

Mockito入門 ~モックとスタブ~

概要

Mockitoでスタブとモックについてのまとめです. 実際の使い方というよりはMockとStubが必要なのは何故か, そしてどんな場合かといことを簡単に書き留めておきたいと思います. 使い方はMockitoパッケージのページを見ればだいたい分かると思います.

Q. 何故モックを使うのか?

コードが複雑化して, 他のオブジェクトを依存性として持ち処理を委譲するようになると, 単体テストのように値の妥当性を検証するだけでは品質を保証できなくなる. 依存性がきちんと呼び出されていることを検証し(verify), その結果が得られた値が期待通りのものであることが求められるから(expect). 前者の検証ではメソッドが呼び出されたことを検証できる機能が必要でその機能を提供するのがモックである.

テスト・ダブルについて

テストで使われる偽物オブジェクトの総称をテスト・ダブルと呼びます. Martin Fowler氏のMocks Aren't Stubsでは以下5つが紹介されている.

テスト・ダブル 説明
Dummy 何も実装しない
Fake 本物の簡易版
Stubs 決まった値を返す機能
Spies 実際のクラスのメソッドの呼び出しを諜報する
Mocks メソッドの呼び出しを記録する

これらは元々はMeszarosという人が定義したらしいです. 元々の定義ではDummyはテスト・ダブルではないと主張されているそうです.

自動テストのスタブ・スパイ・モックの違い

それぞれの役割は二つに分けられるそうです.

  • State verification
  • Behavior verification

MockだけがBehavior verificationのためのテスト・ダブルだそうですが, スパイとの違いがよく分かりません. FlutterのMockitoの元になったJavaのMockitoのSpyの使い方を見ると, 実際のオブジェクトのラッパーとして使うようです. 対象となるクラスとの交信(メソッド呼び出し)を傍受する(spy)と考えると理解しやすいかもしれません.

A Closer Look at Test Spies
Test Doubles — Fakes, Mocks and Stubs.

StubとMock

Mockitoのドキュメントを見るととりあえずStubとMockの違いがわかっていれば良さそうです(Fakeもありますが割愛します). 両者はテスト対象が状態(値)か振る舞いかで分類できました.

スタブでは値とメソッドの呼び出しを対応づけします. あるメソッドを呼び出したら, 決まった値を返すというルールを決めるわけです.

一方Mockはメソッドが正しく呼び出されたか検証します. スタブだけだと, 期待する結果が得られる過程を検証できません. 正しい順番でメソッドが呼び出され(あるいは呼び出されなかった)結果として期待する値が得られたことを確かめる必要があります.

Mockitoでモックを作る

Mockはテスト対象(System Under Test)が実行される際に必要な別のオブジェクトの偽物です. いわゆる依存性というやつです. Mockitoが提供するMockクラスを拡張しモックとしてAPIが必要な対象をimplementsします.

class MockClass extends Mock implements WanToBeMock {}

final mock = MockClass();
final sud = SystemUnderTest(mock);

SystemUnderTestクラスがテスト対象となるクラスです. このクラスの依存性としてモックが渡されているというのがポイントです. Mock化したクラスには以下の特徴があります.

  • 呼び出したメソッドを記録している.
  • メソッドは全てnullを返す.

モック・インスタンスは仕様通りメソッドを呼び出せるものの何もしません. つまり値を返せないわけです. ただ値の検証はテストの大事な要素なのでスタブで実現します.

MockitoにおけるStubbing

メソッド呼び出しが特定の値を返せばスタブで達成したいことを実現できます. whenとthenReturn関数を使います.

when(mock.doSomething()).thenReturn(100);

これでmockのdoSomethingメソッドが呼び出された時100を返すというという条件を明示できました.

コード

class MockClass extends Mock implements WanToBeMock {}

void main() {
  SystemUnderTest sud;
  MockClass mock;

  setUp(() {
    mock = MockClass();
    sud = SystemUnderTest(mock);
  });

  test('should return 100 when mock calculates 10 to the power of 2 (10^2).', () {
     // Arrange
     when(mock.pow(10, 2).thenReturn(100);

     // Act
     sud.pow(10, 2);
     final result = sud.getData();

     // Assert
     verify(
       mock.pow(10, 2) // (1)
     );
     expect(result, equals(100)); // (2)
  });
}

SystemUnderTestは様々な計算をラップしたクラスのようです. また実際の処理は別のオブジェクトに委譲しているようです. この場合単に100を返すようなべき乗の計算をSystemUnderTestのpowに実装するだけでは不十分です.

実際の計算処理はmockインスタンスに委譲する必要があります. しないとテストがパスしません. テストにパスするためには, powの結果が100であるだけでは不十分です.

  • mockのpowが内部で呼び出されている (1)
  • powの結果が100である (2)

という両方を満たすようにSystemUnderTestを実装する必要があります.

まとめ

テスト対象に依存性がない場合はわざわざMockitoを使う必要はありません. testパッケージを利用した所謂単体テストで十分です.

An introduction to unit testing

多くの場合全ての処理を自前で書くことはないので, 何らかのライブラリ(自作を含む)が提供するクラスを使う必要があります. それらをモックとして利用することで, 単に値が妥当である以上のことをテストできるようになります.

補足 (テストの手順など)

テストを書けと言われても何を書いていいか分かりにくいです. 特にMockを使うような場合, つまり依存性が存在する場合に指針があると分かりやすいです. またこの一般的な手順が分かっているとテストコードを読む時もまとまりが見えてきます.

AAA

AAAはArange, Act, Assertの略でテストを書くときの一般的な手順を表します. Arrangeではwhenによるstubbingなんかを行います. Actではテスト対象を実際に実行します. Asseertではメソッドは正しく呼び出せているかやその結果の戻り値が正しいかを検証します. 頭の中でこのフェーズを意識しておくことでテストコードが書きやすくなると思います.

フェーズ 説明
Arrange テスト・ダブルやインスタンスの準備
Act テスト対象の呼び出し
Assert 結果の検証

Red, Green, Refactor

TDD(Test Driven Development)ではテストは最初失敗するように書きます(Red). これは実装がないところからテストを始めるのでActが失敗するのは当然です. その後テストをパスできるように実際のコードを実装します(Green). その後このプロセスを繰り返しながらコードの品質の向上を目指します(Refactor).

Fixture

Fixtureはテスト用のデータのことです. 例えばHTTP通信でJSONを受け取るような場合に, 実際の通信相手がまだ存在しない, あるいは利用できない場合が考えられます. Fixtureとして定義しておくことでこうしたバックエンドの実装に左右されることなく, テストを実行できます.


ブレイン

Androidアプリ開発者を目指しています. 興味あることリスト: https://t.co/ew3bb6grdJ Github: https://t.co/9btqysHqWr Qiita: https://t.co/ZVRhjouauX

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!

ボードとは?

関連記事

コメント