ひよっこ。

I want to…

Posts Tagged ‘Mock’

Mockito-Flexを利用してFlexのTDD/BDD環境を構築する(その2)

Posted by hikaruworld : 2011 2月 16

前回の続きになります。

Mockito-Flexの使い方

Java版のMockitoとの差異

基本的にはJava版のMockitoとほぼ同じですが、メソッドなど異なる部分もあります。
Java版のMockitoでは振る舞いのエミュレートの部分では、
when().thenを利用していましたが、mockito-flexの場合は、
given().willを利用する事になります。BDDMockito側を実装しているようですね。

ちょっとつらいのは、現在のmockitoでは複数の振る舞いの定義が出来ていないため、
以下のようなmockを書いても

given(hoge.piyo()).willReturn(“a”);
given(hoge.piyo()).willReturn(“b”);
given(hoge.piyo()).willReturn(“c”);

実行すると、以下のようになってしまいます。

trace(hoge.piyo()) // “a”
trace(hoge.piyo()) // “a”
trace(hoge.piyo()) // “a”

これは、Mockit-flex側でもIssuesとして、
#17 Multiple called given(something).willReturn(something) always returns first setted value
があがっていますが、まだ対応がはいっていないようです。
一応Answerを利用したやり方はあるようですので後述します。

Java版と同様な部分としては、DSL的な記述が可能なように
org.mockito.integrations以下にメソッド群が切り出されています。
Flex版ではグローバルメソッドとして定義されており、
Javaのようなstaticインポートが不要なところは言語的に楽ですね。

なお、これらのメソッドは実際はorg.mockito.Mockitoの
フィールドを呼び出すようになっています。
実際の挙動を確認する場合は、Mockitoの中身を確認すると良いかと思います。

Mockitoによる実装

ここからmockitoによって提供されている機能を確認していきます。
その際テスト対象のクラスが必要になりますので、
以下のクラスを対象にテストを行います。

public class Main
{
    public function Main()
    {
    }
    public function say(name:String):String
    {
        return "Hi " + name;
    }
    public function say(name:String):String
    {
        return "Hi " + name;
    }
    public function sayObj(obj:Object):String
    {
        return "obj is " + name;
    }
    public function check(data:Data):String
    {
        return data.userId;
    }
}
public class Data
{
	public var userId:int;
	public function Data()
	{
	}
}

なお、前回の方法でtargetに格納されている事とします。

Mock化

任意のクラスをMock化して振る舞いを設定する場合の方法です。

givenによる振る舞いの設定

given()を利用する場合は、以下のように記述します。

given(mock化するメソッドとその引数).willReturn(戻り値)

givenの引数にはmock化するメソッド[Mock]対象と、エミュレートしたい引数を設定します。
この引数はmockitoが提供するメソッドを利用して柔軟に設定する事が可能です。
willReturnはgivenによって与えられた条件時に返す値を定義します。
willReturn以外にも、will,willThrowがあります(詳しくは後述)。

引数のmock化

given時に引数をmock化するためのメソッドを見ていきます。

any()

any()は引数にどんな値を与えられても良い事を表します
(当然引数で定義される型の範囲内でですが)。
なお、any()はnull値を許容しますので、値がnullの場合でもmockとして処理されます

/**
 * anyのテストケースを検証_NULLでない場合
 */
[Test]
public function any_is_not_null():void
{
	given(target.say(any())).willReturn("piyo");
	
	var result:String = target.say("abc");
	
	assertThat(result, isA("piyo"));
}
/**
 * anyのテストケースを検証_NULLの場合
 */
[Test]
public function any_is_null():void
{
	given(target.say(any())).willReturn("piyo");

	// nullの場合でもwillReturnの値が返される
	// ちなみにundefでも問題なし
	var result:String = target.say(null);
	
	assertThat(result, isA("piyo"));
}

anyOf(Class)

anyOfも引数にどんな値を与えられても良い事を表します。
anyOfの引数には、対象の型を指定します。
例えばStringの場合は、anyOf(String)のように引数の型を指定します。

/**
 * anyOfのテストケースを検証_NULLでない場合
 */
[Test]
public function anyOf_is_not_null():void
{
	given(target.say(anyOf(String))).willReturn("piyo");

	var result:String = target.say("abc");

	assertThat(result, isA("piyo"));
}

ただし、前述のany()と異なりnull値を許容しません。mock化したメソッドに対して、
nullを引数に呼ばれた場合はArgumentErrorがthrowされます。

ArgumentError: obj cannot be null

havingPropertyOf

