KikainekoMocker

Java

Mock

機械猫モッカー

モック

機械猫モッカーはどのように動くのか

※現在改定中です。

ここでは、機械猫モッカーの設計仕様や処理の流れを説明します。

これは、機械猫モッカーの開発者向けの情報です。よって、 機械猫モッカーを使用するだけの場合、ここで記述されている内容を理解する必要はありません。

機械猫モッカーの設計

機械猫モッカーは以下の流れに沿って処理を実行しています。

  1. 字句解析
  2. テスト解析
  3. コード生成

解析

機械猫モッカーは、Java が行う字句解析とほぼ等価な字句を切り出します。

まず、org.kikaineko.sourcescan.CodeReader クラスがテスト・コードを読み込み、 コメントを省いたトークンの配列を返します。
(また、この際にダブル・クオートとシングル・クオートで括られた部分を識別します。)
実際には、このとき返されるのは、トークンの配列を抽象化した org.kikaineko.source.util.TokenArray クラス のインスタンスが返されます。

字句解析そのものを行っているのは、org.kikaineko.sourcescan.Tokenizer クラスであり、 内部に org.kikaineko.sourcescan.TokenAutomaton を保持しています。
また、この Tokenizer はコメントの除去までは行わず、コメントの除去は CodeReader クラスが 内部に org.kikaineko.sourcescan.CommentAutomaton を保持して実行しています。

テスト解

テスト解析フェーズでは、前期と後期の2つに分かれています。
前期テスト解析では、テストコードにどんなテストメソッドがあるのかとか、 擬似化対象のクラス(ターゲットと呼びます)は何かとか、後期テスト解析のための下準備を行います。

前期テスト解析

前期テスト解析フェーズで主役を演じるのは、org.kikaineko.mock.analysis.TestAnalyst クラスです。
このクラスは、先ほど抽出したTokenArrayを受け取り、 org.kikaineko.mock.framework.TestClass と org.kikaineko.mock.framework.TargetClass を生成します。

TestClass クラスは、テストコードを抽象化したクラスで、 TargetClass クラスは、モック対象(生成する対象)を抽象化したクラスです。

TestAnalystクラスは、TestClassに対してsetUp及びtestメソッドのTokenArrayを切り出します。 また、TargetClassクラスに対しては、クラス名やパッケージ名などを指定します。

後期テスト解析

次にいよいよテスト解析のメイン部分、後期テスト解析です。
ここで主役となるのは、org.kikaineko.mock.analysis.SmallInterpreter クラスです。
(これが機械猫モッカーの心臓部です)

SmallInterpreter クラスは、TestClass を受け取ると TestClass が保持している test メソッドを調べ、 setUp メソッド → test メソッドという順番でコードを解析していきます。

このとき、SmallInterpreter は実際に計算やオブジェクトのインスタンス化などを実行しています。
つまりほぼコンパイラーやインタプリタと同じ解析を行っています。異なるのは、TargetClass の存在です。

例えば、今 Calc.java の擬似クラスを生成したいとして、Calc calc という変数名を 記述している場合 SmallInterpreter は calc という名前を見つけるたびに TargetClass にアクセスを行います。

この際、calc に対して行われた処理を解析し、Calc が持つべきメソッドを判断します。
また、calc に行われた処理を全て history として保持していきます。
そして assertEquals が呼ばれたときに、calc がどのような値を返すことを期待されるかを判断し、 その値とそれまでの history を記録します。

コード

最終フェーズです。
SmallInterpreter クラスが解析した結果を全て TargetClass に記録し、 それを org.kikaineko.mock.runner.Implementer クラスが受け取りコードを生成します。

Implementer が行っている処理は、単純です。
TargetClass が保持するコンストラクター・メソッドを調べ、それをコードに落し、Stringとして返します。

[to top]

擬似クラス動作

機械猫モッカーが生成する擬似クラスがどのように動作するかを大まかに解説します。

内部

機械猫モッカーはモッククラスに次のフィールドを保持します。
※フィールド名は、バージョン0.9.4のもので、名称が変更される可能性があります。

private String kikainekoHistory
擬似クラスが呼び出された順番を記録する変数です。
private final String[] kikainekoHis
テスト・ケース内で指定された擬似クラスの呼出順を保持する配列です。
private final String[] kikainekoRes
擬似クラスが呼び出された順番を記録する変数です
private static final Class[] kikainekoUsedConcreteClasses
これは機械猫モッカーには重要ですが、擬似クラスの挙動理解には不要です。
このフィールドはテスト・ケースを実行した際に使用される具象クラスのクラスを保持します。
この値を使うことによって、機械猫モッカーはインターフェースへのインスタンス化に対応しようとします。

概要

