Uncle Bobのリトル・モッカー

以下はモックに関する会話です。

これは?

interface Authorizer {   
 public Boolean authorize(String username, String password);   
}

インターフェースです。

ではこちらは?

public class DummyAuthorizer implements Authorizer {   
  public Boolean authorize(String username, String password) {   
   return null;   
  }   
}   

こっちは「ダミー」です。

ダミーはどういった場合に使うのですか?

実際に使用されるかどうかに関係なく、プログラムを進める時です。

例えば?

テストですね。実際に使われることはありませんが、何かしらの引数を渡す必要があります。

例を見せていただけますか?

はい。

 public class System {   
  public System(Authorizer authorizer) {   
    this.authorizer = authorizer;   
  }

  public int loginCount() {   
    //returns number of logged in users.   
  }   
 }   

 @Test   
 public void newlyCreatedSystem_hasNoLoggedInUsers() {   
  System system = new System(new DummyAuthorizer());   
  assertThat(system.loginCount(), is(0));   
 }   

なるほど。このプログラムを実行するためにはAuthorizerをコンストラクタに渡す必要がありますよね。ですがテストでは誰もログインしませんから、Authorizerのauthorizeメソッドが呼び出されることはないのですね。

その通りです。

だからダミーのauthorizeメソッドがnullを返してもエラーではないですね。

確かにそうです。実際のところ、このように返すダミーが最良なのです。

なぜです?

誰かがダミーを使おうとすれば、NullPointerExceptionが発生します。

そうか、ダミーを使われたくないから。

そうです。替え玉なわけですから。

でもこれは「モック」ですよね? テスト用のこういったオブジェクトはモックと呼ばれると思いますが。

ええ。でもその呼び方は俗語なんです。

俗語?

テストに使用するオブジェクトをまとめて、モックと呼ぶことがあるのです。

正式な名前があるのですか?

はい。「テストダブル」(テストの代役)[1]と呼ばれます。

映画でいうところの「スタントダブル」(スタントの代役)みたいな?

まさにそうです。

ではモックという呼び方はただのスラングなのですね。

正式な意味もちゃんと含んでいますよ。ですがまあ、くだけた感じでモックと呼ぶ時はテストダブルと同じ意味合いですね。

なぜ2種類も呼び方があるのですか? モックではなくテストダブルだけを使えばいいのでは?

それには経緯があるのです。

経緯?

はい。かつて非常に優秀な人々によって、モックオブジェクトという言葉を紹介、定義する論文が書かれました。それを読んだ多くの人がモックという言葉を使い始めたのです。論文を読んでいない人たちは、その言葉を耳にし、より幅広い意味合いで使用するようになりました。動詞的な活用もされるようになりましたね。「これをモックしよう」や「モッキングがたくさん必要」という風に。

そういった活用の変化は、言葉に関してはよく起こることですよね。

ええ。その言葉が1音節で言いやすいような場合は特に。

「モックしよう」は「テストダブルを作ろう」と言うより簡単ですもんね。

そうです。口語表現は日常的なものですから。

でも正しい言葉を話さなければいけない時は…

ええ、形式言語を使うべきですね。

モックとは、つまり何なのですか?

その説明をする前に、別の種類のテストダブルも見ておいた方がいいでしょう。

どのような?

「スタブ」です。

スタブとは?

これです。

public class AcceptingAuthorizerStub implements Authorizer {   
 public Boolean authorize(String username, String password) {   
  return true;   
 }   
}

trueを返していますよ。

ええ、そうです。

なぜです?

ログインが必要な部分のテストも行いたいと思いますよね。

ログイン済みですが。

もうすでに、別のテストによりログインのプログラムが動くことは分かっています。それなのに、なぜもう一度テストを?

簡単だから?

でも時間がかかってしまいますし、セットアップも必要です。それにログインにバグが発生すれば、テストが台無しになってしまいます。

うーん。話を進めるために、賛成しておきます。それで?

テストするシステムにAcceptingAuthorizerStubを注入するのです。

これで疑いなしにユーザに権限を与えるのですね。

正解です。

そして、もし権限を持たないユーザを扱うシステムのテストを行いたければ、falseを返すスタブを使えばいいのですね。

またまた正解です。

他にはどんなのが?

これです。

public class AcceptingAuthorizerSpy implements Authorizer {   
 public boolean authorizeWasCalled = false;   

 public Boolean authorize(String username, String password) {   
  authorizeWasCalled = true;   
  return true;   
 }   
}   

これは「スパイ」と呼ばれるものですね。

その通りです。

スパイを使う目的は?

authorizeメソッドがシステムに呼び出されているかを確認できます。

ああ、なるほど。テストでスタブのように注入すれば、authorizerWasCalledの変数をチェックして、システムが本当にauthorizeを呼び出すかを確認するのですね。

全くその通りです。

ではスパイは、呼び出し元の監視役ですね。あらゆる情報を記録できるのだと思いますが。