havingPropertyOf(property, value)は引数の持つプロパティの値が一致する場合に、
実行することが可能です。

[Test]
public function havingPropertyOf_test():void
{
        // 引数に渡されたプロパティuserIdが12の場合だけmock処理を行うように設定
	given(target.check(havingPropertyOf("userId", 12))).willReturn(22);

	var data:Data = new Data();
	data.userId = 12;
        // この場合にはwillReturnで指定した22が結果としてかえる。
	var result:int = target.check(data);

	verify().that(target.check(data));
	assertThat(result, isA(22));
}

TODO 継承周りとか調査

isItNaN

これはNaNつまり非数である場合にのみMock処理を行うために利用されます

[Test]
public function isItNaN_test():void
{
	given(target.sayObj(isItNaN())).willReturn("NaN value");

	var result:String = target.sayObj(Number.NaN);

	assertThat(result, isA("NaN value"));
}

notNull

そのまんまですね。値がnullでない場合のみMockのエミュレーションが実行されます。

[Test]
public function notNull_test():void
{
	given(target.say(notNull())).willReturn("Value is Null");

	var result:String = target.say("hoge");

	assertThat(result, isA("Value is Null"));
}

と、今回はこの辺で。
次回はverify周りに付いて書きます。

Posted in program | タグ: , , , , | Leave a Comment »

Mockito-Flexを利用してFlexのTDD/BDD環境を構築する(その1)

Posted by hikaruworld : 2011 2月 14

FlexでもユニットテストやMockライブラリが充実し、ずいぶんテストがしやすくなっています。
そこで、FlexでTDD/BDD環境の構築をしてみます。
なお、環境は以下のように構築しています

IDE FlashBuilderBurittoPreview
TestingFramework FlexUnit4 / Hamcrest-as / Mockito-Flex

Mockito-FlexはMock処理を簡単に行うためのフレームワークです。
内部的な実装はASMockに依存していますが、Mockitoを利用することで
ASMockのRecord->Replayの処理を省いて簡単にMockが作れるようになります(なるはず…)。

ライブラリの入手

必要な物を一通りダウンロードしてlibs以下においてパスを通します。

mockito-flex

今回の話題となるMockito-Flexです。
ここから現在の最新版1.4M5をダウンロードします。

FlexUnit4

FlashBuilder4にもFlexUnit4は同梱されているのですが、
ちょっと古いらしくRuleの実装がはいっていないバージョンです。
mockito-flexを利用する際、[Rule]があると楽できるため、ここから最新を落としておきます。
ちなみにバージョンは、FlexUnit4.1RC2でした。

Hamcrest-AS3

どうせなのでHamcrestのバージョンも最新にしておきましょう。
Hamcrest-as3はgitHubのここにあります。現時点での最新のバージョンは1.1.3になります。

テストうんぬん前にまずやるべき事

mockitoは最新版はAPIドキュメントなどもないので、ソースをダウンロードしておきます。
ソースはswcに紐つけておきましょう。
(Referenced Libraries -> mockito.XXX.swc -> 右クリックしてプロパティ -> ソース添付 -> 編集)
あと忘れずに、ASDocを作っておきます(以下はコマンドラインから)。

$ pwd
mockito-flex/mockito/src
$ asdoc -source-path ./main/flex/ -doc-sources ./main/flex/ -library-path ../libs/

まだドキュメントが十分そろっていないようですし、分からなくなったらASDocとソースを見るのが解決に一番の近道です。

テストのやりかた

さて、本題です。
ここにmockitoをFlexUnit4で動かすやり方が書いてありますので手順に従ってやっていきます。

基本的なやり方

mockitoをFlexUnit4上で利用するには3つの方法があるようです。

方法1:従来のケース

従来の方法?では、テストクラスへの[RunWith]を利用してmock化を行います。

/**
 * クラス宣言時にFlexUnit4の[RunWith]を利用してMockitoClassRunnerを指定
 */
[RunWith("org.mockito.integrations.flexunit4.MockitoClassRunner")]
public class MainTest
{
  /** Mock化対象のクラスを定義 */
  [Mock(type="com.wordpress.prepro.Main")]
  public var target:Main;
  
  // Testコード...
}

方法2:新しい方法(インスタンス化の制御を自信で行わない)

この方法を利用した場合、[Rule]の機能を利用してMockのインスタンス化が制御されます。
そのため、MockitoRuleを[Rule]メタタグを付けて初期化しておく必要があります。
ただし、インスタンス化の制御を[Rule]に依存するため、RunWithの設定を必要としません。

