オブジェクトを再帰的に精査する

JUnitを使ったテストで、入れ子になったオブジェクトをassertEquals()で精査するのは、死ぬほど面倒なので、フレームワーク精査用のライブラリを用意。

期待するオブジェクト

オブジェクトのダンプはこんな感じ。


ArrayList(11) {
[0]=>
String(5) "value"
[1]=>
String(1) "値"
[2]=>
ArrayList(2) {
[0]=>
String(13) "nested value1"
[1]=>
String(6) "入れ子の値1"
}
[3]=>
ArrayList(2) {
[0]=>
String(13) "nested value2"
[1]=>
String(6) "入れ子の値2"
}
[4]=>
HashMap(2) {
["foo"]=>
String(3) "bar"
["hoge"]=>
String(6) "hogera"
}
[5]=>
HashMap(2) {
["hogera"]=>
String(4) "hoge"
["bar"]=>
String(3) "foo"
}
[6]=>
HashMap(2) {
["egoh"]=>
String(8) "egohegoh"
["oof"]=>
String(3) "rab"
}
[7]=>
int(3) {
[0]=>
Integer "10"
[1]=>
Integer "11"
[2]=>
Integer "12"
}
[8]=>
String
(3) {
[0]=>
String(2) "20"
[1]=>
String(2) "21"
[2]=>
String(2) "22"
}
[9]=>
sample.SampleBean {
["value"]=>
String(6) "Beanの値"
["subBean"]=>
sample.SampleBean {
["value"]=>
String(9) "入れ子の値 その1"
["subBean"]=>
sample.SampleBean {
["value"]=>
String(9) "入れ子の値 その2"
["subBean"]=>
null
["id"]=>
Integer "999"
}
["id"]=>
Integer "99"
}
["id"]=>
Integer "9"
}
[10]=>
sample.SampleBean2 {
["age"]=>
String(2) "20"
["name"]=>
String(4) "山田太郎"
}
}

XMLのデータはこんな感じ。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE bodybuilder>
<bodybuilder>
  <list>
    <add value="value"/>
    <add value="値"/>
    <add>
      <!-- 入れ子のList その1-->
      <list>
        <add value="nested value1"/>
        <add value="入れ子の値1"/>
      </list>
    </add>
    <add>
      <!-- 入れ子のList その2-->
      <list>
        <listvals v1="nested value2" v2="入れ子の値2"/>
      </list>
    </add>
    <add>
      <!-- Map その1 -->
      <map>
        <foo value="bar"/>
        <hoge value="hogera"/>
      </map>
    </add>
    <add>
      <!-- Map その2 -->
      <map>
        <put name="bar" value="foo"/>
        <put name="hogera" value="hoge"/>
      </map>
    </add>
    <add>
      <!-- Map その3 -->
      <map>
        <mapvals oof="rab" egoh="egohegoh"/>
      </map>
    </add>
    <add>
      <!-- 配列 その1 -->
      <array type="int">
        <elem value="10"/>
        <elem value="11"/>
        <elem value="12"/>
      </array>
    </add>
    <add>
      <!-- 配列 その2 -->
      <array>
        <listvals v1="20" v2="21" v3="22"/>
      </array>
    </add>
    <add>
      <!-- Bean その1 -->
      <bean type="sample.SampleBean">
        <id value="9" type="int"/>
        <value value="Beanの値"/>
        <subBean>
          <!-- 入れ子のBean その1 -->
          <bean type="sample.SampleBean">
            <prop name="id" value="99" type="int"/>
            <prop name="value" value="入れ子の値 その1"/>
            <prop name="subBean">
              <!-- 入れ子のBean その2 -->
              <bean type="sample.SampleBean">
                <prop name="id" value="999" type="int"/>
                <prop name="value" value="入れ子の値 その2"/>
              </bean>
            </prop>
          </bean>
        </subBean>
      </bean>
    </add>
    <add>
      <!-- Bean その2 -->
      <bean type="sample.SampleBean2">
        <mapvals name="山田太郎" age="20"/>
      </bean>
    </add>
  </list>
</bodybuilder>

入力されたオブジェクト

オブジェクトのダンプはこんな感じ。


ArrayList(11) {
[0]=>
String(5) "value"
[1]=>
String(1) "値"
[2]=>
ArrayList(2) {
[0]=>
String(13) "nested value1"
[1]=>
String(6) "入れ子の値1"
}
[3]=>
ArrayList(2) {
[0]=>
String(13) "nested value2"
[1]=>
String(6) "入れ子の値2"
}
[4]=>
HashMap(2) {
["foo"]=>
String(3) "bar"
["hoge"]=>
String(6) "hogera"
}
[5]=>
HashMap(2) {
["hogera"]=>
String(4) "hoge"
["bar"]=>
String(3) "foo"
}
[6]=>
HashMap(2) {
["egoh"]=>
String(8) "egohegoh"
["oof"]=>
String(3) "rab"
}
[7]=>
int(3) {
[0]=>
Integer "10"
[1]=>
Integer "11"
[2]=>
Integer "12"
}
[8]=>
String
(3) {
[0]=>
String(2) "20"
[1]=>
String(2) "21"
[2]=>
String(2) "22"
}
[9]=>
sample.SampleBean {
["value"]=>
String(6) "Beanの値"
["subBean"]=>
sample.SampleBean {
["value"]=>
String(9) "入れ子の値 その1"
["subBean"]=>
sample.SampleBean {
["value"]=>
String(9) "入れ子の値 その2"
["subBean"]=>
null
["id"]=>
Integer "0" ←※間違い
}
["id"]=>
Integer "99"
}
["id"]=>
Integer "9"
}
[10]=>
sample.SampleBean2 {
["age"]=>
String(2) "20"
["name"]=>
String(4) "山田太郎"
}
}

