その他の要件

Mock4AS

FlexUnit

サンプルファイルのダウンロード:

ユニットテストを実行する際には、オブジェクトが想定どおりに動作するかどうかを、そのオブジェクトを直接観察することで常に検証できるとは限りません。その代わりに、オブジェクト間の協調動作の観察が必要な場合があります。依存性のあるコンポーネント間の相互作用の検証プロセスをビヘイビア検証と呼びますが、これを容易にするのが、モックオブジェクトフレームワークです。

この記事は、ユニットテストにビヘイビア検証を追加しようと考えている開発者を対象としています。ユニットテストにおけるビヘイビア検証の役割を説明し、ActionScript 3.0用の軽量のモックオブジェクトフレームワークであるMock4ASの基本を紹介します。

ユニットテストにおけるビヘイビア検証の役割

ユニットテストの用語では、テスト対象のオブジェクトをSUT(System Under Test)と呼びます。SUTが作業を実行するために依存しているオブジェクトのことを、DOC(Depended On Component)と呼びます。

例えば、ユーザがWebサイトにログオンしたときに歓迎メッセージを表示するオブジェクトを考えます。ローカライゼーションのために、このGreetingオブジェクトはTranslatorオブジェクトを使って「hello」を目的の言語に翻訳します。テストしたいのはGreetingオブジェクトなので、これがSUTになります。このオブジェクトはTranslatorに依存して適切なメッセージを取得するので、TranslatorがDOCです。

SUTがDOCと想定どおりに協調動作することを検証するため、通常のDOCをテスト用のオブジェクトに置き換えます。これをモックと呼びます。次に、SUTから受け取ることが想定される内容をモックに伝えます。想定を設定したら、メソッドを呼び出してSUTを動作させます。SUTはモックのメソッドを呼び出します。SUTが動作した後、想定した方法でモックが使用されたかどうかをモックに問い合わせます。

モックオブジェクトフレームワークは、ビヘイビアを設定し、モックのメソッドの呼び出しを記録し、メソッドが想定どおりに呼び出されたかどうかを検証するために必要な仕組みを提供します。

SUTとDOCのコードの確認

モックオブジェクトを使用する前に、Greetingサンプルの機能コードを見てみましょう。このサンプルでは、Greetingというカスタムクラスが、Translatorのインプリメンテーションに依存しています(Translatorはインターフェイスです)。

Greetingクラスは、任意の言語で「Hello」と表示するビヘイビアを提供しています。GreetingはTranslatorインプリメンテーションを使用して、「Hello」を英語から選択された言語に翻訳します。Greetingクラスの典型的な使用法を次に示します。

var translator:Translator = new TranslatorImpl(); var greeter:Greeting = new Greeting(translator); var msg:String = greeter.sayHello("Portuguese", "Paulo");

構築時に、GreetingコンポーネントはTranslatorImplクラスのインスタンスを受け取ります。これは、Translatorインターフェイスの具体的インプリメンテーションです。テストにおいては、やはりTranslatorインターフェイスを実装したモックオブジェクトを使用する必要があります。

GreetingとTranslatorのコードを次に示します。

Greeting.as

 

package org.mock4as.samples.greeting { public class Greeting { private var translator:Translator; public function Greeting(translator:Translator){ this.translator = translator; } public function sayHello(language:String, name:String):String{ return translator.translate("English", language, "Hello") + " " + name; } } }

Translator.as

 

package org.mock4as.samples.greeting { public interface Translator { function translate(from:String, to:String, word:String):String } }

GreetingクラスがどのようにTranslatorに依存しているかがわかったので、モックオブジェクトを適用して、GreetingクラスがTranslatorオブジェクトを想定どおりに使用しているかどうかを検証できます。

モックオブジェクトを使用したユニットテスト

モックオブジェクトを使用してユニットテストを行うには、次の手順を実行します。

  1. Mockクラスを作成します。
  2. Mockをインスタンス化します。
  3. Mockに想定とビヘイビアを設定します。
  4. Mockを挿入します。
  5. SUTを動作させます。
  6. Mockの想定を検証します。

Greetingサンプルに対する(モックオブジェクトを使用した)ユニットテストコードを実行する手順を以下に示します。FlexUnitとMock4ASは、このサンプルで使用するユニットテストとモックオブジェクトのフレームワークです。サンプルコードを実行する前に、FlexUnitとMock4ASをダウンロードしてインストールします(記事の最初にあるリンクを参照してください)。

