これは「Friendly Advent Calendar 2014 - Qiita」の記事です。
昨日は、テストでは同期処理で安定した操作をすることが重要なんだ!
でも・・・
ってとこまで書きました。
非同期の必要性 → モーダルダイアログのみ
はい、Friendlyの操作は基本同期で実行されます。
このおかげで、非常に安定したシステムテストを記述できるんですね。
しかし、たった一つだけ困るケースがあります。
それは、モーダルダイアログですね。
例えば、こんなコード普通に書きますよね?
void Func() { using(var f = new MyForm()) { if (f.ShowDialog() == DialogResult.OK) { SetData(f.Data); } } }
これをFriendlyでテストプロセスから呼び出すと、こんな感じです。
//ダイアログを表示して処理をする関数を呼び出す。
form.Func();
もちろん、同期で実行されるので、操作コードは固まります。
当たり前ですよね。
繰り返しますが、Func()が単に重くて時間がかかる処理なら、終わるまで待っていればいいのです。
そういうテストですからね。
でも、モーダルダイアログはそうはいきません。
誰かが操作しないと、先に進んではくれないのです。
どうしましょう?
別のスレッドを使う?
最初は、ダイアログを起動する処理か、もしくは表示されるダイアログを操作する処理をユーザーに別のスレッドでやってもらおうと思っていました。こんな感じです。
(昔考えてた時の雰囲気を再現するためThreadで書きますが、Taskでも同じことです。)
var t = new Thread(()=>form.Func()); t.Start(); //モーダルダイアログの処理 ・・・ //終了待ち合わせ t.Join();
まあ、一見悪くはないように見えます。
これくらシンプルだと実際問題も起きないでしょう。
しかしですね、マルチスレッドプログラミングをなめてはいけません。
例えば、form.Func()が呼ばれるのと、その次の「モーダルダイアログの処理」の最初の処理はどちらが先に呼ばれるかわかりませんね。
あと、仮に変数を共有することになった場合、その排他処理はどうしましょう?
別スレッドでAssert起きたときは?
とか考えることは増えます。
いやわかってますよ。ベストプラクティスに沿ったら問題はないのです。
しかし、これはテストコードです。
できるだけ間違いが入り込む余地は減らしたいのです。
あと、「ボタンを押してモーダルが出るときスレッド(Task)使わなきゃなんないの?」ってみんな嫌がるかなーって想像しました。
ファーストユーザーである僕も嫌でしたw
そもそも登場スレッド増やす必要ないでしょ?
そうなんですよ。
昨日も書きましたが、Friendlyは別プロセスのスレッドを操っているのです。
普通は非同期に動作している二つのスレッドをわざわざ同期させているのです。
これをシェイクハンドしないようにすれば良いだけですよね。
そしたら、非同期なのです。
これを実現するためには、Friendlyがライブラリとしてこの機能を提供する必要があります。
目的はモーダルダイアログ対応なのですが、ここではインフラ層として、「非同期」機能を実現しなければならないのですね。
Asyncクラス
Friendlyでは呼び出しを非同期にするためにはAsyncクラスを使います。
非同期にするために「Async」クラスを使う。
非常に直感的です。
で、どうするかというと、引数に混ぜるのです。
どこに入れてもOKです。
Async a1 = new Async(); form.Func(a1); //どこに入れてもOK form.OnClick(new EventArgs(), new Async()); form.OnClick(new Async(), new EventArgs());
しかも、このAsyncは非同期で実行させた処理の進捗を管理できる優れもの!
Async a = new Async(); form.Func(a1); //進捗どうですか? a.IsCompleted; //終わるまで待つ a.WaitForCompletion();
あ、もちろんプロパティーも非同期にできますよ。
モーダルダイアログはいたるところに潜んでいますよ。
実際コンボボックスの選択を変えたときにダイアログでるアプリとかありますからね・・・。
form.Text(new Async(), "abc");
プロパティー、フィールドはFriendlyでは関数的にも呼び出せるのです。
(当然普通の呼び出しも可。てかそっちが普通。)
引数一個ならsetter,なければgetter。
これはまあ、dynamicになる前のFriendlyの仕様から来ています。
Friendlyの基本(.NetFramework4.0が使えない場合) - 株式会社Codeer (コーディア)
戻り値とか、out引数はどうでしょう?
実は終わったらちゃんと格納されています。
var dic = new Dictionary<int, string>(); dic.Add(1, "1"); dic.Add(2, "2"); //対象プロセスにコピーしてその参照を取得 dynamic dicInTarget = app.Copy(dic); //非同期で実行 Async a = new Async(); dynamic value = app.Null(); dynamic ret = dicInTarget.TryGetValue(a, 1, value); //待つ a.WaitForCompletion(); //値が格納されている Assert.IsTrue((bool)ret); Assert.AreEqual("1", (string)value);
とは言え、この戻り値とout引数への対応は無駄に頑張り過ぎましたね。
実際のテストでこれが役立ったことはありません。
まあ、この層はインフラ層だから、いいかなー。
実際のモーダルダイアログ対応
ここでは、非同期の考え方のみを書かせてもらいました。
実際にモーダルダイアログの対応はもう少しテクニックが必要ですね。
このページとか参照してみてください。
モーダルダイアログ対応 - 株式会社Codeer (コーディア)
このアドベントカレンダーでもそのうちハンズオンやるんでそれに登場予定です。
明日に続く・・・