テストの実行

メッセージを使った Silverlight アプリケーションのテスト

James McCaffrey

コード サンプルのダウンロード

Silverlight は私のお気に入りの 1 つなので、今回のコラムでは Silverlight アプリケーションのテストに使用できる技法を紹介します。

Silverlight は完全な Web アプリケーション フレームワークで、2007 年に初めてリリースされました。現バージョンの Silverlight 3 は、2009 年 7 月にリリースされました。Visual Studio 2010 では Silverlight のサポートが強化され、特に Silverlight のユーザー インターフェイスの設計を容易にするビジュアル デザイナーが完全に統合されています。

今回のコラムの目的は、アプリケーション自体をご覧いただければ一目瞭然でしょう。図 1 は、MicroCalc という、単純ながらも典型的な Silverlight アプリケーションを示しています。MicroCalc はご覧のように Internet Explorer でホストされています。ただし、Silverlight アプリケーションは、Firefox、Opera、Safari など、他のブラウザーでもホストできます。

Figure 1 MicroCalc Silverlight App
図 1 MicroCalc Silverlight アプリケーション

図 2 は簡易テスト ハーネスで、これも Silverlight アプリケーションです。


図 2 MicroCalc のテスト ハーネス

この例では、最初のテスト ケースが選択されています。[Run Selected Test] (選択したテストの実行) というボタン コントロールをクリックすると、Silverlight テスト ハーネスから、選択したテスト ケースの入力データを含むメッセージが、テスト対象の Silverlight MicroCalc アプリケーションに送信されます。このテスト ケース データは一連の指示から構成され、ユーザーがアプリケーションの入力領域に「2.5」と「3.0」を入力し、[Multiply] (乗算) 演算を選択して、[Compute] (計算) をクリックするという操作のシミュレーションを行います。

テスト対象のアプリケーションはテスト ケース データを受け取ると、アプリケーションにインストルメント化されているテスト コードを使用して、プログラムからアプリケーションをテストします。やや遅れて、テスト ハーネスから 2 つ目のメッセージがテスト対象のアプリケーションに送信されます。このメッセージは、状態に関する情報、つまり結果フィールドの値を含むメッセージを送信するよう、アプリケーションに要求します。テスト ハーネスがアプリケーションから結果のメッセージを受け取ると、アプリケーションの結果値 7.5000 と、テスト ケース データの想定値が一致するかどうかを判断して、ハーネスのコメント領域にテスト ケースの結果を "Pass" (合格) と表示します。

今回のコラムは、C# 言語の基本的な知識があることが前提ですが、Silverlight に関する経験は前提としていません。ここからは、まず、テスト対象の Silverlight アプリケーションについて説明します。続いて、Silverlight ベースの簡易テスト ハーネス (図 2 参照) の作成について詳しく説明し、アプリケーションをインストルメント化する方法を紹介します。最後にまとめとして、その他のテスト手法について説明します。

テスト対象のアプリケーション

今回の自動テストのサンプルで、テストの対象となる Silverlight MicroCalc アプリケーションのコードを見てみましょう。ここでは、MicroCalc を Visual Studio 2010 Beta 2 を使用して作成しました。Visual Studio 2010 には Silverlight 3 が完全に統合されていますが、ここで紹介するコードは、Silverlight 3 SDK を別途インストールした Visual Studio 2008 でも機能します。