なお前述しましたが、Ruleは現在FBに同梱されているFlexUnitには含まれていません
ライブラリの部分の手順に従って、事前に最新版に置き換えておく必要があります。

/** [RunWith]は不要 */
public class MainTest
{
  /**
   * [Rule]でMockitoRuleをインスタンス化
   */
  [Rule]
  public var mockitoRule:IMethodRule = new MockitoRule();

  /**
   * Mock化対象クラスの宣言
   * 必ず***public***である必要がある。
   */
  [Mock(type="com.wordpress.prepro.Main")]
  public var mockie:Main;
  
  // Testコード...
}

なお、Mock化したいクラスにコンストラクタ引数が存在する場合は、以下のように設定します。
こうする事で、Mainクラスに引数1がコンストラクタ引数として渡されます。

/**
 * typeでMock化したいクラスを指定
 * argsListでコンストラクタに渡す値をもつフィールドを指定します
 */
[Mock(type="com.wordpress.prepro.Main", argsList="args")]
public var target:Main;
/**
 * Mock化するクラスに渡す引数を配列形式で定義します。
 */
public var args:Array = [1];

※ただし、この方法はドキュメント上には見つかりませんでした。
テストコードで見つけたので、今後どうなるかは不明です。

方法3:新しい方法2(インスタンス化の制御を自力で制御する)

方法2ではテスト実行時にmockオブジェクトが初期化※0されますが、
この方法を用いるとmock()メソッドを利用してmockオブジェクトのインスタンス化を制御します。

/**
 * Mock化対象クラスの宣言
 */
[Mock(type="com.wordpress.prepro.Main")]
public class MainTest
{
  /**
   * [Rule]でMockitoRuleをインスタンス化
   */
  [Rule]
  public var mockitoRule:IMethodRule = new MockitoRule();

  public var target:Main;
  
  // テストコード
  [Test]
  public function test():void
  {
    // mock()でmock化
    this.target = mock(Main);
  }
}

この方法のメリットは好きなときにmock化を制御できること、
また、その際にコンストラクタ引数を任意に指定できる事だと思います。

/**
 * Constructs mock object
 * @param clazz a class of the mock object
 * @param name a name used in various output
 * @param constructorArgs constructor arguments required to create mock instance
 * @return a mocked object
 */
function mock(clazz:Class, name:String=null, constructorArgs:Array=null):*;

とあるように、第一引数にMock化したいクラス名、第二引数にエイリアス?、
第三引数にコンストラクタ引数を指定する事が出来ます。

方法2のフィールド指定によるMock化では、コンストラクタ引数をプロパティとして、
定義しておく必要がありますが、方法3では、テストメソッド内でMockを組み立てながら、
引数を考える事が出来ます。

なお、不適切な引数の状態でMock化を指定した場合、
初期化時に以下のエラーが発生してしまいます。

ArgumentError: Incorrect number of constructor arguments supplied.

※0具体的には[Test]の実行時単位で初期化されます。

設定を間違った場合(タイポや、privateフィールドにしていたetc)などはmockオブジェクトの初期化に失敗するため、
nullアクセスのエラーが以下のようにメッセージ出力されます。
今のFlashBuilderだとカスタムメタデータまでは補完してくれないので、タイポに気をつけましょう….

TypeError: Error #1009: Cannot access a property or method of a null object reference.

何だか長くなってしまったので次に続きます。
次は、実際にmockやverifyを利用して[Test]を実行していきます。

Posted in program | タグ: , , , , | Leave a Comment »

EasyMockを使う

Posted by hikaruworld : 2008 12月 10

久々に使ったらやっぱり忘れていたのでメモしておく。

EasyMockに対して、インターフェイスを与えることで
簡単にモックテストを行えるように作られたモックを提供するためのライブラリ。
俗にいう、Service層やController層でのテストで用いられる事が多い。

実際にはSpringなどのようにDIコンテナなフレームワークを用いてインターフェイスベースで設計された
システムのテストを行う際に有用に使用することが可能。
なお、クラスによる直接の実行でもEasyMockClassExtensionを使用することでMock化することはできる。

インストール

  1. ここから最新版をダウンロードする。現時点では、2.3を使用(古っ)。
  2. 展開後、ディレクトリ直下のeasymock.jarにパスを通す。
  3. 以上。

手順

詳細に関しては、下記サンプルや同梱されているjavadocを見た方が断然分かりやすい。
が、ちょっと独特なのでテストをEasyMockを使って行う具体的な実行方法を書いておく。

  1. createMockでインターフェイスからMockオブジェクトを作成
  2. expect()メソッドを使って振る舞いを記録
  3. replay()メソッドを使って再生
  4. verify()メソッドを使って検証
  5. reset()メソッドを使って初期化