そうですね。例えば呼び出しの回数を数えることができます。

なるほど。それなら呼び出しの度に、渡された引数のリストを保持することもできますね。

もちろん。スパイを使って、テスト対象であるアルゴリズムの内部動作を確認することも可能です。

結合しているようですね。

おっしゃる通り。だから注意が必要です。スパイを多用すると、システムの実装、呼び出し元の振る舞いとの結合が高くなるので、結果的にもろいテストになってしまいます。

もろいとは?

テストとしての意味を持たなくなるということです。

例えばコードを変更したら、壊れるテストもあるということですか。

ええ。ただ、テストを正しく設定できれば、失敗を最小限にとどめることができます。スパイはそれに反する働きをするんですよ。

分かりました。では、他にもテストダブルの方法はありますか?

あと2つほど。1つ目は次の通りです。

public class AcceptingAuthorizerVerificationMock implements Authorizer {   
 public boolean authorizeWasCalled = false;

 public Boolean authorize(String username, String password) {   
  authorizeWasCalled = true;   
  return true;   
 }   

 public boolean verify() {   
  return authorizedWasCalled;   
 }   
}   

モックですね。

そう、「本来のモック」です。

本来の?

もともとの意味を持ったモックオブジェクトです。

なるほど、テストの結果を基にしたアサーションを、モックのベリファイメソッドに入れるようなものですね。

そのとおり。モックでテストの結果を検証します。

じゃあ、アサーションをモックに入れるだけでいいのですね?

そうなんですが、モックが検証するのは振る舞いです。

振る舞い?

そうです。モックは関数の戻り値が重要ではありません。どんな関数と、どういった引数が、いつ、どれぐらいの頻度で呼び出されたかを重視するんです。

それでは、モックは監視の役割をしていると?

ええ。モックはテスト対象のモジュールの振る舞いを監視しています。期待された振る舞いが実行されたかを見るのです。

うーん。モックにエクスペクテーションを入れたら、結合のテストみたいですね。

そうですね。

では、どうしてその方法を?

モックツールを書くことを簡単にするからです。

モックツール?

はい。JMock 、EasyMock 、Mockitoなどのツールを使って、すぐにモックオブジェクトを生成できますよ。

なんだか複雑そうですけど。

そうでもありません。マーティン・ファウラー氏によって書かれたこの有名な論文が、分かりやすく説明してくれています。

本も出ていますよね?

ええ。『Growing Object Oriented Software, Guided by Tests』という、モックによる検証手法や哲学について書かれたとても素晴らしい本です。

なるほど。では、あともう1つのテストダブルとはなんですか?

あと1つは「フェイク」です。

public class AcceptingAuthorizerFake implements Authorizer {   
   public Boolean authorize(String username, String password) {   
    return username.equals("Bob");   
   }   
 }   

おかしいな。これでは“ボブ”というユーザ名を使えば誰でも権限を与えられてしまいますよ。

その通り。フェイクは値を設定することできます。入れたデータごとによって、実際と同じ動きをさせることができます。

いわゆるシミュレータのようなものということですか。

そうです。模擬実験と同じです。

フェイクとスタブとは違うんですね?

そうです。フェイクはいろんな値で振る舞えますが、スタブは違う。いままでに紹介した他のテストダブルでも、フェイクのような振る舞いはしません。

それではフェイクは根本的なレベルで別物というわけですね。

そうです。モックはスパイの、スタブはスパイの、ダミーはスタブのたぐいとも言えますが、フェイクは属性が違います。テストダブルの中で、まったく異なった性質を持つんです。

フェイクは複雑だろうということが想像できますよ。

非常に複雑です。ですから、フェイク単体でユニットテストを行う必要があり、極端に言えば、フェイクは本物のシステムと言えます。

うーん。

分かりづらいですよね。実を言うと、私もここ30年、フェイクは使っていないんです。

何ですって? それじゃあ、今は何を使っているんですか。他のテストダブルは使っているのでしょう?

ほぼ、スタブとスパイしか使ってないですね。プログラムは自分で書くことが多く、モックツールはそれほど使いません。

ダミーもですか?

ええ、めったに使いません。

モックはどうですか?

モックツールを使う時のみですね。

でも、先ほどモックツールは使わないと言われましたよ。

ええ、いつもは使いません。

では、なぜ?

スタブとスパイは書くのが簡単なので、私のIDEでさっと使うことができる。インターフェースを指定してIDEで実行させれば、ほら簡単。ダミーの値が返ってくるんです。それを少し書き換えて、スタブやスパイに置き換えます。こうすれば、ほぼモックツールを使う必要がないんですよ。

単なる使いやすさの問題ということですか?

そうですね。それにモックツールのややこしさが苦手なんです。セットアップにもひと手間必要になるし。なので、ほとんどのケースは、自分でテストダブルを書く方が簡単なんです。

なるほどね。有益なお話をありがとうございました。

いいえ、こちらこそ。

[1] xUnit Test Patterns