XMLのデータはこんな感じ。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE bodybuilder>
<bodybuilder>
  <list>
    <add value="value"/>
    <add value="値"/>
    <add>
      <!-- 入れ子のList その1-->
      <list>
        <add value="nested value1"/>
        <add value="入れ子の値1"/>
      </list>
    </add>
    <add>
      <!-- 入れ子のList その2-->
      <list>
        <listvals v1="nested value2" v2="入れ子の値2"/>
      </list>
    </add>
    <add>
      <!-- Map その1 -->
      <map>
        <foo value="bar"/>
        <hoge value="hogera"/>
      </map>
    </add>
    <add>
      <!-- Map その2 -->
      <map>
        <put name="bar" value="foo"/>
        <put name="hogera" value="hoge"/>
      </map>
    </add>
    <add>
      <!-- Map その3 -->
      <map>
        <mapvals oof="rab" egoh="egohegoh"/>
      </map>
    </add>
    <add>
      <!-- 配列 その1 -->
      <array type="int">
        <elem value="10"/>
        <elem value="11"/>
        <elem value="12"/>
      </array>
    </add>
    <add>
      <!-- 配列 その2 -->
      <array>
        <listvals v1="20" v2="21" v3="22"/>
      </array>
    </add>
    <add>
      <!-- Bean その1 -->
      <bean type="sample.SampleBean">
        <id value="9" type="int"/>
        <value value="Beanの値"/>
        <subBean>
          <!-- 入れ子のBean その1 -->
          <bean type="sample.SampleBean">
            <prop name="id" value="99" type="int"/>
            <prop name="value" value="入れ子の値 その1"/>
            <prop name="subBean">
              <!-- 入れ子のBean その2 -->
              <bean type="sample.SampleBean">
                <prop name="id" value="000" type="int"/>
                                  <!-- ~~~ 間違い -->
                <prop name="value" value="入れ子の値 その2"/>
              </bean>
            </prop>
          </bean>
        </subBean>
      </bean>
    </add>
    <add>
      <!-- Bean その2 -->
      <bean type="sample.SampleBean2">
        <mapvals name="山田太郎" age="20"/>
      </bean>
    </add>
  </list>
</bodybuilder>

テストコード

こんな感じ。


package sample;

import junit.framework.AssertionFailedError;
import bodybuilder.builder.Bullworker;
import bodybuilder.inspector.Inspector;
import bodybuilder.viewer.Viewer;

public class Sample {

public static void main(String[] args) {
Bullworker sample1 = new Bullworker(
"C:\\eclipse3\\workspace\\bodybuilder\\data\\sample1.xml");
Bullworker sample2 = new Bullworker(
"C:\\eclipse3\\workspace\\bodybuilder\\data\\sample2.xml");
Object expected = sample1.getMuscle();
Object actual = sample2.getMuscle();
//Viewer.dump(expected);
//Viewer.dump(actual);

try {
Inspector.assertEquals(expected, actual);
} catch (AssertionFailedError e) {
e.printStackTrace();
}
}

}

テストの実行結果

こんな感じ。


junit.framework.AssertionFailedError: objects differ expected:<999> but was:<0>
ArrayList(11) {
[9]=>
SampleBean {
["subBean"]=>
SampleBean {
["subBean"]=>
SampleBean {
["id"]=>
Integer

at bodybuilder.inspector.Inspector.rethrow(Inspector.java:115)
at bodybuilder.inspector.Inspector.rethrow(Inspector.java:93)
at bodybuilder.inspector.Inspector.assertObjectEquals(Inspector.java:37)
at bodybuilder.inspector.BeanInspector.assertEquals(BeanInspector.java:25)
at bodybuilder.inspector.Inspector.assertObjectEquals(Inspector.java:33)
at bodybuilder.inspector.BeanInspector.assertEquals(BeanInspector.java:25)
at bodybuilder.inspector.Inspector.assertObjectEquals(Inspector.java:33)
at bodybuilder.inspector.BeanInspector.assertEquals(BeanInspector.java:25)
at bodybuilder.inspector.Inspector.assertObjectEquals(Inspector.java:33)
at bodybuilder.inspector.ListInspector.assertEquals(ListInspector.java:17)
at bodybuilder.inspector.Inspector.assertObjectEquals(Inspector.java:33)
at bodybuilder.inspector.Inspector.assertEquals(Inspector.java:12)
at sample.Sample.main(Sample.java:21)

TODO

  • サーブレットAPIへの対応。
    • MockRunner、StrutsTestCaseへの対応。
  • オブジェクトを再帰的に比較するAssertクラスの作成。
  • XMLの一ファイルを一テストケースとするテストユーティリティの作成。
  • ロギングの追加。
  • コンストラクタへの対応。
  • 特殊値(null、日付型、改行を含む文字列)への対応。
  • Bullworkerクラスの進退。
  • スタックトレースをも少し見やすく。

その他

Eclipseのプロジェクト一式*1

*1:JUnit、Commons BeanUtils/Logging、JDOM、Xerces