いつも思うのだけどもビデオの録画みたい。
やっぱり、このやり方は微妙になれないデス。

サンプル

TaskServiceImplのテストを行う際に、内部にインターフェイスの呼び出しTaskDaoが存在しており、
インターフェイスの実装がまだできていない場合に、TaskDaoをMockオブジェクトを使ってラップしてテストを行う方法。

  • class TaskServiceImpl
 /**
 * タスク関連サービス
 */
 public class TaskServiceImpl {
     /**
       * タスク登録時に設定されるDao層のインターフェイス
       * 今回は、このインターフェイスをラップする。
       */
     private TaskDao taskDao;
    /**
     * タスクを登録する。
     * @param userId 登録ユーザID
     * @param taskName タスク名
     * @return 登録されたタスク名の内部ID
     */
     int insertTask(int userId, String taskName) {
         // この処理をmockオブジェクトに置き換える。
         int resultId = this.taskDao.insert(task);
        
         return resultId;
     }
 }
  • テストクラス
// 処理しやすいようにstatic
import static org.easymock.EasyMock.*;

public class TaskServiceImplTest {
     /** 検査対象サービス */
     private TaskServiceImpl taskServiceImpl;
     /** Mockコントロール対象のインターフェイス */
     private TaskDao taskDao;
     /**
      * テストメソッド
      */
     public void test() {
         //検査対象を初期化
         this.taskServiceImpl = new TaskServiceImpl();

         // MockControllからインスタンスを生成
         this.taskDao = createMock(TaskDao.class); //--------------①

         // MockControllによってインスタンス化されたTaskDaoオブジェクトを
         // 検査対象に設定する。
         this.taskServiceImpl.setTaskDao(this.taskDao); //---------------②

         // -- 記録モード --
         // expectメソッドを使って、どのメソッドがどのような引数で呼ばれたときに、
        // どのような値が返却されるか指定できる。
         expect(this.taskDao.insert(task)).andReturn(1);//---------------③

         // -- 実行モード --
         // Mockによって実行させるインスタンスを実行対象として指定する。
         // これによって記録したモードを実行するようになる。
         replay(this.taskDao);//---------------④
         int i = this.taskServiceImpl.insertTask(1, "piyo");//---------------⑤

         // チェック
         // Junitと一緒に使う場合はこんな感じで。
         assertEquals(i, i);

         // -- 検証モード --
         // 記録したとおりに実行されたか確認を行う。
         // 検証モードを実行すると、記録モードで設定した通りにインスタンスが渡され、戻り値が返されたかの検証を行う。
         verify(this.taskDao);//---------------⑥

         // -- 後片付け --
         // 同一インスタンス内で再度expectするためには、初期化処理が必要。
         // これによって記録モードから設定した一連の処理が初期化され、再度記録が可能になる。
         reset(this.taskDao);//---------------⑦

     }
}
  1. Mockオブジェクトを生成する。

    this.taskDao = createMock(TaskDao.class);
    this.taskServiceImpl.setTaskDao(this.taskDao);

  2. 同上
  3. expectメソッドを使って、どのメソッドがどのような引数で呼ばれたときに、どのような値が返却されるか指定できる。

    expect(this.taskDao.insert(task)).andReturn(1);

  4. 実行対象を指定する。

    replay(this.taskDao);
    int i = this.taskServiceImpl.insertTask(1, task);

  5. 同上
  6. 検証モード

    verify(this.taskDao);

  7. 後片付け

    reset(this.taskDao);

おまけ

EasyMockClassExtensionの場合でも、privateなメソッドを実行することは不可能らしい。orz…

EasyMock Class Extension provides a built-in behavior for equals(), toString() and hashCode(). It means that you cannot record your own behavior for these methods. It is coherent with what EasyMock do. This limitation is considered to be a feature that prevents you from having to care about these methods. Final methods cannot be mocked. If called, their normal code will be executed. Private methods cannot be mocked. If called, their normal code will be executed. Remember this can occur during partial mocking. Class instantiation is performed using Objenesis. Supported JVMs are listed here. In the Advanced section, it was explained how to do partial mocking. One important thing is that private methods are never mocked. So if your method under test is calling some private methods, you will need to test them as well since you cannot mock them.

戻り値がないvoidメソッドを呼び出す場合は、以下のように直接メソッドを呼び出すだけでよい。

testDao.update();
replay(testDao);//処理を記憶

Posted in program | タグ: , , , | Leave a Comment »