JUnitタスクを並列実行する方法

Antで複数のJUnitテストを並列に実行する方法です。実行時間の短縮が期待できます。

実装には以下の記事を参考にしました。
solutions daily: Making JUnit tests run parallely

標準の記述

Antで複数のJUnitテストを実行したい場合、普通に書くと以下のようなbuild.xmlになると思います。

<target name="execute.test">
  <junit fork="true" printsummary="true">

    <classpath>
      <path refid="${test.classes}" />
    </classpath>

    <formatter type="xml" />

    <batchtest todir="${test.reports}">
      <fileset dir="${test.sources}">
        <include name="**/*.java"/>
      </fileset>
    </batchtest>
  </junit>
</target>

${test.sources}配下の全テストクラスを実行するターゲットです.
これを複数スレッドで並列に実行できるように書き換えます。

手順

1. ant-contrib(Antの拡張ライブラリ)のjarをダウンロードし、Antのクラスパスに追加します。
2. ant-contribのタスクを有効にするため、以下の内容をbuild.xmlに追加します。

<taskdef
  resource="net/sf/antcontrib/antcontrib.properties"
/>

3. execute.testターゲットを、テストクラスを1つだけ実行するように修正します。

<target name="execute.test">
  <pathconvert property="test.source.relative">
    <fileset file="${test.source.absolute}" />
    <map from="${test.sources}/" to="" />
  </pathconvert>

  <junit fork="true" printsummary="true">

    <classpath>
      <path refid="${test.classes}" />
    </classpath>

    <formatter type="xml" />

    <batchtest todir="${test.reports}">
      <fileset dir="${test.sources}">
        <filename name="${test.source.relative}" />
      </fileset>
    </batchtest>
  </junit>
</target>

4. ant-contribのforeachタスクを使用し、上記3のexecute.testを呼び出します(イメージとしては、foreachタスクがfor文で、そのループの中でexecute.testメソッドを呼び出している、という感じでしょうか)。このforeachタスクのparallel属性をtrueにすることで、それぞれのexecute.testがマルチスレッドで実行されます。また、このときの最大スレッド数をmaxThreads属性で制御できます。

<target name="execute.tests.parallel">
 <foreach
    target="execute.test" 
    maxThreads="5"
    inheritall="true"
    inheritrefs="true"
    parallel="true"
    param="test.source.absolute">
    <path>
      <fileset dir="${test.sources}">
        <include name="**/*.java"/>
      </fileset>
    </path>
  </foreach>
</target>

以上



参考書籍:

Ant 第2版

Ant 第2版

あるディレクトリ以下の.classファイルを再帰的にJadるAntタスク

突然ですが、jadってご存知ですか?javaファイルをコンパイルしてできた.classファイルを、元のjavaファイルに戻すためのいわゆる「デコンパイル」のツールです。
今回、jdbcドライバの中身を見たくて、解凍したJarに含まれている.classファイルを一度でデコンパイルしたかったので、Javaの練習を兼ねてAntのタスクにすることにしました。せっかく作ったのでここでさらしておこうと思います。
Antについてはこの辺↓を見ていただくとして、
http://www.techscore.com/tech/Java/ApacheJakarta/Ant/1/

今回作るタスクの、build.xmlでの記述方法はこんな感じです。

<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="jad">
	<property name="classes.dir" value="classファイルのルート" />
	<target name="jad">
		<jad basedir="${classes.dir}" recurse="true" executable="">
			<arg value="-lnc" />
			<arg value="-p" />
			<arg value="-s java" />
		</jad>
	</target>

        <!-- taskdefタグで、独自タスクを読み込みます -->
	<taskdef name="jad" 
		classname="ant.jad.JadTask" 
		classpath="./ant-jad.jar"/>
</project>

jadタスクについて簡単に説明すると、

  • basedirで.classファイルのルートディレクトリを指定
  • recurseでディレクトリを再帰的に走査するかを指定(true/false)
  • executableでjad.exeへのパスを指定
  • 子要素のargでは、実行時オプションを指定

といった具合です。

独自タスクを作るには,Taskクラスを継承したクラスを作り、executeメソッドを実装するだけでOK!
そのタスクの本体がこちらです。

/**
 * あるディレクトリをベースに、再帰的にjadるためのタスク
 * @author tetran
 *
 */
public class JadTask extends Task {
	private static final String TARGET_EXTENSION = ".class";
	private String executable;
	private String basedir;
	private RecurseAttr recurse;
	private List<Parameter> args = new ArrayList<Parameter>();
	private File dir;
	private boolean isRecursive;
	private String[] executableArray;

