S-JIS[2014-12-21/2018-09-26] 変更履歴

Asakusa Frameworkフローのテスト

Asakusa FrameworkのFlowPartTester・JobFlowTesterのメモ。


概要

AsakusaFWでジョブフローのテストを実行するクラスがJobFlowTester、フロー部品(FlowPart)のテストを実行するクラスがFlowPartTester。

フローをテストする場合、JobFlowTester/FlowPartTesterに入力データ・検証データ・検証ルールを指定する。
JobFlowTester・FlowPartTesterの一番の違いはテスト対象クラスの渡し方で、それ以外は同じ。

ただしJobFlowTesterを使うと、実行時に外部環境が必要になる。[2017-06-03]
(Importer/Exporterに従ってデータにアクセスする)
例えばWindGate JDBCを使っていると、実際にDBアクセスする(テスト環境用のDBが必要になる)。
例えば@directio.csv.file_nameが付けられた項目にはテスト環境のファイルのパスが入ってくる。
こういった場合、ジョブフローであってもFlowPartTesterでテストすると、そういった外部環境は不要になる。
Flow DSLとしてのJobFlowとFlowPartの違いは@Import/@Exportが書かれているかどうか、すなわちImporter/Exporterが記述されているかどうか。FlowPartでは@Import/@Exportは不要)
(例えばWindGate JDBCを使っていてもDBアクセスは行わない(WindGate JDBCかどうかはImporter/Exporterに書かれているので、それらを使わないFlowPartTesterには関係がない))
(例えば@directio.csv.file_nameであっても自動的にパスは入らず、テストデータに書いた値が入ってくる)


テストにはJUnitを使う。
テストを実行すると、テストドライバーが入力データを読み込んでファイルとして用意し、テスト対象クラスのフローを実行する。
そして実行結果のファイルを読み込み、検証ルールに従って検証データと突き合わせてチェックする。


フローのテストを実行するには、Asakusa Frameworkの実行環境が必要となる。[2017-12-08]

大昔は実行環境がHadoopしか無かったので、Hadoopを実行できないWindowsではフローのテストは実行できなかった。
AsakusaFW 0.7.1以降はスモールジョブ実行エンジンを使う事でWindowsでもテストを実行できるようになった。

AsakusaFW 0.10.0で、フローのテストはVanilla(pure Javaの実行エンジン)を使うようになった。
(スモールジョブ実行エンジンは使われなくなった)


JobFlowTesterの例

AsakusaFWのサンプルプロジェクトのCategorySummaryJobのテストクラスの例。

import com.asakusafw.testdriver.JobFlowTester;

import org.junit.Test;
/**
 * {@link CategorySummaryJob}のテスト。
 */
public class CategorySummaryJobTest {
	@Test
	public void describe() {
		JobFlowTester tester = new JobFlowTester(getClass());

		// 入力データを指定
		tester.input("salesDetail", SalesDetail.class).prepare("CategorySummaryJobTest.xls#salesDetail");
		tester.input("storeInfo", StoreInfo.class).prepare("CategorySummaryJobTest.xls#storeInfo");
		tester.input("itemInfo", ItemInfo.class).prepare("CategorySummaryJobTest.xls#itemInfo");

		// 検証データおよび検証ルールを指定
		tester.output("categorySummary", CategorySummary.class).verify("CategorySummaryJobTest.xls#categorySummary", "CategorySummaryJobTest.xls#categorySummary_rule");
		tester.output("errorRecord", ErrorRecord.class).verify("CategorySummaryJobTest.xls#errorRecord", "CategorySummaryJobTest.xls#errorRecord_rule");

		// テストの実行
		tester.runTest(CategorySummaryJob.class);
	}
}

入力データや検証データ等の指定方法は後述。

テストの実行(runTestメソッド)には、テスト対象のジョブフロークラスを渡す。


FlowPartTesterの例

AsakusaFWのサンプルプロジェクト(のCategorySummaryJobをフロー部品化した)CategorySummaryFlowPartのテストクラスの例。

import com.asakusafw.testdriver.FlowPartTester;
import com.asakusafw.vocabulary.flow.FlowDescription;
import com.asakusafw.vocabulary.flow.In;
import com.asakusafw.vocabulary.flow.Out;