1. Mockの作成

Mock4ASを使用するには、org.moc4as.Mockクラスを拡張するか、コンポジションに使用するクラスを作成する必要があります。この記事のコードサンプルでは、簡単にするために、org.moc4as.Mock.のサブクラス化だけを扱います(Mockをコンポジションで使用することもできます。この場合、他のオブジェクトと同じコンポジションの原則に従います。 同じ作業にコンポジションを使用したサンプルについては、Mock4ASプロジェクトページを参照してください)。まず、org.mock4as.Mockを拡張して、モックするコンポーネントのインターフェイスを実装する新規クラスを作成します。例えば、次のようになります。

public class MockTranslator extends Mock implements Translator {}

次に、モックするインターフェイスのメソッドを、モックのrecord()メソッドの呼び出しによって実装します。これにより、依存コンポーネントの呼び出しを記録する構造が作成されます。この例では、translateメソッドの呼び出しを、渡された引数とともに記録します。このサンプルでは、MockTranslatorクラスはtranslateを次のように実装します。

public function translate(from:String, to:String, word:String):String { record("translate", from, to, word); return expectedReturnFor(); }

translateメソッドのこのインプリメンテーションにより、Mock4ASフレームワークは、メソッドとそのパラメータが呼び出されたことを記録します。 また、Mock4ASフレームワークは、translateメソッドに対して設定されている値を返します。record()メソッドとexpectedReturnFor()メソッドは、モックオブジェクトの想定とビヘイビアセットへのリンクを提供します(詳細についてはこの後の節で説明します)。

MockTranslatorクラスのコード全体を次に示します。

import org.mock4as.Mock; class MockTranslator extends Mock implements Translator { public function translate(from:String, to:String, word:String):String { record("translate", from, to, word); return expectedReturnFor("translate"); } }

2. Mockオブジェクトのインスタンス化

MockTranslatorクラスを作成したら、インスタンス化して使用する必要があります。Mockのインスタンス化には、他のActionScriptクラスと同様に、new演算子を使用します。これは通常、SUTを動作させるユニットテストの中で行います。

var Mock:mockTranslator = new MockTranslator();

3. Mockの想定とビヘイビアの設定

Mockが想定された方法で呼び出されたことを検証するため、想定を設定する必要があります。これにはまず、Mock APIのexpects()メソッドを呼び出します。expects()メソッドは最初に呼び出す必要がありますが、Mock APIには想定を設定するためにいくつかのメソッドが用意されています。使用できるメソッドのいくつかを次に示します。

  • expects(methodName:String):想定されるメソッド呼び出しの名前をMockに教えます。
  • times(numOfTimes:int):メソッドが呼び出される回数を設定します。
  • withArgs(arg1, arg2, ...argN):メソッドに渡される引数をMockに教えます。

これらのメソッドは設定対象のMockへの参照を返すため、読みやすいようにメソッド呼び出しを連結することができます。例えば、translate("English","Portuguese", "Hello")が1回呼び出されることを想定するようにMockに指示するには、次のようにします。

mockTranslator.expects("translate").times(1).withArgs("English","Portuguese","Hello");

Mock4ASには、Mockオブジェクトのビヘイビアを設定する機能もあります。ビヘイビアを設定するには、前のコードサンプルにwillReturn()メソッドへの呼び出しを追加します。

mockTranslator.expects("translate").times(1).withArgs("English","Portuguese","Hello").willReturn("Ola").times(1);

このサンプルは、translateメソッドが「English」、「Portuguese」、「Hello」を引数として呼び出されたときに、「Ola」を返すようにMockに指示します。これにより、Greetingオブジェクトがこの入力にどのように応答するかをテストできます。

メモ:このサンプルでは文字列引数("Ola")を渡していますが、willReturn()メソッドは任意の型のオブジェクトを渡すために使用できます。

willReturn()はあらゆる型のデータを返すために使用でき、無効なデータも返せます。これは、オブジェクトが無効なデータにどのように対応するかを検証する際に便利です。Mockがエラーを発生するように設定するには、willReturn(new Error())を使用します。これを使えば、DOCから発生したエラーをSUTがどのように処理するかをテストできます。

4. Mockの挿入

これまでに、MockインスタンスmockTranslatorを作成し、想定とビヘイビアを指定しました。今度は、GreetingオブジェクトにmockTranslatorインスタンスを使用するように指示する必要があります。次のコードは、mockTranslatorを構築時にGreetingに挿入する方法を示します。