	@Override
	public void execute() throws BuildException {
		validate();
		isRecursive = Boolean.valueOf(recurse.getValue());
		System.out.println(isRecursive);
		setupTemplate();
		try {
			exec(dir);
		} catch (IOException e) {
			e.printStackTrace();
			throw new BuildException("File Access Error!");
		}
	}

	private void setupTemplate() {
		executableArray = new String[args.size() + 2];
		executableArray[0] = isEmpty(executable) ? "jad" : executable;
		System.arraycopy(argsAsArray(), 0, executableArray, 1, args.size());
	}

	private void exec(File dir) throws IOException {
		File[] files = dir.listFiles(dirAndClasses());
		Runtime runtime = Runtime.getRuntime();
		for (File f : files) {
			if (f.isDirectory() && isRecursive) {
				exec(f);
				continue;
			}
			String thisPath = f.getAbsolutePath();
			log("processing-> " + thisPath);
			// 対象ファイル名は配列の最後
			executableArray[executableArray.length - 1] = thisPath;
			runtime.exec(executableArray, null, dir);
		}
		runtime.gc();
	}
	private String[] argsAsArray() {
		String[] result = new String[args.size()];
		for (int i = 0; i < result.length; i++) {
			result[i] = args.get(i).getValue();
		}
		return result;
	}
	/**
	 * ディレクトリとclassファイルを抽出する。
	 * @return
	 */
	private static FilenameFilter dirAndClasses() {
		return new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				if (new File(dir, name).isDirectory()) {
					return true;
				}
				return name.endsWith(TARGET_EXTENSION);
			}
		};
	}

	private void validate() {
		if (basedir == null || basedir.isEmpty()) {
			throw new BuildException("'basedir' must be designated!");
		}
		dir = new File(basedir);
		if (dir.isDirectory() == false) {
			throw new BuildException("such a directory does not exist");
		}
	}

        // 属性はsetterメソッドで追加する
	public void setExecutable(String executable) {
		this.executable = executable;
	}

	public void setBasedir(String dir) {
		this.basedir = dir;
	}

	public void setRecurse(RecurseAttr recurse) {
		this.recurse = recurse;
	}

        // 要素の追加はaddXXX(Parameter p)を使う
	public void addArg(Parameter arg) {
		this.args.add(arg);
	}

        // EnumeratedAttributeを拡張したクラスを用意することで、
        // 入力値を制限できる & EclipseのAntエディタで補完される!
	public static class RecurseAttr extends EnumeratedAttribute {
		@Override
		public String[] getValues() {
			return new String[] {"true", "false",};
		}
	}

	private static boolean isEmpty (String val) {
		return val == null || val.isEmpty();
	}
}

後はこのクラスをjarに固めて出来上がり!
独自タスクを作るときに大事なポイントとしては、

  • Taskを拡張したクラスを作る
  • executeメソッドを実装する
  • 属性はsetterメソッドで追加する
  • 子要素はaddXXXメソッドで追加する

くらいを押さえておけば十分だと思います。
ライブラリの拡張って、ちょっと敷居が高いような気がしていましたが、やってみると以外といけるものですね!こういう「自前ライブラリ」をどんどん増やしていきたいと思いました。

ていうか、前回の投稿からもう1年経とうとしていたとは。今後は週一くらいでネタを投下したいです。

OOP中毒?

最近、仕事でのコーディングがなかなか進まない。
原因はいくらか考えられるけど(実力不足とか)、その中の1つとして
「スタイルにこだわりすぎる」ってのがあるように思う。

例えば、変数名に異常なほどこだわったり、パターンを使いたがったり、
一言で言えば「かっこよく」書きたがっているってことかな。
オブジェクト指向本の影響を悪い方向に受けてしまっているようだ。
(まあ、これも実力不足に起因しているとも言えるけど)

なによりもまず「動くコード」を書くことが最優先であるはずなのに、
最初からきれいに書こうとしすぎている。
で、結局たいした進捗もないまま1日が終わってたりすることが多い。
まさに「できないやつ」の典型のようだ。

てなわけで、しばらくはスタイルは気にせず(最低限の規約は守るけど)、
とにかくまずは動くコードを書くようにしていかなければ、と思う次第であります(仕事だから当然か)。
そうやって経験値を積んでいった上で、改めてスタイルを確立できていければいいかなーっと。

きっとその方が楽しいと思うしw

Eclipseのプロジェクト名

今日、JAVA Puzzlar を購入したので実験してみようと思い、
macEclipse(3.6)を使ってJavaプロジェクトを作成しました。

