Silverlight サーバー側処理(非同期処理)の モック を作る

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 って .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 を開かないとテストが実行されないし、エラーが発生しても通知出来るかわからないから自動ビルドの一環には、取り入れられないかもしれないなぁ。。