モックとスタブの違い 一番良いのはどっちか?

結局一番良いのはどっちか?

これは自信を持って答えるのが難しい問いだと思う。個人的に私は状態中心のテスタを行ってきたが、これまでそれを変えようという理由が見当たらなかった。相互作用中心テストにも心がひきつけられるほどの利点は見当たらないし、私が関心があるのは実装にテストが結合することで起きる事についてなのだ。

相互作用中心のテストをお遊び以上にはどのテストでもやってみようとしないことの不利益というのも悩むところだ。[私が本を共著もした] テストドリブン開発自体から私が学んできたように、 真剣にやろうとしないでテクニックを判断しようと困ることはよくあることだ。私は相互作用中心テストを用いる人達をほんとうに多く知っているし、その人達はこのテクニックでとてもハッピーになれている優れた開発者達なのだ。

だからもし相互作用中心のテストがあなたの興味をそそるようなら、私は試しにやってみることをお勧めする。特に、相互作用中心テストが改善策になりうる幾つかの部分で問題を抱えているなら、試す価値はある。私は今、改善を見ることができる場所は主に2つあると思う。一つは、テスト失敗時に、テストが [問題の箇所で] はっきりと中断せず、問題がどこにあるかを伝えてくれないために、デバッグに多くの時間を費やしている時。(この問題の改善は適当な粒度のオブジェクトの集まりに対して状態中心テストをすることでもできるだろう)2つ目の部分は、もしオブジェクトが十分な振る舞いを備えていなかった時に、相互作用中心のテストはより振る舞いが豊かなオブジェクト [=必要な振る舞いを持つオブジェクト] を作成するように促す可能性だ。

もっと知るには

モックによる相互作用スタイルのテストについてより調べるのに、OOPLSA*12004で受理された素晴らしいOOPSLAの論文がある。残念なことにまだそれは会議前制限がかかっていてオンラインでない*2。テストテクニックについては jMock, nMock, EasyMock, .NET EasyMock といったツールのウェブサイトを見ることでも調べられる(他にもモックツールはあるので、このリストが完全なものとは思わないでほしい)。モック・オブジェクト・コミュニティはモックオブジェクトサイト*3をモックオブジェクトについての情報のポータルにしようとしているが、今のとこまだ整っていない。XP2000ではオリジナルのモックオブジェクトの論文があったが、これはやや古くなってしまった。

*1:Object-Oriented Progamming, Systems, Laguages & Applications http://www.oopsla.org/

*2:この記事が書かれたのは2004年7月

*3:MockObjectsのWiki(更新が止まっている):http://www.mockobjects.com/FrontPage.html

ControlがApplication/WindowedApplicationを継承する方法

ここでこのアイディアを知った。

code behind

ここでは、MXMLActionScriptを切り分け、リンクさせる方法を書いている。

基本的には、ActionScriptクラスをMXMLで継承するという方法。
これを適用すると下のようになる。

AppclicationControl.as

public class ApplicationControl extends Application {
privatge view:Applicatoin = this; //viewという変数使いたいだけで、必ずしもいらない
public var ti:TextInput;

}

AppView.mxml



...

ただこれは基本的にActionScriptMXMLを切り分けるというアイディアなので、両者は分離してない(2つで1つ)。しかもControl内で操作する必要のあるView内コンポーネントを全てControl内でも定義しないといけない。

どちらかというとViewがApplication/WindowedApplicationを継承する方がいいと思う。FlexBuilderを使っている場合は、Flex/AIRプロジェクトを作る時にMXMLをベースにした雛形ができるので、それにも合わせやすい。

ViewがApplication/WindowedApplicationを継承する方法

この方法ではアプリケーションのルートがViewになるため、全て(のインスタンス)はViewから生成しないといけない。よって普通はControlからViewを生成するとこが、この場合ViewからControlを生成することになる。

View(AppView.mxml)は以下のようになる。



...

Control(ApplicationControl.as)は以下のようになる。

public class ApplicationControl {
//View=Application
private var view:AppView = Application.application as AppView;
...
}

冗長なコードになるが、ルートだけなので混乱はしないと思う。

Flex/AIRアプリでのルートエージェント

PACのコンポーネント指向の見方ではControlが中心的な役割を果たす。ルート(のエージェント)でも同じだ。しかしFlex/AIRアプリはApplicationまたはWindowedApplicationがルートクラスになる。そしてこれらはDisplayObjectだ。


PAC的にはDisplayObjectはView(本当のPACではPresentation)に含ませたい。
あくまで統一性の問題だが、Viewとロジック(ControlやModel(=Absolution))は切り分けたるためにも、他のエージェントと同じようにしたい。


その方法を2つ考えた。

  1. ViewがApplication/WindowedApplicationを継承し(Application/WindowedApplicationタグがルートになる)、Controlにロジックを埋め込む(ここまでは他のエージェントと同じ)。ただし、ControlのインスタンスはViewから生成する。
  2. ControlがApplication/WindowedApplicationを継承し、ViewはそのControlを継承する(Controlタグがルートになる(実質View=Controlとなる))。

データバインディング

これまで決めたようにViewはMXMLで記述する。MXMLでデータバインドを使うのは、たとえば DataGrid の dataProvider に(別のクラスの)変数を指定して、その変数の値が変わった時に自動的にDataGrid にも反映させるような時だ。
そうするには dataProvider に{}に入れた変数を指定する(を使う方法もある)。この場合、以下のようなソースになる。