ところが...
パズル1のisOddを書いてみて実行しようとしたところ、
こんなかんじ↓のエラーが発生して実行できない!?

Exception occurred executing command line.
Cannot run program
"/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/bin/java"
(in directory "/Users/xxx/workspace/ぷろじぇくと"): error=2, No such file or directory

どうやら、今回作成したプロジェクトの名前が日本語になっていることが原因みたい。
(いつもは英数字のみなんだけど、今日はたまたま日本語使ってました)
英数字のみの名前に変更しもう一度試してみたところ無事実行できて一安心( ̄。 ̄)

ちなみにWindowsでは日本語名でも問題ないようす。
macでだけ発生するのかも(Linuxは未確認)

これの解決に2時間も費やしてしまったのがたまらなく悲しい...orz


Java Puzzlers 罠、落とし穴、コーナーケース

Java Puzzlers 罠、落とし穴、コーナーケース

積んどくリスト

いま家にある未読の本をここで整理しておきます。
少しでも興味が出るとよく考えもせずに買ってしまうものだから
ぜんぜん消化が追いつかないw


以下、積んどくリスト @2010/7/11

>>Java
STRUTS・イン・アクション

STRUTS・イン・アクション

デコンパイリングJava ―逆解析技術とコードの難読化 (Art Of Reversing)

デコンパイリングJava ―逆解析技術とコードの難読化 (Art Of Reversing)

Eclipseプラグイン開発

Eclipseプラグイン開発

>>JavaScript
jQueryで作る Ajaxアプリケーション

jQueryで作る Ajaxアプリケーション

>>Python
Python クックブック 第2版

Python クックブック 第2版

リバースエンジニアリング ―Pythonによるバイナリ解析技法 (Art Of Reversing)

リバースエンジニアリング ―Pythonによるバイナリ解析技法 (Art Of Reversing)

>>プログラミング
アルゴリズムクイックリファレンス

アルゴリズムクイックリファレンス

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)

>>UI
デザイニング・インターフェース ―パターンによる実践的インタラクションデザイン

デザイニング・インターフェース ―パターンによる実践的インタラクションデザイン

ユーザビリティエンジニアリング原論―ユーザーのためのインタフェースデザイン (情報デザインシリーズ)

ユーザビリティエンジニアリング原論―ユーザーのためのインタフェースデザイン (情報デザインシリーズ)

>>コンピュータ関連
新標準SQLite (オープンソースRDBMSシリーズ)

新標準SQLite (オープンソースRDBMSシリーズ)

改訂 新Linux/UNIX入門

改訂 新Linux/UNIX入門

新版暗号技術入門 秘密の国のアリス

新版暗号技術入門 秘密の国のアリス

セキュリティの神話

セキュリティの神話

>>その他
家を借りたくなったら

家を借りたくなったら

まだ科学で解けない13の謎

まだ科学で解けない13の謎

数学ガール/フェルマーの最終定理 (数学ガールシリーズ 2)

数学ガール/フェルマーの最終定理 (数学ガールシリーズ 2)


これ以上積み上げてしまわないためにも、
週に2冊くらいのペースは維持していきたいなー

iPhone4ゲットだぜ!

ついに!ねんがんのiPhone4をてにいれたぞ!(AA略


てゆうか、受け取れるの来週末だと思ってたから
装備品(保護フィルムとか)全然用意してなかったよw

ハダカのまま使うのはいやなので、
とりあえず適当な保護フィルムを買ってくることに。
ちなみに買ったのはこれ↓


んで、使い始める前の初期設定を開始する。

回線の切り替えとか、
アドレス帳の移行とか、
メールアドレス変更のお知らせとか、
いろいろめんどい作業を終わらせ、
さていろいろ試そうか、と思ったら!
...日曜日終了のお知らせですよ(´Д`)

いいさ。手に持ってるだけで幸せな気分になるし。
いろいろやるのは明日からの楽しみにとっておくさ( ̄ー ̄)


それにしても、久々にいい買い物をしたなぁ。
一括払いにしたから来月の支払いが少し怖いけど。

明日からが楽しみだなー
使い倒すぞー!

そろそろ何か書こう

開設したはいいものの、何を書いたらいいか
わからずにずっと放置してた。

でもこのままだと永遠に書き始めない気がするので
適当に何か書いていこうと思う。

主にプログラミング関連の技術メモとして使おうと
思ってはいるけど、ほかにも思いついたこととか
書いてみよう。

とりあえず目標は週一回更新!
ってことで、よろしくおねがいします。