import org.junit.Test;
/**
 * {@link CategorySummaryFlowPart}のテスト。
 */
public class CategorySummaryFlowPartTest {
	@Test
	public void describe() {
		FlowPartTester tester = new FlowPartTester(getClass());

		// 入力データを指定
		In<SalesDetail> salesDetail = tester.input("salesDetail", SalesDetail.class).prepare("CategorySummaryFlowPartTest.xls#salesDetail");
		In<StoreInfo> storeInfo = tester.input("storeInfo", StoreInfo.class).prepare("CategorySummaryFlowPartTest.xls#storeInfo");
		In<ItemInfo> itemInfo = tester.input("itemInfo", ItemInfo.class).prepare("CategorySummaryFlowPartTest.xls#itemInfo");

		// 検証データおよび検証ルールを指定
		Out<CategorySummary> categorySummary = tester.output("categorySummary", CategorySummary.class).verify("CategorySummaryFlowPartTest.xls#categorySummary", "CategorySummaryFlowPartTest.xls#categorySummary_rule");
		Out<ErrorRecord> errorRecord = tester.output("errorRecord", ErrorRecord.class).verify("CategorySummaryFlowPartTest.xls#errorRecord", "CategorySummaryFlowPartTest.xls#errorRecord_rule");

		// テストの実行
		FlowDescription flowPart = new CategorySummaryFlowPart(salesDetail, storeInfo, itemInfo, categorySummary, errorRecord);
		tester.runTest(flowPart);
	}
}

入力データや検証データ等の指定方法は後述。

テストの実行(runTestメソッド)には、テスト対象フロー部品のインスタンスを渡す。
FlowPartのインスタンスを作成するには入力や出力データを表すInやOutが必要になるので、tester.input()やtester.output()で生成する。


ジョブフロークラスであっても、FlowPartTesterを使ってテストすることが出来る。[2017-06-03]
ジョブフローとフロー部品の違いは、コンストラクターの各引数に@Import/@Exportが指定されているかどうかだけなので(FlowPartTesterを使う場合はそれらが無視されるだけなので)、形式的には全く同じ。


AsakusaFW 0.10.0で、FlowPartTesterのrunTestメソッドにラムダ式を渡せるようになった。[2017-12-10]
FlowPartクラスを作らなくてもフローのテストが出来るので、フローを使ってオペレーターのテストをする場合に便利。


バッチ引数の指定方法

BatchContextのバッチ引数(実行時引数)を使っているフローをテストする場合、JobFlowTester/FlowPartTesterに バッチ引数を指定する。

		tester.setBatchArg("argName", "value");

ちなみに、バッチ引数の内容をstatic変数にキャッシュするような使い方をしている場合は、テストの実行に注意が必要。

デフォルトの状態では、テストの実行にはHadoopを起動するので、テストケース毎に毎回static変数はクリアされる。

一方、スモールジョブ実行エンジンを使っている場合はテストケース間で同一のJavaVMが使われるので、static変数は最初に実行された状態でキャッシュされたままになる。
テスト用にキャッシュをクリアするような仕組みを自分で組み入れる必要がある。


入力データの指定方法

テストの入力データはJobFlowTester/FlowPartTesterのinputメソッド・prepareメソッドで指定する。

ジョブフローの場合
		tester.input("salesDetail", SalesDetail.class).prepare("CategorySummaryJobTest.xls#salesDetail");
フロー部品の場合
		In<SalesDetail> salesDetail =
		tester.input("salesDetail", SalesDetail.class).prepare("CategorySummaryFlowPartTest.xls#salesDetail");

フロー部品の場合はテスト対象FlowPartのコンストラクターに渡す必要があるので、変数に入れておく。

inputメソッドでは、テスト対象クラスの入力ポート名とデータモデルクラスを指定する。
prepareメソッドでは、入力データを指定する。


prepare

prepareメソッドでは、入力データを指定する。

入力データはExcelやJSONで指定できる。
Excelの場合はファイル名とシート名を「#」で区切って指定する。
ファイルは、src/test/resourcesの下のテストクラスと同一パッケージの場所に配置しておく。


AsakusaFW 0.9.1以降では、(Excelでデータを記述せずに)直接入力ファイルを指定することも出来る。[2017-04-30]