var greeter:Greeting = new Greeting(mockTranslator);

構築時に、GreetingコンポーネントはmockTranslatorを受け取ります。これはTranslatorインターフェイスの具体的インプリメンテーションです。 これで、GreetingコンポーネントがTranslatorを呼び出すと、mockTranslatorが呼び出され、メソッドの呼び出しを記録します。

5. SUTの動作

次に、SUTを動作させる必要があります。モックを呼び出すことが想定されているメソッドを単に呼び出します。基本的には、SUTに実行を指示することになります。後で、SUTが動作の際に想定した方法でモックを使用したことを検証します。このサンプルでは、GreetingコンポーネントのsayHello()メソッドを呼び出します。これには、単にgreeterのsayHello()メソッドを呼び出します。

greeter.sayHello("Portuguese", "Paulo");

6. モックの想定の検証

最後の手順は、モックの想定を検証することです。mockTranslatorに対して、translateメソッドが、「English」、「Portuguese」、「Hello」をこの順序で引数として、1回呼び出されることを想定するように指示してあります。GreetingクラスのsayHello()メソッドを呼び出してこのクラスを動作させたので、greeterがmockTranslatorを想定どおりに呼び出したことを、mockTranslator.success()メソッドを使用して次のように検証します:mockTranslator.success();

success()メソッドは、メソッドの呼び出しが先に設定された想定と一致するかどうかを確認します。モックが想定どおりに呼び出された場合、success()はtrueを返します。そうでなければ、success()はfalseを返します。

通常は、success()メソッドを次のようにテストケースアサーションの中で呼び出します。

assertTrue(mockTranlator.errorMessage(),mockTranlator.success());

success()がfalseを返した場合、errorMessage()メソッドが検証失敗の原因を表す文字列を返します。失敗する場合、考えられる原因を以下に示します。

  • 想定しないメソッドが呼び出された
  • 想定したメソッドが呼び出されなかった
  • 措定したメソッドが想定しない引数で呼び出された
  • メソッドが想定した順序で呼び出されなかった

メモ:このサンプルでは、FlexUnitテストフレームワークを使用しています。ただし、Mock4ASは任意のActionScript 3.0ユニットテストフレームワークで使用できます。

Greetingユニットテストのサンプルコード全体を以下に示します。

GreetingTest.as

package org.mock4as.samples.greeting { import flexunit.framework.TestCase; import flexunit.framework.TestSuite; import org.mock4as.Mock; public class GreetingTest extends TestCase { public function GreetingTest(methodName : String) { super(methodName); } public function testGreetingInAnyLanguage():void { // create the Mock var Mock:mockTranslator = new MockTranslator(); // set expectations and behavior mockTranslator.expects("translate").withArgs("English","Portuguese","Hello").andReturn("Ola"); // inject the Mock var greeter:Greeting = new Greeting(mockTranslator); // exercise the class you are testing greeter.sayHello("Portuguese", "Paulo")); // verify Mock behavior assertTrue(mockTranslator.errorMessage(),mockTranslator.success()); } } }

MockTranslator.as

import org.mock4as.Mock; import org.mock4as.samples.greeting.Translator; class MockTranslator extends Mock implements Translator { public function translate(from:String, to:String, word:String):String { record("translate", from, to, word); return expectedReturnFor("translate"); } }

次のステップ

モックオブジェクトは、すべてのテストに必要なわけではありませんが、ユニットテストの際に発生するいくつかのニーズに応えてくれます。この記事で紹介したサンプルはきわめて単純ですが、同じ手法を使ってもっと複雑なテスト作業も実行できます。Mock4ASは、一般的に使用されているモックオブジェクトフレームワークの多くと同じ動作とAPIを持つように設計されています。

モックオブジェクトとテストコードの詳細については、以下のリファレンスを参照してください。

モックオブジェクトを使えばオブジェクトを単体でテスト(ユニットテスト)できますが、システム全体が意図したとおりに動作するかどうかを検証するには、ファンクションテストを使用します。Flexアプリケーションに対する自動ファンクションテストの詳細については、Paulo氏の記事「Selenium RCによるFlash用の機能テストの作成と実行」を参照してください。

必要条件

この記事に必要な予備知識

ActionScript 3.0とオブジェクト指向の原理を十分に理解するとともに、ユニットテストに関する多少の知識を持っている必要があります。

ユーザーレベル

上級