擬似クラスの動作は単純です。

  1. 擬似クラスが呼び出される度に、それを kikainekoHistory に記録します。
  2. kikainekoHistory を kikainekoHis 配列の中身と比較し、一致するものがあればその index を返します。
  3. その index と同じ kikainekoRes 配列の要素を返します。
  4. kikainekoRes 配列から返された値を解析して、擬似クラスのメソッドの返り値として返します。

例)

public int doSomething(java.lang.Object arg0) {
  //TODO To change.Mock Part Start ...
  String[] args = { org.kikaineko.mock.util.ToStringer.get(arg0) };

  //呼出を記録し、その記録を返している (1) 
  String cond = kikainekoHistoryAdd(
                  "doSomething(java.lang.Object)", args, false);
  //呼出記録と一致する返り値を(文字列)で取得 (2,3)
  String res = kikainekoReturn(cond) ;

  //TODO To change.Mock Part End.
   
  //文字列の返り値を解析して、期待される型に変換 (4)
  return org.kikaineko.mock.util.ReturnValue.intValue(res);
}
※赤字のコメントが説明用に追加したもので、それ以外は実際にモッカーが生成するコードです。

呼び出れるメソッド

擬似クラスでは、以下のメソッドが呼び出されています。

org.kikaineko.mock.util.ToStringer.get( Object )
引数の Object 型(プリミティブ型を含む)を機械猫モッカーの規則にしたがって文字列に変換します。
   これは、機械猫モッカー独自のメソッドです。
String kikainekoHistoryAdd (String name, String[] args, boolean false)
第一引数の name はメソッドの名前で、args は引数の文字列表現の配列です。
このメソッドは、name メソッドが引数 args で呼ばれたことを kikainekoHistory に 記録していくメソッドです。
これは、擬似クラス内に生成されます。
String kikainekoReturn (String cond)
呼出記録を引数にとって、返り値の文字列表現を取得するメソッドです。
kikainekoRes 配列の要素を返します。
これは、擬似クラス内部に生成されます。
org.kikaineko.mock.util.ReturnValue.intValue (String res)
返り値の文字列表現を期待される型に変換するメソッドです。
org.kikaineko.mock.util.ToStringer と逆方向の変換を実行します。
これは、機械猫モッカー独自のメソッドです。

機械猫モッカーは基本的に何を実行しているのかを明確にするために、 意図的に擬似クラス内に2つのメソッドを生成しています。
ただし、
  org.kikaineko.mock.util.ToStringer
  org.kikaineko.mock.util.ReturnValue
の2つが機械猫モッカーにアクセスしており、その結果擬似クラスが機械猫モッカーに依存してます。
(100行以上も意味不明なコードをずらずらと擬似クラスに生成するよりはマシかと…)

文字変換規則

機械猫モッカーでは、内部で扱われる値を全て文字列で処理するため、 プリミティブ型やオブジェクトなど文字列に変換する規則が必要となります。
ここでは、その規則の概要を説明します。

文字列変換を一手に引き受けているのが org.kikaineko.mock.util.ToStringer です。
その逆変換を行うのがorg.kikaineko.mock.util.ReturnValueです。

プリミティブ / String / ラッパー

プリミティブ型、及び、String とラッパーは、ほぼそのまま変換します。

つまり、

assertEquals("1", ToStringer.get(1));
assertEquals("1.0", ToStringer.get(1.0));
assertEquals("true", ToStringer.get(true));
assertEquals("false", ToStringer.get(false));
assertEquals("test", ToStringer.get("test"));
assertEquals("1", ToStringer.get(new Integer(1)));
が成功するような変換を行います。

オブジェクト

オブジェクトの文字列表現への置き換えは、少し複雑な処理を行っています。

まず、オブジェクトの文字列表現ですぐに思いつくのは toString() メソッドですが、 機械猫モッカーは toString() メソッドは使用していません。
その理由としては、全てのクラスに対してオーバーライドが期待できないという点 と toString() メソッドの結果から、オブジェクトを再生成することが非常に困難だという点です。

そこで機械猫モッカーでは、オブジェクトの文字列表現として、 そのオブジェクトが持つフィールドを文字列に表現することを戦略として採用しています。
フィールドがプリミティブ型、文字列、ラッパーのいずれかであれば、そのまま文字列に変換しますが、 フィールド自体がオブジェクトであれば、そのオブジェクトがもつフィールドを文字列に変換する というような再帰的な変換を行います。

例えば、int型のフィールドを2つ持っているオブジェクトを文字列変換すると次のようになります。

assertEquals("1 2 ", ToStringer.get(new TwoInt(1, 2)));
空白文字「 」を区切りとして、フィールドを文字列に変換します。

ただし、static / final / transientで修飾されたフィールドは文字列変換の対象とはなりません。
これはそのオブジェクトの状態を表現するものではないと判断されるからです。

[to top]