Visual Studio を起動して、[ファイル] メニューの [新規作成] をポイントし、[プロジェクト] をクリックします。Silverlight アプリケーションは、それ自体が Web アプリケーションというわけではなく、Web アプリケーションでホストできる .NET コンポーネントです。[新しいプロジェクト] ダイアログ ボックスで、テンプレートのオプションから [Visual C#] をクリックします。また、Visual Basic を使用して Silverlight アプリケーションを作成したり、新しい F# 言語を使用して Silverlight ライブラリを作成することもできます。

ライブラリを既定の [.NET Framework 4] のままにして、[Silverlight アプリケーション] テンプレートをクリックします。Silverlight に含まれるのは .NET Framework のサブセットなので、Silverlight アプリケーションで .NET Framework 4 のすべての機能を使用できるわけではありません。[名前] ボックスに「SilverCalc」、[場所] ボックスに「C:\SilverlightTesting」と入力して、[OK] をクリックします (SilverCalc は Visual Studio のプロジェクト名で、MicroCalc はアプリケーション名です)。

続いて、[新しい Silverlight アプリケーション] ダイアログ ボックスが表示されますが、Silverlight の初心者はこれには混乱を招くかもしれません。図 3 のオプションを詳しく見てみましょう。


図 3 [新しい Silverlight アプリケーション] ダイアログ ボックスのオプション

1 つ目のエントリの [Silverlight アプリケーションを新しい Web サイトでホストする] チェック ボックスは既定でオンになっています。これは、Silverlight アプリケーションをホストするため、2 つの異なる Web ページを作成するよう Visual Studio に指示します。

2 つ目のエントリは、このホスト ページ 2 ページを含む Visual Studio プロジェクトの名前です。このプロジェクトが、Visual Studio ソリューションに追加されることになります。

ダイアログ ボックスの 3 つ目のエントリは、[ASP.NET Web アプリケーション プロジェクト]、[ASP.NET Web サイト]、および [ASP.NET MVC Web プロジェクト] の 3 つのオプションを含むドロップダウン リストです。ここでは、これらのオプションの詳細についての説明を省きますが、重要なのは、[ASP.NET Web アプリケーション プロジェクト] が最適な汎用オプションである点です。

ダイアログ ボックスの 4 つ目のエントリは、Silverlight のバージョンを選択するドロップダウン リストで、この場合は [Silverlight 3.0] をクリックします。[OK] をクリックすると、Visual Studio によって空の Silverlight アプリケーションが作成されます。

[MainPage.xaml] をダブルクリックして、Visual Studio エディターに XAML ベースの UI 定義を読み込みます。Width 属性と Height 属性を追加し、Background 属性の背景色を変更することで、最上位の Grid コントロールの既定の属性を変更します。

<Grid x:Name="LayoutRoot"
  Background="PaleGreen" 
  Width="300" Height="300">

既定では、Silverlight アプリケーションは、ホスト ページのクライアント領域全体を占有します。ここでは、幅と高さを 300 ピクセルに設定して、Silverlight アプリケーションを WinForm アプリケーションの既定のサイズと同じくらいにします。色を調整して、Silverlight アプリケーションが使用している領域をわかりやすくします。

次に、Visual Studio を使用して、ラベル、3 つのテキスト ボックス コントロール、2 つのオプション ボタン コントロール、および 1 つのボタン コントロールをアプリケーションに追加します (図 1 参照)。Visual Studio 2010 にはデザイン ビューが完全に統合されているため、テキスト ボックスなどのコントロールをデザイン サーフェイスにドラッグするだけで、基になる XAML コードが自動的に生成されます。

<TextBox Width="99" Height="23" Name="textBox1" ... />

MicroCalc アプリケーションにラベルとコントロールを配置したら、ボタン コントロールをダブルクリックして、MainPage.xaml.cs ファイルにイベント ハンドラーを追加します。コード エディターで次の C# コードを入力して、MicroCalc アプリケーションに機能を追加します。

private void button1_Click(
  object sender, RoutedEventArgs e) {

  double x = double.Parse(textBox1.Text);
  double y = double.Parse(textBox2.Text);
  double result = 0;
  if (radioButton1.IsChecked == true)
    result = x * y;
  else if (radioButton2.IsChecked == true)
    result = x / y;
  textBox3.Text = result.ToString("0.0000");
}

まず、textBox1 コントロールと textBox2 コントロールにテキストとして入力された値を取得して、double 型に変換します。例を簡潔にするために、実際のアプリケーションで実行する通常のエラー チェックは省略しています。

次に、ユーザーがどちらのオプション ボタン コントロールを選択しているかを確認します。ここでは完全修飾ブール式を使用する必要があります。

if radioButton1.IsChecked == true

次のような省略形を使用すると考えていたかもしれません。

if radioButton1.IsChecked

IsChecked プロパティが通常の bool 型ではなく、Null 値を許容する bool? 型なので、完全修飾の形式を使用します。

指定された結果を計算した後、textBox3 コントロールに小数点以下 4 桁まで結果を格納します。

これで、MicroCalc アプリケーションの準備ができたので、F5 キーを押して、アプリケーションを実行するよう Visual Studio に指示します。Visual Studio は既定で Internet Explorer を起動し、自動生成された関連 .aspx ホスト ページを読み込みます。Visual Studio は IIS ではなく組み込みの Web 開発サーバーを使って、Silverlight テスト ホスト ページを実行します。Visual Studio は、.aspx テスト ホスト ページに加えて、アドレスを Internet Explorer に入力して手動で読み込むことができる HTML テスト ページも生成します。

テスト ハーネス

以上でテスト対象の Silverlight アプリケーションを確認したので、今度はテスト ハーネスについて説明します。

ここでは、ローカル メッセージングを使用して、ハーネスとアプリケーション間でメッセージを送信することにしました。まず、Visual Studio 2010 の新しいインスタンスを起動します。上記と同じプロセスを使用して、TestHarness という新しい Silverlight アプリケーションを作成します。MicroCalc アプリケーションと同様に最上位の Grid コントロールを編集します。幅と高さを既定のサイズから 300 ピクセルに変更し、Silverlight コントロールがはっきりと見えるよう背景色をビスクに変更します。次に、1 つのラベル コントロール、2 つのリスト ボックス コントロール、および 1 つのボタン コントロールを、ハーネスのデザイン サーフェイスに追加します。

ボタン コントロールの Content プロパティを "Run Selected Test" (選択したテストの実行) に変更してから、ボタンをダブルクリックし、イベント ハンドラーを生成します。ロジック コードをハンドラーに追加する前に、ハーネスの MainPage.xaml.cs ファイルで、LocalMessageSender オブジェクトとテスト ケース データをクラス スコープで宣言します。これで、ハーネスからテスト対象のアプリケーションにメッセージを送信することができます。

public partial class MainPage : UserControl {
  LocalMessageSender lms = null;
  private string[] testCases = new string[] {
    "001:2.5:3.0:Multiply:7.5000",
    "002:8.6:2.0:Divide:4.3000"
  };
...

LocalMessageSender クラスは、System.Windows.Messaging 名前空間に含まれているため、.cs ファイルの先頭で using ステートメントを使用してこの名前空間への参照を追加します。これで、クラス名を完全修飾する必要がなくなります。ここではテスト ケース データに簡単な手法を採用しており、テスト ケース ID、1 つ目の入力値、2 つ目の入力値、操作、および想定結果の各フィールドをコロンで区切った文字列として使用しています。次に、テスト ケースのフィールドごとに文字列変数をクラス スコープで追加します。

private string caseID;
private string input1;
private string input2;
private string operation;
private string expected;
...

技術的には、これらの変数は必要ありませんが、変数を用意することで、テスト コードを読みやすく、変更しやすくします。

これで、MainPage コンストラクターに LocalMessageReceiver オブジェクトのインスタンスを作成して、テスト ハーネスでテスト対象のアプリケーションからメッセージを受信できるようになりました。

public MainPage() {
  InitializeComponent();

  try {
    LocalMessageReceiver lmr =
      new LocalMessageReceiver("HarnessReceiver",
        ReceiverNameScope.Global,
        LocalMessageReceiver.AnyDomain);
...

LocalMessageReceiver オブジェクトのコンストラクターは 3 つの引数を受け取ります。1 つ目の引数は、受信者を識別するための名前です。これは対象となる受信者を指定するために、LocalMessageSender オブジェクトで使用されます。2 つ目の引数は列挙型で、受信者の名前のスコープがグローバル ドメインに設定されているか、より限定されたドメインに設定されているかを指定します。3 つ目の引数は、受信者がメッセージをどこから受信するかを指定します。この場合はドメインです。

次に、受信者にイベント ハンドラーを定義して、受信者オブジェクトを起動します。

lmr.MessageReceived += HarnessMessageReceivedHandler;
lmr.Listen();
...

ここでは、テスト ハーネスがメッセージを受信すると、HarnessMessageReceivedHandler というプログラム定義のメソッドに制御が移ります。ご想像のとおり、Listen メソッドでは、LocalMessageSender オブジェクトからテスト対象のアプリケーションに送信される受信メッセージを継続的に監視します。

ここで、先ほど宣言した送信者オブジェクトのインスタンスを作成します。

lms = new LocalMessageSender(
  "AppReceiver", LocalMessageSender.Global);
lms.SendCompleted += HarnessSendCompletedHandler;
...

送信者オブジェクトの最初の引数は対象となる受信者オブジェクトの名前です。ここでは、送信者の名前は特定しません。テスト ハーネスが送信者となるときは、テスト対象のアプリケーションにある AppReceiver という受信者にのみメッセージを送信します。つまり、受信者オブジェクトには名前があり、あらゆる送信者オブジェクトからメッセージを受け取ることができますが、送信者オブジェクトには名前がなく、特定の受信者にしかメッセージを送信することができません。

送信者オブジェクトのインスタンスを作成したら、SendCompleted イベントのイベント ハンドラーを定義します。これで、テスト ケースを読み込み、例外を処理することができます。

...
    foreach (string testCase in testCases) {
      listBox1.Items.Add(testCase);
    }
  } // try
  catch (Exception ex) {
    listBox2.Items.Add(ex.Message);
  }
} // MainPage()

テスト ケースの配列を単純に繰り返し処理して、各テスト ケースの文字列を listBox1 コントロールに追加します。例外が検出された場合、コメントに使用する listBox2 コントロールにテキストを表示します。

この時点では、ハーネス内にテスト ケースの入力をアプリケーションに送信できる送信者オブジェクトと、アプリケーションから状態情報を受信できる受信者オブジェクトが存在します。ここで、前に追加した button1_Click ハンドラー メソッドに戻ります。このハンドラーでは、まず、選択したテスト ケースを解析します。

string testCaseData = (string)listBox1.SelectedItem;
string[] tokens = testCaseData.Split(':');
caseID = tokens[0];
input1 = tokens[1];
input2 = tokens[2];
operation = tokens[3];
expected = tokens[4];
...

これで、テスト ケースの入力をテスト対象の Silverlight アプリケーションに送信する準備ができました。

string testCaseInput = 
  input1 + ":" + input2 + ":" + operation;
listBox2.Items.Add("========================");
listBox2.Items.Add("Test case " + caseID);
listBox2.Items.Add(
  "Sending ‘" + testCaseInput + "’ to application");
lms.SendAsync("data" + ":" + testCaseInput);
...

ここでは、テスト ケースの入力だけをまとめて送信するようにしました。テスト ケース ID と想定値を扱うのはハーネスだけなので、この 2 つの値はアプリケーションには送信しません。listBox2 コントロールにいくつかコメントを表示したら、LocalMessageSender オブジェクトの SendAsync メソッドを使用して、テスト ケース データを送信します。"data" という文字列を先頭に追加して、アプリケーションが受信したメッセージの種類を特定できるようにします。

アプリケーションに実行する時間を与えるため、ボタンのイベント ハンドラーは 1 秒間一時停止してから終了します。その後、アプリケーションに状態情報を要求するメッセージを送信します。

System.Threading.Thread.Sleep(1000); 
  lms.SendAsync(“response”);
} // button1_Click

送信完了に関するイベント ハンドラーを定義したことを思い出してください。ただし、この設計では送信後の処理を明示的に実行する必要はありません。

ハーネス コードの最後の部分では、Silverlight アプリケーションからハーネスに送信されたメッセージを処理します。

private void HarnessMessageReceivedHandler(object sender,
  MessageReceivedEventArgs e) {

  string actual = e.Message;
  listBox2.Items.Add(
    "Received " + actual + " from application");
  if (actual == expected)
    listBox2.Items.Add("Pass");
  else
    listBox2.Items.Add("**FAIL**");

  listBox2.Items.Add("========================");
}

ここで、アプリケーションからのメッセージをフェッチします。これは結果を示す textBox3 コントロールの値で、actual という変数に格納します。コメントを表示した後、アプリケーションから送信された実際の値と、テスト ケース データから解析した想定値とを比較して、テスト ケースの結果の合格/不合格を判断して表示します。

Silverlight アプリケーションをインストルメント化する

ここで、テスト対象の Silverlight アプリケーション内部にインストルメント化されたコードをテストします。まず、LocalMessageSender オブジェクトをクラス スコープで宣言します。

この送信者がテスト ハーネスにメッセージを送信します。

public partial class MainPage : UserControl {
  LocalMessageSender lms = null;
  public MainPage() {
    InitializeComponent();
...

次に、MainPage コンストラクターで、テスト ハーネスからのメッセージを受信する受信者のインスタンスを作成して、イベント ハンドラーを定義し、ハーネスからのメッセージの待機を開始します。

try {
  LocalMessageReceiver lmr =
    new LocalMessageReceiver("AppReceiver",
      ReceiverNameScope.Global,
      LocalMessageReceiver.AnyDomain);
  lmr.MessageReceived += AppMessageReceivedHandler;
  lmr.Listen();
...

先ほどと同様、受信者オブジェクトには名前を割り当て、その名前はハーネスの送信者オブジェクトの最初の引数に対応付けます。続いて、例外を処理します。

...
  }
  catch (Exception ex) {
    textBox3.Text = ex.Message;
  }
} // MainPage()

ここでは、例外メッセージを、MicroCalc アプリケーションの結果フィールドに使う textBox3 コントロールに表示するようにしました。この手法は完全にその場しのぎで、メッセージング コードで例外がスローされた場合、例外メッセージをテスト ハーネスに送信できない可能性があります。次に、テスト ハーネスから送信されたメッセージを処理します。

private void AppMessageReceivedHandler(object sender,
  MessageReceivedEventArgs e) {
  string message = e.Message;
  if (message.StartsWith("data")) {
    string[] tokens = message.Split(‘:’);
    string input1 = tokens[1];
    string input2 = tokens[2];
    string operation = tokens[3];
...

テスト ハーネスからは 2 種類のメッセージが送信されます。テスト ケースの入力データの場合は "data"、アプリケーションの状態について要求している場合は "response" で始まります。StartsWith メソッドを使用して、受信したメッセージがテスト ケースの入力データかどうかを判断します。入力データであれば、Split メソッドを使用して、わかりやすい名前を付けた変数に入力を解析します。

次に、インストルメンテーションではテスト ケースの入力を使用して、ユーザー操作のシミュレーションを行います。

textBox1.Text = input1;
textBox2.Text = input2;
if (operation == "Multiply")
  radioButton1.IsChecked = true;
else if (operation == "Divide")
  radioButton2.IsChecked = true;
...

一般に、ユーザー入力のシミュレーションを行うためにコントロールのプロパティ (この例では Text プロパティ、IsChecked プロパティなど) の変更を行うのは簡単です。しかし、ボタンのクリックなど、イベントのシミュレーションを行うには、次の手法が必要になります。

button1.Dispatcher.BeginInvoke(
  delegate { button1_Click(null,null); });

Dispatcher クラスは Windows.Threading 名前空間に含まれるため、この名前空間を参照する using ステートメントをアプリケーションに追加します。BeginInvoke メソッドを使用すると、Silverlight ユーザー インターフェイス スレッドで非同期にメソッドを呼び出すことができます。BeginInvoke メソッドでは、メソッドのラッパーであるデリゲートを受け取ります。ここでは、呼び出しを簡略化するため、匿名デリゲート機能を使用します。BeginInvoke メソッドは DispatcherOperation オブジェクトを返しますが、この場合は、この値を無視しても安全です。

Dispatcher クラスには CheckAccess メソッドもあります。このメソッドを使用して、BeginIvoke メソッドが必要かどうか (CheckAccess メソッドが false を返す場合)、またはプロパティを変更できるかどうか (CheckAccess メソッドが true を返す場合) を判断することができます。

アプリケーションの状態を要求するテスト ハーネスからのメッセージを処理したら、インストルメンテーションは終了です。

...
  }
  else if (message == "response") {
    string actual = textBox3.Text;
    lms.SendAsync(actual);
  }
} // AppMessageReceivedHandler()

受信したメッセージが "response" という文字列であれば、textBox3 コントロールの値を取得して、テスト ハーネスに送信します。

今回のコラムで紹介したテスト システムは、使用可能な数多くの手法の 1 つにすぎません。また、このテスト システムは "4-4-4" の超簡易自動テストに最適です。つまり、想定有効期間が 4 週間以内、4 ページ以内のコードで構成され、4 時間あれば作成できるハーネスです。

メッセージを使用するテストを他の手法と比較した場合に、その主なメリットは手法が非常に簡単なことです。主なデメリットは、テスト対象のアプリケーションに多くのインストルメント化が必要になることで、必ずしも実現できるとは限りません。

今回紹介した手法に代わる主な手法として、HTML ブリッジと JavaScript を使用する手法と、Microsoft UI オートメーション ライブラリを使用する手法の 2 つがあります。いつものように、すべての状況に適した特定の 1 つのテスト手法というものは存在しないことを忘れないでください。ただし、今回紹介したメッセージ ベースの手法は、多くのソフトウェア開発シナリオで効率的かつ効果的な手法となる可能性があります。

Dr. James McCaffrey は Volt Information Sciences Inc. に勤務し、ワシントン州レドモンドにあるマイクロソフト本社で働くソフトウェア エンジニアの技術トレーニングを管理しています。これまでに、Internet Explorer、MSN サーチなどの複数のマイクロソフト製品にも携わってきました。また、『.NET Test Automation Recipes』(Apress、2006 年) の著者でもあります。連絡先は jammc@microsoft.com (英語のみ) です。

この記事のレビューに協力してくれた技術スタッフの Karl Erickson と Nitya Ravi に心より感謝いたします。