Package com.asakusafw.testtools.inspect

Source Code of com.asakusafw.testtools.inspect.AbstractInspector

/**
* Copyright 2011-2014 Asakusa Framework Team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.asakusafw.testtools.inspect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.apache.hadoop.io.Writable;

import com.asakusafw.runtime.value.ValueOption;
import com.asakusafw.testtools.ColumnInfo;
import com.asakusafw.testtools.ModelComparator;
import com.asakusafw.testtools.RowMatchingCondition;
import com.asakusafw.testtools.TestDataHolder;
import com.asakusafw.testtools.inspect.Cause.Type;

/**
* Inspectorの抽象クラス。
* このクラスを継承してInspecorのを実装する場合、
* モデルオブジェクトの期待値と実値を比較するメソッド
* {@link #inspect(Writable, Writable)}のみを実装すれば良い。
*/
public abstract class AbstractInspector implements Inspector {

    /**
     * 全カラムの情報。
     */
    private  List<ColumnInfo> columnInfos;

    /**
     * KEYカラムの情報。
     */
    private List<ColumnInfo> keyColumnInfos = new ArrayList<ColumnInfo>();

    /**
     * テストの開始時刻。
     */
    private long startTime;


    /**
     * テストの終了時刻。
     */
    private long finishTime = 0;

    /**
     * NG原因のリスト。
     */
    private List<Cause> causes = new ArrayList<Cause>();


    /**
     * 検査を実行するメソッド。
     * 継承クラスで実装してください。
     * @param expectRow 期待する値のレコード
     * @param actualRow 実値のレコード
     */
    protected abstract void inspect(Writable expectRow, Writable actualRow);


    /**
     * 検査が失敗した原因を設定する。
     * @param type 失敗の原因
     * @param expect 期待する値のレコード
     * @param actual 実値のレコード
     */
    protected final void fail(Type type, Writable expect, Writable actual) {
        String tableNameString = "table = " + columnInfos.get(0).getTableName() + ",";
        String keyValueString;
        if (actual != null) {
            keyValueString = getKeyValueString(actual);
        } else {
            keyValueString = getKeyValueString(expect);
        }
        Cause cause = new Cause(type, tableNameString +  keyValueString, expect, actual);
        causes.add(cause);
    }


    /**
     * 検査が失敗した原因を設定する。
     * @param type 失敗の原因
     * @param expect 期待値のレコード
     * @param actual 実値のレコード
     * @param expectVal 期待する値
     * @param actualVal  実値
     * @param columnInfo NG原因となったカラムの情報
     */
    protected final void fail(Type type, Writable expect, Writable actual,
            ValueOption<?> expectVal, ValueOption<?> actualVal,
            ColumnInfo columnInfo) {
        String format = "table = %s, %s,  column name = %s, expected = %s, actual = %s";
        String additionalMessage = String.format(format, columnInfo.getTableName(),
                getKeyValueString(actual), columnInfo.getColumnName(),
                expectVal.toString(), actualVal.toString());
        Cause cause = new Cause(type, additionalMessage, expect, actual,
                expectVal, actualVal, columnInfo);
        causes.add(cause);
    }

    /**
     * 指定のモデルオブジェクトからキー値を表す文字列を取得する。
     * @param model モデルオブジェクト
     * @return キー値を表す文字列
     */
    private String getKeyValueString(Writable model) {
        StringBuilder sb = new StringBuilder();
        sb.append(" Key = (");
        boolean first = true;
        for (ColumnInfo info : getKeyColumnInfos()) {
            if (info.isKey()) {
                if (first) {
                    first = false;
                } else {
                    sb.append(", ");
                }
                sb.append(info.getColumnName());
                sb.append(" = ");
                sb.append(getValue(model, info).toString());
            }
        }
        sb.append(")");
        return sb.toString();
    }


    /**
     * 全カラムの情報を取得します。
     * @return 全カラムの情報
     */
    protected final List<ColumnInfo> getColumnInfos() {
        return columnInfos;
    }

    /**
     * 全カラムの情報を設定します。
     * @param columnInfos 全カラムの情報
     */
    @Override
    public void setColumnInfos(List<ColumnInfo> columnInfos) {
        this.columnInfos = columnInfos;
        for (ColumnInfo info : columnInfos) {
            keyColumnInfos.add(info);
        }
    }

    /**
     * KEYカラムの情報を取得します。
     * @return KEYカラムの情報
     */
    protected final List<ColumnInfo> getKeyColumnInfos() {
        return keyColumnInfos;
    }

    /**
     * テストの開始時刻を設定します。
     * @param startTime テストの開始時刻
     */
    @Override
    public void setStartTime(long startTime) {
        this.startTime = startTime;
    }

    /**
     * テストの終了時刻(実際には検査の開始時刻)を取得します。
     * @return テストの終了時刻(実際には検査の開始時刻)
     */
    public long getFinishTime() {
        return finishTime;
    }

