mockery

前回のブログを書いたあとに、 @hidenorigoto 先生から指摘を受けてしまったので Mockeryを紹介してみるよ。
知らなかった自分が恥ずかしいが、PHP5.3以上に対応ということも助けて、かなり高機能だよ。

パフォーマンスとかはどうかはまだ体感していないですが。

static method のモック

本シリーズで紹介している通り、static method によるテスト阻害は
なかな苦しいものです。

しかし、ロード済みでないという制約下のもとで、Mockery による
static method のモックを作ることができます。
(やはり、クラスオートロード利用向けな予感がします。)
以下が例です。


SampleSingleton クラスが、仮にDBの接続系だったりすると、それを利用している
Sample::sfoo() などのテストはやりにくくなってしまいます。

なんとかなるよ! Mockery ならね。

なんという事でしょう。これだけで、OKです。

Prefixing the valid name of a class (which is NOT currently loaded) with “alias:” will generate an “alias mock”. Alias mocks create a class alias with the given classname to stdClass and are generally used to enable the mocking of public static methods. Expectations set on the new mock object which refer to static methods will be used by all static calls to this class.

(アホ超訳)

“alias:” というのを接頭語として正しいクラス名 (まだロードされていない) を利用すると、エイリアスモックを作成します。エイリアスモックは stdClass に対象クラス名でクラスエイリアスを作成します。それは一般的に public static メソッドのモッキングに有効です。static method を参照する新しいモックオブジェクトにセットされた expectations は、そのクラスのすべての static call が呼ばれたときに使われます。

なるほどなるほど。

ちなみに、PHP5.3新関数 の class_alias() という関数を駆使しているかんじです。
今回の例では、SampleSingleton をエイリアスとして、モックのクラスを読んでいます。
で、これまた PHP5.3 の新機能である __callStatic を使って何やらムフフやっている予感がします。

パーシャルモック

Mockery は、本当にリア充なので、 JMockit でできるようなパーシャルモックもやってのけます。

上の場合、Sample::bar() メソッドだけ、モックするよーという感じです。
Sample::foo() は維持してくれます。

残念なことに、private method のモック実現は runkit 使わないと難しいそうだけど。

まだまだ、ドキュメントを読み始めたばっかりなので、
面白い使い方があったらまた紹介します。

この物語はフィクションです。そういうことにしておいてください。

こんにちは。Java訓練中の川原です。

テストがない。
そんな現実のレガシーコードと戦う事になったとします。
テストがないコードというのは、テストを実行することを考えて作られていないので、いざテストを書こうとすると(僕の技量がまだまだなことも大きいが)怖気づいてしまい、結局またテストが書けないという悪循環に陥る可能性があります。
改修するタイミングなどに、思い切って改修する場所の周辺だけでもテストを作っておきたいものです。

勇気必要ですよね。ケント・ベック本の「まえがき」にもあるとおりです。

こんなケースに出会った

今回は、 Java のケースを想定してください。


Controller.execute() の改修を行うので 、命綱として Controller のテストを書いておきたいとします。
名前の通り、 Singleton は、 Singleton Pattern を実装したクラスです。 例えば、データベースの接続状態などを管理し、データを取得するような機能を持つとかそういうのを想定してください。
Util はクラスメソッドをもっています。やはり、こちらも外部コンテンツに接続した結果が帰ってくるようなものを想定してください。
おまけに、 Controller には、 fetchData() という、これまた何が帰ってくるかわからないようなメソッドがあったと想定します。

Singleton.foo() 、 Util.bar() そして、 Controller.fetchData() は、テストとしてはなかなか扱いづらいです。しかも、ものすごくたくさんの場所で使われているとして、他のクラスがまったくテストを書いていないとすればいきなり全力でゴリゴリとリファクタリングしまくるのも辛いわけです。
(時間ないなら尚更辛い!)

そんな時に、Mock を書くことになるかと思いますが JMock では苦しい気がしました。なぜなら、JMock では Mock として利用されるインスタンスは、インターフェイス化されていないといけないからです。
また、 Util.bar() のようなクラスメソッドをモックする機能は JMock にはありません。
Java には、 Mockito という Mocking Framework もありますが、やはりクラスメソッドのモック化は厳しいような雰囲気でした。

というわけで、 現実と戦うためには JMockit が最強っぽい予感です。

たのしいモック

// Controller.java
package net.bucyou;

import org.apache.commons.lang3.RandomStringUtils;

public class Controller {
	public String execute() {
		return fetchData() 
			+ Singleton.getInstance().foo()
			+ Util.bar();
	}
	
	public String fetchData() {
		return RandomStringUtils.randomAscii(2);
	}
}
// Singleton.java
package net.bucyou;

import org.apache.commons.lang3.RandomStringUtils;

public class Singleton {
	private static Singleton singleton = null;
	
	private Singleton() {
		
	}
	