import com.example.modelgen.dmdl.csv.SalesDetailCsvFormat;
		tester.input(〜).prepare(SalesDetailCsvFormat.class, "sales_detail.csv");

このテストクラスがsrc/test/javaのcom.example.flowpartパッケージにある場合、
入力ファイルはsrc/test/resources/com/example/flowpartに置く。

Formatクラスを指定するのは、入力ファイルをどういった形式で読み込んで(パースして)どのデータモデルに変換するかを知らせる必要がある為。


入力データが空でいい場合は、prepareメソッドを省略してよい。

ジョブフローの場合
		tester.input("salesDetail", SalesDetail.class);
フロー部品の場合
		In<SalesDetail> salesDetail = tester.input("salesDetail", SalesDetail.class);

ただし、ジョブフローの場合、Importerで空ファイルを許容するように指定しておかないといけない。
(ImporterクラスでisOptionalメソッドをオーバーライドし、trueを返すようにする)


検証データおよび検証ルールの指定方法

テストの検証データおよび検証ルールはJobFlowTester/FlowPartTesterのouputメソッド・verifyメソッドで指定する。

ジョブフローの場合
		tester.output("categorySummary", CategorySummary.class).verify("CategorySummaryJobTest.xls#categorySummary", "CategorySummaryJobTest.xls#categorySummary_rule");
フロー部品の場合
		Out<CategorySummary> categorySummary =
		tester.output("categorySummary", CategorySummary.class).verify("CategorySummaryFlowPartTest.xls#categorySummary", "CategorySummaryFlowPartTest.xls#categorySummary_rule");

フロー部品の場合はテスト対象FlowPartのコンストラクターに渡す必要があるので、変数に入れておく。

outputメソッドでは、テスト対象クラスの出力ポート名とデータモデルクラスを指定する。
verifyメソッドでは、検証データと検証ルールを指定する。


verify

verifyメソッドでは、検証データと検証ルールを指定する。

Excelの場合はファイル名とシート名を「#」で区切って指定する。
ファイルは、src/test/resourcesの下のテストクラスと同一パッケージの場所に配置しておく。


AsakusaFW 0.9.1以降では、(Excelでデータを記述せずに)直接検証ファイルを指定することも出来る。[2017-04-30]

import com.example.modelgen.dmdl.csv.SalesDetailCsvFormat;
		tester.output(〜).verify(SalesDetailCsvFormat.class, "sales_detail.csv", "CategorySummaryFlowPartTest.xls#categorySummary_rule");

このテストクラスがsrc/test/javaのcom.example.flowpartパッケージにある場合、
検証ファイルはsrc/test/resources/com/example/flowpartに置く。

Formatクラスを指定するのは、検証ファイルをどういった形式で読み込んで(パースして)どのデータモデルに変換するかを知らせる必要がある為。
(検証ファイルを読み込んでデータモデルとして比較するのであり、出力されたファイルをそのまま検証ファイルと比較するわけではない)


出力データを検証する必要が無い場合は、verifyメソッドを省略してよい。

ジョブフローの場合
		tester.output("errorRecord", ErrorRecord.class);
フロー部品の場合
		Out<ErrorRecord> errorRecord = tester.output("errorRecord", ErrorRecord.class);

dumpActual

出力されたデータの内容を確認したい場合、dumpActualメソッドで出力場所を指定することが出来る。

ジョブフローの場合
		tester.output("categorySummary", CategorySummary.class)
//			.verify("CategorySummaryJobTest.xls#categorySummary", "CategorySummaryJobTest.xls#categorySummary_rule")
			.dumpActual("file:///tmp/categorySummary.xls");
フロー部品の場合
		Out<CategorySummary> categorySummary =
		tester.output("categorySummary", CategorySummary.class)
//			.verify("CategorySummaryFlowPartTest.xls#categorySummary", "CategorySummaryFlowPartTest.xls#categorySummary_rule")
			.dumpActual("file:///tmp/categorySummary.xls");

※dumpActualで指定できるファイルの種類はExcel(拡張子xls)だけっぽい。


dumpDifference

実行結果データと検証データの相違内容をファイルに出力することが出来る。

		tester.output("categorySummary", CategorySummary.class)
			.verify("CategorySummaryJobTest.xls#categorySummary", "CategorySummaryJobTest.xls#categorySummary_rule")
			.dumpDifference("file:///tmp/categorySummary.diff.html");