この gridData に [Bindable] をつけてないと「データバインディングでは"gridData"への代入を検出できません。」という警告が出る。当然そのまま実行しても gridData の値とは連動しない。

よってModel側のgridDataは以下のように宣言されてないといけない。

public class Model {
[Bindable]
var gridData:Object
・・・
}


ちなみに、コンパイラで作られたソースを見ると、このView MXMLの初期化時にMyModelのインスタンスが一時的にnewされている。MyModelのインスタンスは、Controlから代入されるので、View(=このDataGrid)でnewする必要はない。PACの利点である(切り捨てたが)、ViewがModelの初期化に縛られないことも失う実装になる(繰り返すと、想定してるアプリは大袈裟でないのでこの利点は全く関係ない)。


(上のことを回避する意味でも)以下のようにASを使うこともできるが、dataProviderのとこで「データバインディングでは"model"への代入を検出できません。」と警告が出る。
なぜ前のコードがよくて下のがダメなのかは分からないが、MXMLではなるべくタグを使え、と覚えておく。










イベントによるオブジェクト間のコミュニケーション

さて前回の続き、というか今回のモデルの実装法の続き。

今度はイベントを使ったオブジェクト間のやり取りについて。View(Presentation改め)とControl、Controlと別エージェントのControlは疎結合のために原則イベントで協調するようにする。
ModelとControl間は原則Controlからしか働きかけないので含めてないが、必要な場合はView-COntrolと同じになる。

ActionScript3ではEventDispatcherクラスがイベントの送出/受信を行うので、必然的にControlはEventDispatcherを継承することになる。(ただしこれはViewのためではない。ControlはViewに対する参照を持っている(持たざるを得ない)のでイベントを使う必要はない参照)。

一方、Flex/FlashのビジュアルオブジェクトはDisplayObjectを継承している(これがEventDispatcherを継承している)ので、Viewは気にしなくてもイベントが扱える。


ということで仕組みとしては、

  • ViewとControl:Viewからイベントを送出して、Controlでイベントを受け、イベントの種類によってControlが処理をする
  • Control(子)とControl(親):子からイベントを送出して、親でイベントを受け、イベントの種類によって親が処理をする。

しつこいが、ControlからView、親Controlから子Controlは親側が参照を持つのでイベントは使わない。


そして、この仕組みを実装しようとしたら(以下View->Controlについてのみ書く。Control->Controlも同じ)、次のような感じになると思う。

View





Control

public class Control extends EventDispatcher {
var view:View;
public function Control() {
this.addEventListener("イベントA",handlerA);
}
 ・・・
}


しかし、こうはできない。Viewの方はいいが、ControlでこのようにaddEventListenerしてもイベントAが受け取れないのである。


なぜかというと、Viewで送出されたイベントはViewにしか聞こえないからだ。
AS3のイベントを調べると、必ずイベントフローについて知るはずだ。特にこのサイトが分かりやすい。ビジュアルなので一発理解。
http://void.heteml.jp/blog/archives/2006/07/as3_eventflow2.html


それを知ると、親オブジェクトにもイベントがバブリングして(一方向であれ往復であれ)届くと思ってしまう。ただ注意しないといけないのは、それは親がDisplayObjectの場合だけ、つまり親にaddChild()された子DisplayObjectのイベントだけが親へそしてStageへと届くのだ。

だからViewとControlの場合、ControlがViewインスタンスを持っているとしてもViewのイベントはControlまで来ない。
イメージ的にはこんな感じ。

・・・当たり前なのだろうか?


ともあれこれを踏まえてControlのコードは以下のようになる。

public class Control extends EventDispatcher {
private var view:View = new View;
public function Control() {
view.addEventListener("イベントA",handlerA);
}
 ・・・
}

さらにViewのコードもControlに埋め込んでしまえる。

public class Control extends EventDispatcher {
private var view:View = new View;
public function Control() {
view.addEventListener("イベントA",handlerA);
view.addEventListener(FlexEvent.CREATION_COMPLETE, function():void {
dispatchEvent(new Event("イベントA"));
});
}
 ・・・
}

まるで飼い主と飼い犬のような関係だ。飼い主は犬にお腹が空いたらイベントAと鳴けと教えておいて(これがaddEventListener)、犬がお腹が空いた(上の場合FlexEvent.CREATION_COMPLETEが起きた時)らイベントAと鳴く(dispatchEvent)。そうすると飼い主が何かしら(handleA)してくれる。犬にはそれしかできない。
できれば飼い主に食事を持ってくるよう直接飼い主にアクセスしたいが(飼い主#feedDog()みたいに)犬にはその飼い主がどこにいるか分からないから鳴くしかなく、それで何が起きるかも飼い主次第なのだ。しかも飼い主は特定の人物とはかぎらない。指示ができて何かしらの行動ができればいいのだ。


そこまでして(?)この方法で得るとこはあるのかというと、一重に疎結合のためだ。そのおかげで飼い主は誰でもなれる。
イベントドリブンと聞いて思い浮かぶ非同期処理だけなら別に疎結合でなくてもいいが、疎結合はつまり再利用性を高め(=継承クラスを作るのに便利とか)、カプセル性を高める(=そのクラスで行うことが明確になる=責務の分離)ことが利点なのかと思う。


他の方法でも(仲介パターンとか)いいかもしれないが、ActionScript3の標準機構を使うならイベント方式が一番いい気がする。あまり自信がないが、とりあえずイベント方式で行ってみよう。