Silverlight クラスライブラリ の UnitTest を行うために、サーバー側処理(非同期処理)用のモックを作る必要が出たので少し試してみた。
ソリューションの構成
- デリゲートを使って非同期処理を実現する。
最初に試したのがこれ。見事失敗。
Class1.cs
using System; using System.Threading; namespace SilverlightClassLibrary1 { delegate void Dummy(); public class Class1 { public string Result { get; private set; } public void 呼び出されるメソッド() { Dummy d = new Dummy(待機); d.BeginInvoke(コールバック, null); } private void 待機() { // 2秒待機 Thread.Sleep(2000); } private void コールバック(IAsyncResult ar) { this.Result = "ダミー"; } } }Class1Test.cs
using Microsoft.VisualStudio.TestTools.UnitTesting; using SilverlightClassLibrary1; namespace SilverlightClassLibrary1Test { [TestClass] public class Class1Test { [TestMethod] public void 呼び出されるメソッドTest() { Class1 instance = new Class1(); instance.呼び出されるメソッド(); Assert.AreEqual("ダミー", instance.Result, "Result"); } } }
んで、実行結果
エラー詳細
MSDN で調べたら、Silverlight の delegate は非同期をサポートしていないんだって。
Silverlight 用 .NET Framework クラス ライブラリ Delegate クラス
Silverlight では、デリゲートを使用した非同期メソッドの呼び出しはサポートされていません。BeginInvoke を呼び出すと、NotSupportedException が発生します。
- BackgroundWorker を使う
これが、うまくいった。(BackgroundWorker って .NET 2.0 から追加されたんだけど使うのは初めてな気がする)
Class1.cs
using System.Threading; using System.ComponentModel; namespace SilverlightClassLibrary1 { public class Class1 { public string Result { get; private set; } public void 呼び出されるメソッド() { BackgroundWorker b = new BackgroundWorker(); b.DoWork += 待機; b.RunWorkerCompleted += コールバック; b.RunWorkerAsync(); } private void 待機(object sender, DoWorkEventArgs e) { // 2秒待機 Thread.Sleep(2000); e.Result = "ダミー"; } private void コールバック(object sender, RunWorkerCompletedEventArgs e) { this.Result = e.Result as string; } } }Class1Test.cs
using Microsoft.Silverlight.Testing; using Microsoft.VisualStudio.TestTools.UnitTesting; using SilverlightClassLibrary1; namespace SilverlightClassLibrary1Test { [TestClass] public class Class1Test : SilverlightTest { [TestMethod] [Asynchronous] public void 呼び出されるメソッドTest() { Class1 instance = new Class1(); this.EnqueueCallback(() => instance.呼び出されるメソッド()); this.EnqueueConditional(() => !string.IsNullOrEmpty(instance.Result)); this.EnqueueCallback(() => Assert.AreEqual("ダミー", instance.Result, "Result")); this.EnqueueTestComplete(); } } }
実行結果が
実は、デリゲートを試した時とテストコードが変わってる。
非同期のテストをするときは、BackgroundWorker を使った時の様なテストコードにしないといけないんだけど、
(SilverlightTest クラスを継承して、Enqueue〜 メソッドを使用する)
テストされる側(ここでは、Class1.呼び出されるメソッド())の非同期処理が呼び出される前に、例外が発生する等で非同期処理が実行されないと
EnqueueConditional の条件を満たさずに、テストが終わらなった。どうやって回避したらいいのかな?
それと、Visual Studio から直接実行するときは、デバッグ無しで実行しないと、Visual Studio が例外を捕まえるので注意!
後は、テスト結果が NUnit みたく xml ファイルで出力出来ないのかなぁ? CruiseControl.NET とも連携出来る様になるし。
一応、テストプロジェクトが出力した html(TestPage.html)を開いたらテストを実行してくれるから、
ビルド後に、TestPage.html と 〜.xap を 見える場所に配置したら運用で何とかなりそうなんだけど。。
ただ、html を開かないとテストが実行されないし、エラーが発生しても通知出来るかわからないから自動ビルドの一環には、取り入れられないかもしれないなぁ。。