	public static Singleton getInstance() {
		if (null ==  Singleton.singleton) {
			Singleton.singleton = new Singleton();
		}
		
		return Singleton.singleton;
	}
	
	public String foo() {
		return RandomStringUtils.randomAscii(2);
	}
}
// Util.java
package net.bucyou;

import org.apache.commons.lang3.RandomStringUtils;

public class Util {
	public static String bar() {
		return RandomStringUtils.randomAscii(2);
	}
}

さて、厄介なコードを用意しました。
RandomStringUtils.randomAscii(2); に関しては、ランダムで2文字のAscii文字を返します。
当然、 Controller.execute() した結果として、何が帰ってくるかはわかりません。

// ControllerTest.java
package net.bucyou;

import static org.junit.Assert.*;
import static org.hamcrest.core.Is.is;
import org.junit.Test;

public class ControllerTest {

	@Test
	public void testExecute() {
		Controller controller = new Controller();
		
		assertThat(controller.execute(), is("AABBCC"));
	}

}

とりあえず書いたテストです。当然失敗です。

さて、モックを作る準備として、 jmockit.jar をビルドパスに組み込みます。

ドキュメントによると、 classpath の順序に注意らしいです。JUnit の前に持って行きましょう。

さらに、テスト実行時に -javaagent:jmockit.jar などと、 VM argument に追加してやる必要があるそうです。
(ココらへんが、どういう理由で必要なのかはまだちゃんと理解してないです。)

さぁ、モックを書こう!

// ControllerTest.java
package net.bucyou;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import mockit.Expectations;
import mockit.Mocked;
import org.junit.Test;

public class ControllerTest {
	@Mocked
	final Util util = null;
	
	
	@Test
	public void testExecute() {
		final Controller controller = new Controller();
		
		// Expections は呼ばれる順番もチェックするぞ!
		new Expectations(controller) {
			Singleton singleton;
			{
				// controller.fetchData() のパーシャルモック
				// このメソッドだけ挙動変更とかができる。
				controller.fetchData();
				result = "AA";
				
				// Singleton のモック
				Singleton.getInstance();
				result = singleton;
				singleton.foo();
				result = "BB";
				
				// Util のモック
				Util.bar();
				result = "CC";
			}
		};
		
		assertThat(controller.execute(), is("AABBCC"));
	}
}

そして、実行。
ハハハ。美しい Test 成功のグリーンが輝いておる。

これでできた!

やったね!これで、テストを足掛かりに少しは前進することができるかなー。とか考えたわけです。
あと、モックを作るのは楽しいです!
JMockit は、カバレッジ計測を行ったりもできるらしいので、リア充な Mocking Framework だと思います。リア充乙。

ちなみに、これなんて読むんですか。
ジェイモキットとか読んでるんですけど。 JMock + Mockito で、ジェイモヒートとかなんですかね?

参考資料

このブログが、ワークショップのレビュー場になりつつある大原部長です。
(ほんとうは、もっと翻訳とかオープンソースネタを晒したいんだお。
とかいうと、どうでもいいことはポストしてんじゃねーか。と言われそうなのでオフレコで)

昨日、 TDD BC Tokyo 1.6 が開催されました。
和田さんの素晴らしい講演や、Java初心者 (3ヶ月程度) で 挑んだペアプロなどなど
非常に熱いものでした。

とぎゃったーがあるので、全体的なまとめはこちらをどうぞ。

気づき

  • 仮実装をやる意味: 仮実装完了時点でレッドならば、テストの方が間違っているかもしれない。テストをテストする必要。
  • テストもリファクタリングが必要
  • リファクタリングも一歩ずつ着実に
  • 気づくことが大事。
  • テストのために機能が必要になったら実装することも必要。
  • TDDは「かもしれない運転」と考えた。だとしたら、TDDのない開発は暴走した自動車と一緒だ。

振り返り

毎回、このようなイベントでは全体でKPTを行っていますが、個人的にもKPTをやろうと思ったわけです。
最近は、自分自身のOJTでもKPTが十分ハナシのネタになるので、KPTはわかりやすく素晴らしいフレームワークだと思います。

Keep

  • すばらしいイベントに参加できた。
  • ちゃんとブログ書いた。
  • t_wadaさんとお話できた。
  • 普段Javaを触っている人たちとペアプロ・お話することができた。

Problem

  • バージョン管理するべきだった。Gitを使いたかったが、Eclipse用の環境を当日揃えた上に、使い方がよく分からなかった。
  • 道具の整備不足。Eclipseのショートカットとかその日に覚えたのが結構多い。
  • 他の言語との触れ合いは少なかったかも。同じ言語でも、Groovy という興味深いものを目の前にしていたが、触れることは当日はできなかった。

Try

  • Acts as Professional
  • 社内でのTDD布教 → まずはチーム内勉強会などで共有などなど。
  • Groovy 興味持った。やる。
  • Githubでソースコードの公開。(Subversionでは公開済)
  • 本読もう。みんなで読もう