9月26日のJJUG主催ナイトセミナー JJUG ナイトセミナー - torutkの日記 で、「イマドキの現場で使えるJavaライブラリ事情」の紹介にあったBytemanをJUnitから使い、モック的に利用する流れを簡単に記述しました。
Bytemanの設定
Bytemanの公式ホームページ(次のURL)から[Downloads]を辿ってbyteman binary + docsをダウンロードします。本日時点でバージョンは2.1.0です。
ダウンロードしたファイルbyteman-download-2.1.0-bin.zipを展開します(例:C:\java\byteman-download-2.1.0)。
Java開発環境(NetBeans)の設定
NetBeans IDE 7.2の上でJUnitテストケースからBytemanを使用します。まず、Bytemanのライブラリを設定します。
[ツール]メニュー>[Antライブラリ]を選択、[新規ライブラリ]で
ライブラリ名[Byteman]を作成します。クラスパスに次のJARを追加します。
- C:\java\byteman-download-2.1.0\lib\byteman.jar
- C:\java\byteman-download-2.1.0\lib\byteman-install.jar
- C:\java\byteman-download-2.1.0\lib\byteman-submit.jar
- C:\java\byteman-download-2.1.0\contrib\bmunit\byteman-bmunit.jar
Bytemanを使ってJUnitテストケースを実行するプロジェクトを選択し、[プロパティ]>[ライブラリ]>[テストをコンパイル]を選択、[ライブラリの追加]で先ほど作成した[Byteman]を追加します。また、Bytemanの実行時には、JDKのtools.jarに含まれるクラスを使用しているので、[JAR/フォルダの追加]でJDKインストールディレクトリ/lib/tools.jarを追加します。
テストケースの記述でBytemanを使用
テストケースクラスに@RunWithアノテーションを記述します。
import org.jboss.byteman.contrib.bmunit.BMUnitRunner; import org.junit.runner.RunWith; @RunWith(BMUnitRunner.class) public class HelloBytemanTest {
次に、Bytemanで細工を入れたいテストケースメソッドに、@BMRuleアノテーションでインストゥルメントする処理の定義を記述します。
@BMRule(name="skip finalize method call", targetClass="FileOutputStream", targetMethod="finalize()", condition="true", action="System.out.println(\"skip finalize\"); return") @Test public void openCloselessメソッドを繰り返し呼ぶ試験() throws Exception {
この例では、このメソッドのテストケース実行中にjava.io.FileOutputStreamのfinalizeメソッドコールが発生したときは、常に(condition="true")、アクション(action)を実行します。
テストケース本体は通常のJUnitテストケース記述です。
System.out.println("openCloseless"); HelloByteman instance = new HelloByteman(); boolean expResult = true; for (int i = 0; i < 1000; ++i) { String path = String.format("oyo-%04d", i); boolean result = instance.openCloseless(path); assertEquals(expResult, result); } TimeUnit.SECONDS.sleep(10); System.gc(); System.out.println("GC実行!"); TimeUnit.SECONDS.sleep(10); }
openCloselessメソッドの中でFileOutputStreamをnewし、close()をしないでリターンさせています。(リソースリークのバグを再現)
FileOutputStreamはfinalizeメソッドの中でcloseを実行しています。そのため、明示的にcloseを呼び出し忘れても、GCが発生後finalizeが実行された時点でファイルリソース(ファイルディスクリプタ)が解放されます。
上述コードはあまりいい書き方ではありませんが(リソース解放忘れをアサートしていない、テストケース内でsleepしている、などJUnit的にはアンチパターン)、テストケース中でSystem.gc()を実行するまでの間、1000個のファイルディスクリプタを握り続けます。その後、"skip finalize"がずらっと表示されます。