※相違が無い場合(テストが成功した場合)はこのファイルは出力されない。(ファイルが既存でも削除されたりはしない)


transform

AsakusaFW 0.7.0以降では、検証ルールに従ってチェックする前に、実行結果データを変換することが出来る。

例えば「値がnullだったら0扱いしてチェックしたい」場合に使用する。

import com.asakusafw.testdriver.core.ModelTransformer;
		tester.output("categorySummary", CategorySummary.class)
			.verify("CategorySummaryJobTest.xls#categorySummary", "CategorySummaryJobTest.xls#categorySummary_rule")
//			.dumpActual("file:///tmp/categorySummary.xls")
			.transform(new ModelTransformer<CategorySummary>() {
				@Override
				public void transform(CategorySummary model) {
					if (model.getAmountTotalOption().isNull()) {
						model.setAmountTotal(0);
					}
				}
			});

transformメソッドにはModelTransformerを渡す。
ここには実行結果データが(検証前に)入ってくるので、自由に加工することが出来る。

なお、dumpActualで出力されるデータは、加工前のものになる。


ModelTransformerは関数型インターフェースなので、ラムダ式で記述することが出来る。[2018-09-26]

		tester.output("categorySummary", CategorySummary.class)
			.verify("CategorySummaryJobTest.xls#categorySummary", "CategorySummaryJobTest.xls#categorySummary_rule")
//			.dumpActual("file:///tmp/categorySummary.xls")
			.transform(model -> {
				if (model.getAmountTotalOption().isNull()) {
					model.setAmountTotal(0);
				}
				
			});

なお、AsakusaFW 0.10.2で、transformはConsumerを受け取るようになった。(Consumerを受け取るメソッドが追加になった)
つまり、Consumerのメソッド(合成の為のandThen)が使える。
(ちなみにModelTransformerはConsumerを継承する形になった)


AsakusaFW 0.10.2で、入力データについても変換できるようになった。[2018-09-26]
(こちらはtransformメソッドではなく、prepareの引数に指定する)

例えば文字列から算出するハッシュ値とか、Excel上で計算するのは大変だが、Java上で計算してセットすることが出来る。

		tester.input("hoge", Hoge.class)
			.prepare("test.xls#hoge", model -> {
				model.setHashValue(MyHashUtil.calculate(model.getTextAsString()));
			});

Excelファイルの記述

テストデータを記述するExcelファイルの雛形はAsakusaFWによって生成することが出来る。

Shafuの「テストデータ・テンプレートを作成」を実行するとbuild/excelの下にデータモデル毎にExcelファイルが生成されるので、
生成されたExcelファイルから必要なファイルをsrc/test/resourcesの下にコピーし、データや検証ルールを記述する。


入力データ・検証データ

入力データや検証データは以下のような感じで記述する。

  A B C
1 store_code store_name  
2 0001 DUMMY  
3 0002 DUMMY  
4 0003 DUMMY  
5      

一番上がデータモデル内のプロパティー名(ヘッダー行)。
使わないプロパティーは書かなくてもいい(列を削除してよい)し、データモデル内の順番と一致している必要も無い。

AsakusaFW 0.7.0以降では、別のセルを参照したり計算式を書いたりすることが出来る。


検証ルール

検証ルールは以下のような感じで記述する。

  A B C D E F
1 Format EVR-2.0.0        
2 全体の比較 全てのデータを検査 [Strict]        
3 プロパティ 値の比較 NULLの比較 コメント オプション  
4 category_code 検査キー [Key] 通常比較 [-] TEXT    
5 amount_total 完全一致 [=] 通常比較 [-] LONG    
6 selling_price_total 完全一致 [=] 通常比較 [-] LONG    
7            

「値の比較」には以下のようなものが選択できる。

値の比較 ver 説明
検査対象外 [-] 1.0.0 値の検証(比較)を行わない。
検査キー [Key] 1.0.0 キーとして使用する。
完全一致 [=] 1.0.0 値が完全に一致していることを確認する。
部分一致 [<=] 1.0.0  
現在日付 [Today] 1.0.0  
現在時刻 [Now] 1.0.0  
特殊ルール [Expr] 2.0.0 値が範囲内に収まっていることを確認する。範囲は「オプション」で指定する。