    /**
     * テストの終了時刻を設定します。
     * このメソッドは単体テストでの使用を想定しています。
     * このメッソドを呼び出さなかった場合、またはこのメソッドで0を
     * 設定した場合、テストの終了時刻にinspet()メソッドを呼び出した
     * 時刻が設定されます。
     * @param finishTime テストの終了時刻(実際には検査の開始時刻)
     */
    @Override
    public void setFinishTime(long finishTime) {
        this.finishTime = finishTime;
    }

    @Override
    public final List<Cause> getCauses() {
        return causes;
    }

    /**
     * 指定のモデルオブジェクトの指定のカラムの値を取得する。
     * @param model モデルオブジェクト
     * @param info カラム情報
     * @return カラムの値
     */
    protected final ValueOption<?> getValue(Writable model, ColumnInfo info) {
        ValueOption<?> vo = null;
        try {
            Method method = model.getClass().getMethod(info.getGetterName());
            vo = (ValueOption<?>) method.invoke(model);
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        return vo;
    }

    @Override
    public boolean isSuccess() {
        if (causes.size() == 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * テストの開始時刻します。
     * @return テストの開始時刻
     */
    protected long getStartTime() {
        return startTime;
    }

    @Override
    public boolean inspect(TestDataHolder dataHolder) {
        // テスト終了時刻が設定されていない場合、テスト終了時刻に現在時刻を指定
        if (finishTime == 0) {
            finishTime = System.currentTimeMillis();
        }

        // 始めにデータホルダが保持するデータをソートする
        dataHolder.sort();

        // 検査しないと指定されたテーブルに対しては常に成功を返す
        if (dataHolder.getRowMatchingCondition() == RowMatchingCondition.NONE) {
            return true;
        }

        // 各テーブルに対し、検査を実施する
        Comparator<Writable> comparator = new ModelComparator<Writable>(columnInfos, dataHolder.getModelClass());

        // 期待データのキーが重複していないかの検査
        List<Writable> expect = dataHolder.getExpect();
        for (int i = 1, n = expect.size(); i < n; i++) {
            if (comparator.compare(expect.get(i - 1), expect.get(i)) == 0) {
                fail(Type.DUPLICATEED_KEY_IN_EXPECT_RECORDS, expect.get(i), null);
            }
        }

        // 実際の出力データのキーが重複してないかの検査
        List<Writable> actual = dataHolder.getActual();
        for (int i = 1, n = actual.size(); i < n; i++) {
            if (comparator.compare(actual.get(i - 1), actual.get(i)) == 0) {
                fail(Type.DUPLICATEED_KEY_IN_ACTUALT_RECORDS, null, actual.get(i));
            }
        }
        if (!isSuccess()) {
            return false; // キーの重複がある場合検査を失敗させ、以降の処理を実行しない
        }

        // 各レコードを比較
        Iterator<Writable> expectIterator = expect.iterator();
        Iterator<Writable> actualIterator = actual.iterator();
        Writable expectRow = null;
        Writable actualRow = null;
        for (;;) {
            // expect, actualのどちらかしか存在しない場合
            if (!expectIterator.hasNext() && expectRow == null) {
                if (actualRow != null) {
                    fail(Type.NO_EXPECT_RECORD, null, actualRow);
                }
                while (actualIterator.hasNext()) {
                    actualRow = actualIterator.next();
                    if (dataHolder.getRowMatchingCondition() == RowMatchingCondition.EXACT) {
                        fail(Type.NO_EXPECT_RECORD, null, actualRow);
                    }
                }
                // 比較終了
                break;
            }
            if (!actualIterator.hasNext() && actualRow == null) {
                if (expectRow != null) {
                    fail(Type.NO_ACTUAL_RECORD, expectRow, null);
                }
                while (expectIterator.hasNext()) {
                    expectRow = expectIterator.next();
                    fail(Type.NO_ACTUAL_RECORD, expectRow, null);
                }
                // 比較終了
                break;
            }

            if (expectRow == null) {
                expectRow = expectIterator.next();
            }
            if (actualRow == null) {
                actualRow = actualIterator.next();
            }
            int result = comparator.compare(expectRow, actualRow);
            if (result < 0) {
                // 期待データに対応する実際の出力データが存在しない
                fail(Type.NO_ACTUAL_RECORD, expectRow, null);
                expectRow = null;
            } else if (result == 0) {
                inspect(expectRow, actualRow);
                expectRow = null;
                actualRow = null;
            } else {
                // 実際のデータに対応する期待データが存在しない
                if (dataHolder.getRowMatchingCondition() == RowMatchingCondition.EXACT) {
                    fail(Type.NO_EXPECT_RECORD,  null, actualRow);
                }
                actualRow = null;
            }
        }
        return true;
    }

    /**
     * テスト結果をクリアする。
     */
    public void clear() {
        finishTime = 0;
        causes.clear();
    }
}
TOP

Related Classes of com.asakusafw.testtools.inspect.AbstractInspector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.