ひよっこ。

I want to…

Posts Tagged ‘XUnit’

Selenium2のInternetExplorerDriverでエラーになった

Posted by hikaruworld : 2011 7月 26

Selenium2が便利と聞いて試してみたら、InternetExplorerDriverが起動しなくてハマったのでメモしておきます。

取りあえず依存関係をpom.xmlに追加して、以下がテストメソッド(基本チュートリアルのコピペ)になります。

@Test
public void testname() throws Exception {
    WebDriver driver = new InternetExplorerDriver(/*ieCapabilities */);

    driver.get("http://www.google.com");
    WebElement element = driver.findElement(By.name("q"));
    element.sendKeys("Cheese!");

    element.submit();

    driver.quit();
}

これを実行すると、以下のエラーが出た

org.openqa.selenium.WebDriverException: Unexpected error launching Internet Explorer. Protected Mode must be set to the same value (enabled or disabled) for all zones. (WARNING: The server did not provide any stacktrace information)
Build info: version: ‘2.0.0’, revision: ‘12817’, time: ‘2011-07-14 23:50:51’
System info: os.name: ‘Windows 7’, os.arch: ‘x86’, os.version: ‘6.1’, java.version: ‘1.6.0_20’
Driver info: driver.version: InternetExplorerDriver
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:131)
at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:105)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:402)
at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:101)
at org.openqa.selenium.ie.InternetExplorerDriver.setup(InternetExplorerDriver.java:103)
at org.openqa.selenium.ie.InternetExplorerDriver.(InternetExplorerDriver.java:61)
// 以下省略

えー、と思って取りあえずissueを検索してみたら、Issues1795を発見。
あと、SeleniumのWikiにもInternetExplorerDirverにRequired Configurationという記述があります。