実行結果データと検証データの比較は、キーが一致したデータ(レコード)に対して行われる。(レコードの並び順は関係ない)
したがって、キー項目を何かしら指定しないといけない。

キーが無いデータ(一意に定まらないデータ)は、AsakusaFWのテストドライバーではテストすることが出来ない。


特殊ルール

「値の比較」の「特殊ルール [Expr]」はEVR-2.0.0(AsakusaFW 0.7.0)から使えるようになった機能で、主にDECIMAL・DOUBLE・日付で値が範囲内に収まっていることを確認するもの。
DECIMALやDOUBLEでは、小数点以下の値があると、「完全一致 [=]」では一致と見なされないことがある。この為、特殊ルールで正当な値を範囲指定する。

値そのものは検証データのシートに書き、範囲は「オプション」に記述する。
例えば±0.001の範囲を指定する場合は、オプションに「~0.001」と書く。


なお、特殊ルールでは、「値がnullであること」を指定することは出来ない。
検証データのシートを空欄にしていると、テスト実行時にエラーになってしまう。(完全一致の比較では、空欄はnullとして扱われる)
transformを使って値を変換してチェックする


テストの実行環境の作成

フローのテストを実行するためには、AsakusaFWの実行環境を作っておく必要がある。

  1. 環境変数ASAKUSA_HOMEにパス(ディレクトリー)を定義しておく。
    この場所にAsakusaFW(の実行環境)がインストールされる。
  2. AsakusaFWをインストールする。

Windowsでも実行環境を作ることは出来る。
AsakusaFW 0.7.1以降はスモールジョブ実行エンジンを使用する。
AsakusaFW 0.10.0はデフォルトでVanillaが使われるので、特に気にしなくてよい。[2017-12-08]


テスト実行時のエラー

テストの実行でよく起こるエラーの例。

例外メッセージ 説明
java.lang.AssertionError:
この環境ではJavaコンパイラを利用できません(JDKを利用してテストを実行してください)
ジョブフローやフロー部品のコンパイルを行う必要がある為、プロジェクトのJavaライブラリーは(JREでなく)JDKである必要がある。
→EclipseのビルドパスのJavaライブラリーをJDKに変更する
com.asakusafw.lang.compiler.common.DiagnosticException:
system Java compiler is not enabled