つまり、IEのインターネットオプションのセキュリティ設定で全てのゾーンの保護モードを会わせろとのことのようです。
って、英語でログにそのまま書いてあるじゃんorz…
(Issues1795#comment19の添付画像を参照)

というわけで全部有効に設定して再度確認。

OKでした(-

おまけ、Issues1795#comment1にあるように設定したら、指摘されているようにクラッシュしました。残念。

DesiredCapabilities ieCapabilities = DesiredCapabilities.internetExplorer();
ieCapabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
WebDriver driver = new InternetExplorerDriver(ieCapabilities);

以上です。

広告

Posted in program | タグ: , , , , | 1 Comment »

単体テストの品質分析に関する妄想(途中)

Posted by hikaruworld : 2011 4月 3

単体テストの品質分析に関して、ちょっとまじめに考えている(現在進行形)。
(普段何気なくやってる事を整理しただけなので、別にたいしたことを書いている訳ではない)
単体テストで品質分析を行う場合、
  • プロダクトコードの検証
  • テストコードの検証
の二つの視点で考えるようにしている。

プロダクトコードの検証

コードフォーマットに起因する問題分析

実施方法::CheckStyle,CPDなどを利用する(Javaの場合)
目的::読みやすいクリーンなコードを維持する事で、読み違いやミスなどを防止する。

フォーマット規約に反していないか、冗長なコードがないか、重複したコードがないかを検証する。
問題があった場合に、対象のコードに対応が必要かどうかを検討する。

プログラムコード構造に関する問題分析

実施方法::FindBugs,PMDなどを利用する。
目的::実装中の考慮漏れをコードの構成側から分析する。

構造上に潜在的に危険なコードがないか検証する。
問題があった場合は、問題の大きさを考慮して対応が必要かどうかを検討する。
ただし、これらのツールは問題の大小に応じて修正するかどうかを検討する場合が多い。

#  FindBusgとPMDを別途あげているのは、これらのコード解析方法が異なるため。
# どうせ機械的に実行する以上、いろんな観点から分析できるようにしておく。

テストコードの検証

単体テスト自体の実行のタイミングはSCMへのコミット単位となっている。
そのためテストコードの検証は開発作業の流れの中に取り込む形で考えるため、
以下のような手順で行う。

  1. TDDを用いて、プログラムのクラス、メソッド単位で、仕様が網羅されているか、
    異常系の例外ケースを考えているか考慮しながら設計(テストケース)と実装を行う。
  2. 実装後にカバレッジを確認し漏れている部分を確認する。
  3. 漏れている部分の内、構造上網羅していなければまずい部分に関してはテストケースを追加する。

# イメージ的には、ブラックボックス(1) -> ホワイトボックス(2)というイメージ。ざっと絵を書いてみた。

リグレッションが発生した場合は、以下の点を考慮しながら確認を行う。
なお、リグレッションが発生するタイミングはテストコードの実行されるタイミング=SCMコミット時である。

  1. 仕様が追加ないしは、変更されたことに起因するリグレッションかどうかを確認する。
  2. 実装した内容に問題があったことに起因するリグレッションかどうかを確認する。
  3. 1の場合は、仕様網羅性を考慮してテストケースの確認を行う。
  4. 2.の場合は、構造網羅性を考慮してテストケースの確認を行う。

仕様網羅率を目的とした検証

プログラムのメソッドレベルのI/Oレベルで、仕様を網羅する形でテストコードが実装されていることの検証を行う。
観点としては、これらの項目がテストケースとして加味されていることに観点を当てる。

  • 同値検証
  • 境界値検証
  • 異常系の例外ケース

構造網羅率を目的とした検証

カバレッジレポートを確認しながら、構造上の漏れがないことを検証する。
実行されていないプロダクトコードに関しては、必要性の有無を考慮した上で、
対応したテストコードの実装を行うか検討を行う。

  • 命令網羅率が漏れなく網羅されていること
  • 分岐網羅率が漏れなく網羅されていること

なお、構造網羅率は実装と効果に関するトレードオフを考える必要がある。
たとえばPOJOなクラスのgetter/setter、発生し得ない例外など明らかに不要なテストもあるので、
そこにパワーを使うくらいなら、プライオリティの高い複雑な共通部などに注力した方がよい。

網羅されていない部分に関して

ユニットテストでの実装可能性を考慮する。
実装内容によっては非常に困難なため、手動テスト(デバッグなどブレークポイントを利用した検証)の検討を行う。
実装可能であれば、処理が漏れている部分に関しては追加のテストを実装する。
なお、この際プロダクトコードを変更する事でテストが容易なようであれば、修正を検討する。

修正範囲に関する検証

実装が追加されたコード、削除されたプロダクトコードに関して、
テストコードの修正も適切に行われているかどうかを検証する。
基本的にはプロダクトコードよりもテストコードを先に書いている以上、基本発生しない。
但し、追加されたプロダクトコードによって仕様網羅率が変更される可能性があるため、検証を行う必要がある。

リグレッションに関する検証

テストコードの実行結果からリグレッションの有無、テストの失敗が頻発してる部分をを検証する。
テストコードの実行履歴に関しては、コミット単位で実行されるユニットテストケース履歴から検証する。
特に以下のようなリグレッションが発生した場合は焦点を当ててみる事。
  • ユニットテストの継続的な失敗が発生している場合。
  • リグレッションが発生している場合(修正したテストケース以外がリグレッションしている場合)
  • インターフェイス層を超えた形でエラーが失敗しているケース

層別の公開インターフェイスの変更に伴う検証

インターフェイスが変更されている場合は影響が多きくなるため、整合性に問題がないか参照部分の検証を行う。

おまけ

単体テストと結合テストを絡める形で網羅できればいいというのが実は本音。
品質分析をプログラミングテスト工程!の最後にやらなきゃ行けないというのはちょっと理解できない。CIのタイミングがベターだと思う。

etc…

以上です。

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

FlexUnit4 補足第3回 – ユーザカスタムメタデータパラメータ –

Posted by hikaruworld : 2009 7月 9

fxug@富山 補足第3回です。
なんだかんだで、補足が多いですねorz…。

さて今回は、User Defined Metadata Parameters
つまり、ユーザによって定義されたメタデータに付与されるパラメータの話になります。
ようするに[Test(milestone=”piyo”)] のことですね。

勉強会でも触れましたが、FlexUnit4のメタデータにはユーザが
好き勝手に任意でkey=value形式でパラメータを設定する事が出来ます。

この値(正確にはメタデータ)は、テスト完了後に参照する事が可能なメソッドが提供されており、
利用したい場合はIRunListenerを実装するクラス(RunListenerなど)を用いてイベントを差し込む事で、
結果の一覧を取得する事が可能になります。

まずは、RunListenerの確認をば。
以下のソースを参照ください。

/** テストクラスの開始時 */
public function testRunStarted( description:IDescription ):void {}
/** テストクラスの完了時 */
public function testRunFinished( result:Result ):void {}
/** [Test]の開始時 */
public function testStarted( description:IDescription ):void {}
/** [Test]の完了時 */
public function testFinished( description:IDescription ):void {}
/** テストが失敗した場合 */
public function testFailure( failure:Failure ):void {}
/** テストの前提条件チェックに失敗した場合 */
public function testAssumptionFailure( failure:Failure ):void {}
/** [Ignore]により無視された場合 */
public function testIgnored( description:IDescription ):void {}

様々なタイミングでイベントを差し込むメソッドが準備されています。
そのうち、org.flexunit.runner.IDescriptionを引数に取っているクラスに関しては、
getMetadata(String) というメソッドを用いて設定されたパラメータを取得することが出来ます。

では、実際にどのように取得できるのか確認してみます。

なお、IDescriptionを実装したクラスにorg.flexunit.runner.Descriptionがあり、org.flexunit.runner.Description.getAllMetadata()というメソッドが実装されています。
当初はこのクラスでパラメータの一覧を取ってみようと思い実行してみたのですが、
以下のようなtraceが出力されました。

Method not yet implemented

ソースをのぞいてみると。。。

public function getAllMetadata():XMLList {
	trace("Method not yet implemented");
	return new XMLList();
}

うーん。思いっきりダミー実装が返されていますw。まぁこの辺りはBeta版ということで、笑って許しましょう。
さて、話を戻します。
先ほども書きましたがgetMetadata(String) というメソッドを用いて設定されたメタデータをXML形式で取得可能です。

例えば、このテストの場合。

[Test(expected="RangeError", description="This one makes sure something works",issueID="12345")]
public function exceptionTest():void
{
	var ary:Array = new Array(-1);
}

ListenerはRunListenerを継承して、testFinished(description:IDescription)の実装をoverrideします。
(必要箇所のみ抜粋)

/**
 * {@inheritDoc}
 */
override public function testFinished( description:IDescription ):void {
	trace(description.getMetadata("issueID"));
}

traceした出力結果は以下の通り。

<metadata name="Test">
  <arg key="expected" value="RangeError"/>
  <arg key="description" value="This one makes sure something works"/>
  <arg key="issueID" value="12345"/>
</metadata>

返されるクラスはXMLですので、後は必要に応じてE4Xなり何なりで処理する事が可能です。
ただし、このgetMetadata(String)に渡すことの可能な引数String型は
Beta1では無視されます。実装が以下の通りなので。

public function getMetadata( type:String ):XML {
	return _metadata;
}

本来は渡されたtypeのメタデータなりパラメータなりを取得してくれると思うのですが、
まぁ、これもβ版ということで笑って流しておきましょう。
正式版が出るまでは、自分でIDescriptionを実装しろってことですよね。

以上。

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

FlexUnit4Beta1 補足第2回 – XMLListener(→JUnitListener)による出力 –

Posted by hikaruworld : 2009 7月 8

お詫びと補足

この記事は当初UP後、間違いに気がついて大きく書き直してあります。
当初JUnit形式での出力はまだ不可能かと思っていましたが、
実際はJUnitListenerを利用して出力する事が可能と思われます。
なお、出来る限り元の内容を残しておこうかと思っていたのですが、修正しすぎて一部の内容を消しまってあります。


以下本文。

fxug@富山の補足 第2回になります。

IRunListenerのクラス階層

FlexUnit4Beta1では実行時のFlexUnitCoreにaddListener()の引数として、
任意のListenerを渡す事で実行結果を取得する事が可能です。
addLitener()出来るのはorg.flexunit.runner.notification.IRunListenerを実装したクラスになります。
ちなみに、FlexUnit4がデフォルトで提供しているクラスは以下の通りです。

  1. org.flexunit.listeners.UIListener
  2. org.flexunit.runner.notification.RunListener
    • org.flexunit.internals.listeners.FluintDisplayListener
    • org.flexunit.internals.TextListener
    • org.flexunit.listeners.JUnitListener
    • org.flexunit.runner.Result#Listener
  3. org.flexunit.runner.notification.async.WaitingListener
  4. org.flexunit.runner.notification.async.XMLListener

このうち1.UIListenerと2.RunListenerを除く3.と4.のクラスは、
org.flexunit.runner.notification.IRunListenerをさらに継承したインターフェイス、
org.flexunit.runner.notification.IAsyncStartupRunListenerを実装しています。

1.UIListener

これまでの記事でも利用しているように、
TestCaseBaseに対して実行結果をGUI上にハンドリングされるような動きをするようです。

2.RunListener

IRunListenerが実装されたテンプレートメソッドのようです。
ASDocには何らかのテストの前後に何らかのイベント処理を設定したい場合には
必要に応じてこのクラスを拡張するようにと書かれています。

なおRunListenerに関しては、
さらにFluintDisplayListener, TextListener, JUnitListener,Result#Listenerの4つのクラスに継承されています。

FluintDisplayListenerはおそらく統合されたFluint向けの機能と名前から勝手に解釈してスルーします(汗
TextListenerは実装からみるにコマンドライン出力に利用されているようです。
JUnitLitenerはJUnit形式で出力してくれるという事でしょうか。このクラスに関しては後で補足することになりました。
Result#Listenerに関しては外部からは参照できない内部クラスですのでこれも勝手にスルーします。

3.WaitingListener

このクラスは実装のないテンプレートクラスになっているようで
コンストラクタで5000ms遅延させた後、WaitingListener.readytureに設定し
AsyncListenerWatcher.LISTENER_READY をdispatchしているのみのようです。

その他の実装(testRunStarted, testRunFinishedなど。。。)は空になっています。
当初はこのクラスがテンプレートクラスとして利用されるのかなと思っていたのですが、
このクラスはoverrideを許可されていないのでそういった利用方法ではないようです。

4.XMLListener

に関しては出力結果をJUnitライクなXML形式で出力されるだろうと妄想しています。
実際はJUnit形式の出力はJUnitListenerで行われます。
XMLLitenerはXML形式で出力を行いますが、このXMLを誰がどう解釈するかはちょっとかりませんでした。
今後FlexUnit4独自のフォーマットに対応した何らかのアプリケーションが同梱されて出来てくるのでしょうか。。。

さて今回の話題は、このXMLListenerの使い方になります(→上記のように後ほどJUnitListenerに遷移します)。

XMLListenerの実行とその結果

とりあえず、FlexUnitCore.addListener()XMLListenerを与えて、実行してみます(必要部分のみ抜粋)。
なおこのテストはAirプロジェクトで行っています。利用しているIDEはFlashDevelop3RTM1か、FlexBuilder3です。

var core:FlexUnitCore = new FlexUnitCore();
core.addListener(new XMLListener());

但しこのまま実行しても、出力結果は以下の通りでエラーになってしまいます。

[ERROR] FlexUnit4 Listener [object XMLListener] failed to start

エラーの意味はわかりますが、理由が全くわからないのでソースを覗いて見ました(コンストラクタ部分を抜粋)。

public function XMLListener( projectName:String = "", contextName:String = "" ) {
    this.projectName = projectName;
    this.contextName = contextName;

    socket = new XMLSocket ();
    socket.addEventListener( Event.CONNECT, handleConnect );
    socket.addEventListener( IOErrorEvent.IO_ERROR, errorHandler);
    socket.addEventListener( SecurityErrorEvent.SECURITY_ERROR,errorHandler);
    socket.addEventListener( Event.CLOSE,errorHandler);
    try
    {
       socket.connect( server, port );
    } catch (e:Error) {
        trace (e.message);
    }
}

ソースコードを見ればわかりますが、XMListenerは内部でXMLSocketを生成して、XML形式のソケットを送っていることがわかります。
てっきり、ファイルか何かに書き出していると思っていたのですがそうではないようです。

おそらくIOErrorEvent.IO_ERROR,SecurityErrorEvent.SECURITY_ERRORのいずれかが発生していると考えられますので、
念のため確認をしておきます。

private function errorHandler(event:Event):void {
    // trace文を追加
    trace(event);
    dispatchEvent( new Event( AsyncListenerWatcher.LISTENER_FAILED ) );
}

以下は実行結果のログになります。

[IOErrorEvent type=”ioError” bubbles=false cancelable=false eventPhase=2 text=”Error #2031: Socket Error. URL: 127.0.0.1″ errorID=2031]

やはり思ったとおり、接続失敗で落ちているようです。
しかし、わざわざXMLSocketで通信しているのは不思議な感じがします。
FlexUnit4の正式版が出るまでにはAir版のクライアントサーバアプリを同梱してそれを起動して使うようにしようと考えているのでしょうか。
ちょっと背景が気になりますね。。。

エラーを回避

さて、今後の予想はともかく上記の状態ではXMLSocketサーバがないことには話になりません。
原因がXMLSocketサーバに接続できていないとのことでしたので、
XMLSocketサーバを簡単に書いてテストしてみます。

ここはJavaで書いています。
Adobeのリファレンスにも(Javaでの)簡単なXMLSocketサーバの書き方の説明があるので参考になるかと思います。
*ソケット接続 – Java XML ソケットサーバーの作成および接続

なお、XMLListenerのソースを見ればわかると思いますが、XMLListener内部で生成されるXMLSocketは8765ポートを利用していますので、
それに併せる形で書く必要があります。

[Inspectable]
public var port : uint = 8765;

[Inspectable]
public var server : String = "127.0.0.1"; //this is local host. same machine

基本的にはソケット接続とほぼ同じですが、検証に利用した手抜きのソースを張っておきます。

import java.io.*;
import java.net.*;

public class SimpleServer {
    private static final int SERVER_PORT = 8765;

    public static void main(String[] args) throws Exception {
        new SimpleServer(SERVER_PORT);
    }
    public SimpleServer(int port) throws Exception {
        System.out.println(">> Starting SimpleServer");
        ServerSocket socket = new ServerSocket(port);
        Socket incoming = socket.accept();
        BufferedReader readerIn = new BufferedReader(new InputStreamReader(incoming.getInputStream()));
        System.out.println("Enter EXIT to exit.\r");

        String str;
        while ((str = readerIn.readLine()) != null) {
            System.out.println("Echo: " + str + "\r");
        }
        incoming.close();
        socket.close();
    }
}

XMLSocketサーバを起動した上で再度テストを実行してみます。
以下はログになります(複数のテストを実行しています、これは使ったテストケースが別のため。)。

[INFO] FlexUnit4 test::HamcrestTest.assertBetweenFalse .
[INFO] FlexUnit4 test::HamcrestTest.assumTahtTestNG .
[INFO] FlexUnit4 test::HamcrestTest.assumTahtTestNG I
[INFO] FlexUnit4 test::HamcrestTest.assertThrow .
[INFO] FlexUnit4 test::HamcrestTest.assertThatAnything .
[INFO] FlexUnit4 test::HamcrestTest.assumeThatTestOK .
assumeThatOK….
[INFO] FlexUnit4 test::HamcrestTest.assertBetweenTrue .
[INFO] FlexUnit4 test::HamcrestTest.assertDescribedAs .
[INFO] FlexUnit4 test::HamcrestTest.assertThatIsA .
[INFO] FlexUnit4 test::HamcrestTest.assertThatAllOf .
[INFO] FlexUnit4 test::HamcrestTest.assertEither .
[INFO] FlexUnit4 test::HamcrestTest.assertThatGreaterThan .
[INFO] FlexUnit4 test::HamcrestTest.assertNot .
[INFO] FlexUnit4 test::HamcrestTest.assertThatAnyOf .
[INFO] FlexUnit4 test::HamcrestTest.assertThatBoth .
[INFO] FlexUnit4 Time: .102
[INFO] FlexUnit4 OK (14 tests)

無事実行されたことが確認できました。

XMLSocketサーバ側はどうなっているでしょうか。
送られた文字列を標準出力する簡単な実装なので、ログを確認してみます。

<startTestRun totalTestCount='14'  projectName='' contextName='' />
    <testCase name='assertBetweenFalse' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assumTahtTestNG' testSuite='test::HamcrestTest'  status='ignore'/>
    <testCase name='assumTahtTestNG' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertThrow' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertThatAnything' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assumeThatTestOK' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertBetweenTrue' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertDescribedAs' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertThatIsA' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertThatAllOf' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertEither' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertThatGreaterThan' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertNot' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertThatAnyOf' testSuite='test::HamcrestTest'  status='success'/>
    <testCase name='assertThatBoth' testSuite='test::HamcrestTest'  status='success'/>
<endOfTestRun/>

こちらもXMLSocketが正常に送られていることがわかります。

但し、疑問点が1つ。
FlexUnit4をXMLListenerで出力を行った際はJUnit4と同様の書式で出力されると予想していたのですが、
そうではないようです。

これは、正式版に向けてJUnitと同様の形式でFlexUnitが実装されていくのか、
それともFlexUnit4独自のレポート形式になるのかどちらになるのでしょうか。。。

JUnitListenerによるレポート出力

IRunListenerを実装したRunListenerを継承したJUnitListenerを用いてJUnit形式の出力を行います。
ただし、このクラスを含むライブラリFlexUnit4CIRunner.swcはダウンロードした版には含まれておらず、
直接ソースから取得する必要がありますので注意してください(探しても見つからない訳だ。)。

JUnitListenerもXMLSocketを利用して通信を確立させていますので、同様のSocketServerを起動しておく必要があります。
また、JUnitListenerの接続ポートは現時点では1024が指定されています。
以下が出力結果になります(これも慌てて確認したのでテストクラスが違います)。

<testsuite errors="0" failures="0" name="exception.ExceptionHandlingTest" tests="2" time="0">
  <testcase classname="exception.ExceptionHandlingTest" name="illegalOperationErrorTest" time="0"/>
  <testcase classname="exception.ExceptionHandlingTest" name="exceptionTest" time="0"/>
</testsuite>
<testsuite errors="0" failures="0" name="exception.ExceptionHandlingTest" tests="2" time="0">
  <testcase classname="exception.ExceptionHandlingTest" name="illegalOperationErrorTest" time="0"/>
  <testcase classname="exception.ExceptionHandlingTest" name="exceptionTest" time="0"/>
</testsuite>

このような形でJUnit形式での出力がおこわれます。
(実際に解釈されるかは未確認なので注意。なんかたりない気がする。。。)

ところで勉強会でも少し話しましたが、ようやくFlashBuilder4でFB-18873がfixされたようですので、次のビルド版が楽しみです。

以上。

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

FlexUnit4(beta1)でのTheoryとDataPoint

Posted by hikaruworld : 2009 7月 7

fxug@北陸in富山で発表した資料のうち肝心なとこが抜けていて、@shoitoや、@coelacanthに突っ込まれたので、暫くはblogで補足していきます。

といいつつ、以下の内容は動作確認をしながら検証しただけなので、本当にあっているかどうかは自信がないので。
間違っているかもしれません。(ソースまでは覗いていない。。。)

目次

1.[Theory]と[DataPoint]
2.[DataPoints]
3.テストの引数に複数パラメータを与える
4.[Theory]と[Test]の併用
5.疑問点

1.[Theory]と[DataPoint]

FlexUnit4の新機能として、[Theory][DataPoint]というものが提供されています。
【Theory】は和訳すると【定理、理論】と訳されますが、このFlexUnit4のメタタグで言われるTheoryとは、
上記のようにメタ[DataPoint]が成り立つための理論と解釈すればいいのかなと思います。

実際の挙動としては[DataPoint]で定義された値を[Theory]で検証を行うといった機能性を持っているようです。

ただ、この使い方はドキュメントを見ただけではいまいちわかりにくいものがあります。
というかドキュメントのサンプルソースを見ても全く挙動がわかりません(汗。。。
のですが、サンプルソースを書かないと話が進まないので以下ソースです。

package test 
{
    import org.flexunit.assertThat;
    import org.flexunit.assumeThat;
    import org.flexunit.experimental.theories.Theories;
    import org.hamcrest.core.isA;
    import org.hamcrest.core.not;
    import org.hamcrest.number.greaterThan;
    import org.hamcrest.object.instanceOf;
    
    /**
     * Theoryの実行確認クラス
     * @author hikaruworld
     */
    [Suite]
    [RunWith("org.flexunit.experimental.theories.Theories")]
    public class TheoryTest 
    {
        //--------------------------------------------------------------------------
        //
        //  DataPoints
        //
        //--------------------------------------------------------------------------
        [DataPoint]
        public static var number:Number = 5;
        [DataPoint]
        public static var number2:Number = 10;
        [DataPoint]
        [ArrayElementType("String")]
        public static var stringValues:Array = ["one","two","three","four","five"];
        //--------------------------------------------------------------------------
        //
        //  Theories
        //
        //--------------------------------------------------------------------------
        /**
         * この理論の検証は[DataPoint]がこのメソッドの引数と一致するStringの場合にのみ実行される。
         * ここで注力するのは、[DataPoint]の型がNumberのものは検証されるが、[DataPoint]の型がArrayのものは検証されない点
         * 以下は実行結果の出力.
         * TODO...
         */
        [Theory]
        public function testNumber( numberValue:Number ):void 
        {
            trace("check theories ...:" + numberValue);
            assumeThat(numberValue, isA(5));
            trace( "numberValue check Ok is... : " + numberValue );
        }
        /**
         * このテストは、[DataPoint]で指定されている型がArrayの場合にのみ実行され検証が行われる。
         *
         */
        [Theory]
        public function testArray(arrayValue:Array):void
        {
            //TODO...
            //assumeThat();
        }
     }
}

この実行結果は以下の通りになります。

7/7/2009 10:29:02.781 [INFO] FlexUnit4 test::TheoryTest.testNumber .
check theories ...:5
numberValue check Ok is... : 5
check theories ...:10
7/7/2009 10:29:02.828 [INFO] FlexUnit4 test::TheoryTest.testArray .
7/7/2009 10:29:02.843 [INFO] FlexUnit4 Time: .056
7/7/2009 10:29:02.843 [INFO] FlexUnit4 OK (2 tests)

ログを見てもらえばわかると思いますが、これは上記のようにメタタグ[DataPoint]が設定されているプロパティに対して、
メタタグ[Theory]が付与されているメソッドで引数の型が一致する[DataPoint]の検証が行われています。

そして、TheoryTest.testNumberでは[DataPoint]が付与されているnumber,number2の検証を行っており、
assumeThat()が一致しないnumber2に関しては以降の処理が実行されず、以下のログが出力されていないのがわかります。

numberValue check Ok is... : 10

どうやらこの辺りは、引数から暗黙に解釈されているらしく混乱しました。
但し、暗黙的に解釈しているとしても引数の型を「Object」や「*」を指定した場合には下記の警告にもあるようなエラーが発生しました。
内部的にはis演算子を利用して、インスタンスを比較しているのかもしれません。

警告

ちなみに、もし[Theory]の引数に対応する[DataPoint]が存在しなかった場合、
以下のエラーが投げられることになります。

7/7/2009 13:01:26.062 [WARN] FlexUnit4 1 test::TheoryTest.hoge Never found parameters that satisfied hoge method assumptions.  Violated assumptions:

2.[DataPoints]

[DataPoints]は配列要素の中身を一個ずつ利用する際に用いられるようです。
以下ソースです(必要な部分のみ抜粋)。

[DataPoints]
[ArrayElementType("String")]
public static var stringValues:Array = ["one", "two", "three", "four", "five"];

[Theory]
public function checkStr(str:String):void
{
	trace( "str : " + str );
}

以下はログになります。

7/7/2009 16:03:44.468 [INFO] FlexUnit4 test::TheoryTest.checkStr .
str : one
str : two
str : three
str : four
str : five

上記の通り、[DataPoints]で指定されている配列が分解されて与えられ5回実行されていることがわかります。

もし、配列ではない型に[DataPoints]を与えた場合にどうなるか試してみましたが
まず、intやNumberのようなデータ型やクラスの場合だとどの引数にも合致せず正常に実行されませんでした。

3.テストの引数に複数パラメータを与える

これを利用することでテストメソッドに複数のパラメータの組み合わせを与えることが可能になります。
(必要な部分のみ抜粋)

[DataPoint]
public static var number:Number = 5;
[DataPoint]
public static var number2:Number = 10;
[DataPoint]
/**
 * このテストはNumber型の[DataPoint]の組み合わせを引数に取り実行される。
 */
[Test]
public function hoge(val:Number, val2:Number):void
{
	trace( "val : " + val );
	trace( "val2 : " + val2 );
}

以下はログ

7/7/2009 13:17:24.546 [INFO] FlexUnit4 test::TheoryTest.hoge .
val : 5
val2 : 5
val : 5
val2 : 10
val : 10
val2 : 5
val : 10
val2 : 10

見てもわかるように、5-5,5-10,10-5,10-10[DataPoint]上に設定されている全ての組み合わせ(6パターン)が
実行されていることがわかります。

動き的には、TestNG@Parametersや、@DataProviderといえばわかる方にはわかりやすいのではないでしょうか。

4.[Theory]と[Test]の併用

ここがちょっと疑問点なのですが・・・
[Theory]の意味や概要を確認していると,[Theory]による検証が失敗した場合は[Test]が実行されないと考えてました。
しかし、そういった挙動ではないようです。以下はソースです(相変わらず抜粋)。

[DataPoints]
[ArrayElementType("String")]
public static var stringValues:Array = ["one", "two", "three", "four", "five"];
/**
 * Theoryによる値の検証
 */
[Theory]
public function checkStr(str:String):void
{
	assumeThat(str.length, greaterThan(4));
	trace( "str : " + str );
}
/**
 * テストの実行
 */
[Test]
public function testStr(str:String):void
{
	trace( "str : " + str );
	Assert.assertTrue(str.length > 0);
}

以下のログを参照ください。

7/7/2009 16:21:17.906 [INFO] FlexUnit4 test::TheoryTest.testStr .
str : one
str : two
str : three
str : four
str : five
7/7/2009 16:21:17.921 [INFO] FlexUnit4 test::TheoryTest.checkStr .
str : three

[Theory]での前提条件検証assumeThat(str.length, greaterThan(4));によって
FlexUnit4 test::TheoryTest.checkStr ではthree以外が無視されていることがわかります(正確には無視ではなく、処理されないですが)。

その一方でその前提条件精査に関わらず、FlexUnit4 test::TheoryTest.testStrが実行されてしまっているのがわかります。
もうひとつ疑問なことが、[Theory]よりも[Test]が先行して実行されてしまっている点です。

これはRunWithでTheoriesを指定することを考えても、[Theory]と[Test]は混ぜるな危険ということになるのでしょうか?
うーん。。。
この辺りは後でソースかテストソースを覗いてみようかと思います。

5.疑問点

1. TheoryとTestの併用に関して
2. [DataPoints]による配列分解の実装

以上ー。
気が向けばまとめます。

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 »