[/2017-12-02]
java.lang.IllegalStateException: java.io.IOException:
Direct I/O configuration is not set: $ASAKUSA_HOME/core/conf/asakusa-resources.xml
環境変数ASAKUSA_HOMEの下にAsakusaFWがインストールされていない。
実行環境の構築
java.lang.AssertionError:
開発環境とテスト実行環境でフレームワークのバージョンが一致しません(開発環境:0.7.1-hadoop1, 実行環境:0.6.2)
環境変数ASAKUSA_HOMEの下にインストールされているAsakusaFW(実行環境)のバージョンとプロジェクトのAsakusaFW(開発環境)のバージョンが異なっている。
実行環境の構築
java.lang.IllegalStateException: java.io.IOException:
Cannot run program "〜\asakusa\testing\libexec\hadoop-execute.sh" (in directory "〜"): CreateProcess error=193, %1 は有効な Win32 アプリケーションではありません。
(AsakusaFW 0.10.0より前 [2017-12-08]
Windowsでは、スモールジョブ実行エンジン(AsakusaFW 0.7.1以降)を使わないとテストを実行できない。
スモールジョブ実行エンジンを使う設定
ERROR Failed to locate the winutils binary in the hadoop binary path
java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.
WindowsのHadoopはwinutils.exeを使うのだが、それが無いとこのメッセージがログに出力される。[2017-02-11]
Hadoopのディレクトリーが見つからなくてnullになっているのに、それをチェックせずにコマンドを実行しようとしてエラーになっている(苦笑)
ただしAsakusaFWは独自のwinutilsを使うので、テストの実行自体には影響が無い。
java.lang.IllegalStateException: ExitCodeException exitCode=-1073741515: Windowsでは、VC++のランタイムが無いと、この例外が発生する。[2017-02-11]
VC++ランタイムエラー
java.lang.IllegalStateException: java.io.IOException:
テストデータの検証に失敗しました: Input(salesDetail)
(file:/〜/com/example/jobflow/CategorySummaryJobTest.xls#salesDetail, 2, 3)の形式を判別できませんでした。先頭に ' を付けて文字列を表すようにしてください
データモデル上でTEXTのプロパティーにExcelデータで数値を指定すると、このエラーになる。
TEXTのプロパティーではデータを文字列にしなければならない。
「2, 3」はエラーのあったデータの位置を表している。
最初の2は2行目、次の3は3列目(C列)を意味している。
java.lang.AssertionError:
bid.byCategoryの出力categorySummaryには1個の差異があります
実行結果と検証データが一致していない。
検証エラーメッセージ

VC++ランタイムエラー

Windows上でフローのテストを実行する際、VC++のランタイムがインストールされていないとエラーになる。[2017-02-11]

java.lang.IllegalStateException: ExitCodeException exitCode=-1073741515: 
	at com.asakusafw.testdriver.FlowPartTester.runTest(FlowPartTester.java:116)
〜
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: ExitCodeException exitCode=-1073741515: 
	at org.apache.hadoop.util.Shell.runCommand(Shell.java:545)
〜
	at com.asakusafw.testdriver.FlowPartTester.runTest(FlowPartTester.java:111)
	... 24 more

WindowsのHadoopはwinutils.exeというコマンドを使用する。
これを実行するにはVC++のランタイムがインストールされている必要がある。
(大抵の場合は何らかのアプリケーション(Excelとか)をインストールしていればVC++のランタイムもインストールされるのだが、まっさらなWindowsだと入っていないことがある)


AsakusaFWでは、Hadoopがインストールされていなければ独自のwinutils.exeを使う。
この場合は、ログのどこかに以下のようなメッセージが表示されているはず。

11:16:31 INFO installing winutils.exe into default location: C:\Users\HISHID~1\AppData\Local\Temp\winutils-hishidama.exe
11:16:31 INFO winutils.exe is successfully installed: C:\Users\HISHID~1\AppData\Local\Temp\winutils-hishidama.exe

これを直接実行して「MSVCR100.dllがない」というエラーダイアログが出るなら、VC++ランタイムがインストールされていない。

> cd /d C:\Users\HISHID~1\AppData\Local\Temp
> winutils-hishidama.exe

MSVCR100.dllがない

> echo %ERRORLEVEL%
-1073741515

このエラーを解消するには、Microsoft Visual C++ 2010 再頒布可能パッケージ辺りをインストールすればよい。


検証エラーメッセージ

実行結果の値が検証データの値と一致しないと例外が発生し、どのプロパティーが一致していなかったか表示される。

java.lang.AssertionError: bid.byCategoryの出力categorySummaryには1個の差異があります:
CategorySummary: ["amount-total"(=2)が正しくありません: 「= 1」]: expected <{selling-price-total=100, amount-total=1, category-code="test1"}>, but was <{selling-price-total=100, amount-total=2, category-code="test1"}>

	at com.asakusafw.testdriver.JobflowExecutor.verify(JobflowExecutor.java:432)
	at com.asakusafw.testdriver.JobFlowTester.runTestInternal(JobFlowTester.java:167)
	at com.asakusafw.testdriver.JobFlowTester.runTest(JobFlowTester.java:100)
	at com.example.jobflow.CategorySummaryJobTest.describe(CategorySummaryJobTest.java:47)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

bid.byCategoryの出力categorySummaryには1個の差異があります」の「bid.byCategory」はテスト用のジョブフロー名。
出力categorySummary」は出力ポート名。

次の行の最初にある「CategorySummary」はデータモデルクラス名。
"amount-total"(=2)が正しくありません: 「= 1」」は、プロパティーamount_totalの実行結果の値が2、検証データが1であることを表す。
expectedの後ろに出ているのは検証データの値、but wasの後ろに出ているのは実行結果データ。

dumpDifferenceを使うことで、HTML形式で相違内容を出力できる


プロパティー名の区切り文字の指定

検証エラーメッセージに表示されるプロパティー名は、デフォルトではハイフン区切りになっている。
データモデル定義やExcelデータではアンダースコア区切りになっているので、エラーメッセージのプロパティー名をコピペして検索したいときに不便。

で、AsakusaFW 0.7.0で、検証エラーメッセージに使われる区切り文字を指定できるようになった。

import com.asakusafw.testdriver.core.PropertyName;
/**
 * {@link CategorySummaryJob}のテスト。
 */
public class CategorySummaryJobTest {

	static {
		System.setProperty(PropertyName.KEY_SEGMENT_SEPARATOR, "_");
	}
〜
}

これで、検証エラーメッセージに表示されるプロパティー名がハイフン区切りになる。

java.lang.AssertionError: bid.byCategoryの出力categorySummaryには1個の差異があります:
CategorySummary: ["amount_total"(=2)が正しくありません: 「= 1」]: expected <{selling_price_total=100, amount_total=1, category_code="test1"}>, but was <{selling_price_total=100, amount_total=2, category_code="test1"}>

テスト実行用に生成されたソースの検索

フローのテストを実行した時に、AsakusaFWによって生成されたクラスで例外が発生することがある。[2015-04-18]

フローのテスト時に生成されるソースは(デフォルトでは)“Javaの一時ディレクトリー”上に作られる為、探すのが大変。
だが、以下のようにすれば探すのが楽になる。

  1. フローのテストクラスで、ソースを出力する場所(コンパイル時に使用するワークディレクトリー)を指定する。
    		FlowPartTester tester = new FlowPartTester(getClass());
    		tester.setCompilerWorkingDirectory(new File("build/tmp/flowTest"));
  2. フローのテストを実行する。
    何らかの問題があれば例外が発生し、コンソールにスタックトレースが出力される。
    (以下のようなスタックトレースは、summarize演算子の集計項目がnullだった場合によく発生するorz この場合、MapOutputFragment1の該当行を見れば、どのカラムがnullだったのか分かる)
    java.lang.NullPointerException: null
    	at com.asakusafw.runtime.value.DecimalOption.get(DecimalOption.java:84) ~[asakusa-runtime-0.7-SNAPSHOT.jar:na]
    	at test.flowpart.testing.flowpart.stage0001.MapOutputFragment1.add(MapOutputFragment1.java:24) ~[jobflow-flowpart.jar:na]
    	at test.flowpart.testing.flowpart.stage0001.MapOutputFragment1.add(MapOutputFragment1.java:10) ~[jobflow-flowpart.jar:na]
    	at test.flowpart.testing.flowpart.stage0001.MapFragment1.add(MapFragment1.java:19) ~[jobflow-flowpart.jar:na]
    	at test.flowpart.testing.flowpart.stage0001.StageMapper1.runInternal(StageMapper1.java:26) ~[jobflow-flowpart.jar:na]
    〜
  3. スタックトレース内のソースファイル名と行番号の箇所がリンクになっているので、testパッケージのクラスのリンクをクリックする。
    (Eclipseのプロジェクトのソースフォルダー内に存在するファイルであれば、それがエディターで開くのだが)生成されたソースはソースフォルダーには無い為、エディターは開くがエラーになってソース自体は表示されない。
  4. エディター内にソースの場所を設定する為のボタンがあるので、それを押す。
    すると、「Add Source」ダイアログが開く。
    1. 「File System Directory」を選択してOKを押す。
      ※setCompilerWorkingDirectory()でディレクトリーを相対パスで指定した場合、プロジェクト内にディレクトリーが作られるので「Workspace Folder」の方を使いたいところだが、プロジェクトをリフレッシュしないとディレクトリーが見つからないので、「File System Directory」の方が確実。
    2. setCompilerWorkingDirectory()で指定した場所を入力する。
      相対パスで指定していた場合、プロジェクトのディレクトリーの下にそのパスのディレクトリーが作られているはず。
  5. これで、エディターにソースが表示されるようになる。

フローのテストクラス実行用の「Run Configurations」の「Source」タブの「Source Lookup Path」に、追加したソースパスが表示されている。
逆に言えば、ここからソースパスを追加しておくことでも同様の効果が得られる。
(あるいは、不要になったソースパスを削除することが出来る)


(DMDL EditorXの)スタックトレースからファイルを検索する機能(実行環境で例外が発生したときのソース検索)


AsakusaFW目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま