始めよう!Silverlight

第8回Silverlightで単体テスト(後編)

はじめに

前回はSilverlightで単体テストを行うための環境の構築と、APIの簡単なテストを紹介しました。後編となる今回は、その続きとしてUIのテストと非同期のテストを紹介します。

今回、使用するVisualStuidoのソリューションやプロジェクト、クラスなどは前回のものを引き続き使用します。

テストクラスの準備

初めにSilverlightプロジェクトのPageクラスをテストするためのテストクラスを作成しましょう。このテストクラスは後ほど紹介するUIのテストと非同期のテストの両方で使用します。

[新しい項目の追加]でテンプレートに[Silverlight Test Class]を選択し、ファイル名をPageTest.csとして保存してください。

画像

追加されたPageTest.csを開いて、内容を以下のように編集します。

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Silverlight.Testing;

namespace SLUnitTest.Tests
{
  [TestClass]
  public class PageTest : SilverlightTest  //①
  {
    private Page page = null;
  
    [TestInitialize]  //②
    public void Pageの初期化()
    {
      page = new Page();
      //③
      this.Silverlight.TestSurface.Children.Add(page);
    }
  }
}

まず①ではSilverlightTestクラスを親クラスに設定しています。このSilverlightTestクラスは以下のような機能を持ちます。

  • RootVisualとHTML DOMとのブリッジ機能
  • Sleepなどのヘルパーメソッド
  • 非同期テストのためのヘルパーメソッド

続いて②ではTestInitialize属性が付加された「Pageの初期化」メソッドを宣言しています。このメソッドは各テストメソッドが実行される前に毎回実行されます。

「Pageの初期化メソッド」の中では、まずテスト対象のPageクラスがインスタンス化されて、続いて③の部分でTestSurfaceの子供にそのインスタンスを追加しています。このTestSurfaceはテストを実行したときの画面の左側にあるPanelであり、ここにテスト対象のオブジェクトを追加することで、Silverlightの表示階層に追加され、UIのテストを実行することが可能になります。このTestSurfaceは各テストが終了するたびに初期化されるため、毎回設定する必要があります。

では、まずはUIのテストのためにPageクラスにテスト対象となる機能を追加します。

テストの対象となる機能を追加

Pageクラスに以下のような機能を追加し、それをテストすることにします。

  • ボタンが押されたら、TextBlockに「Hello world」と表示される。

まずはPageクラスに上記の機能を追加します。Page.xamlにルートのGrid要素以下にButtonとTextBlockをStackPanelでならべて追加してください。以下のようになります。

<StackPanel>
  <TextBlock x:Name="messageTextBlock" />
  <Button x:Name= "messageButton" 
    Click="Button_Click" Content="メッセージ" />
</StackPanel>

次にボタンがクリックされた時の処理をPage.xaml.csに記述します。クリックイベントを処理するButton_Clickメソッドを定義して、その中でmessageTextBlockのTextプロパティに「Hello world」を設定します。

private void Button_Click(object sender, RoutedEventArgs e)
{
  messageTextBlock.Text = "Hello world";
}

UIのテストコードを追加

さっそくテストコードを記述したいところですが、その前にもう一つテスト対象のPageクラスに準備するものがあります。それはボタンがクリックされたことをシミュレートするために外部から呼び出せるメソッドを追加することです。

今回取り上げているテスティングフレームワークはキーボードやマウス操作のシミュレートを行うことはできません。そのため、そのような処理をテストする場合は、キーボードやマウス操作が行われたことを擬似的に再現するメソッドを対象となるクラスに準備し、外部からそのメソッドを呼ぶ必要があります。

具体的には対象となるクラス、つまりこの場合Pageクラスに以下のようなメソッドを追加します。

/// <summary>
/// for test
/// </summary>
internal void SimulateButtonClick()
{
  Button_Click(messageButton,new RoutedEventArgs());
}

ここではSimulateButtonClickというメソッドを定義し、実際にボタンのクリックイベントで呼ばれるButton_Clickメソッドを直接呼び出すようにして、ボタンがクリックされたことをシミュレートしています。

このSimulateButtonClickメソッドはinternalで定義されています。このメソッドを別のアセンブリであるテストプロジェクトのアセンブリから呼び出せるように、以下をSilverlightプロジェクトのAssemblyInfoに定義します。

[assembly: InternalsVisibleTo("SLUnitTest.Tests")]

上記のInternalVisivleTo属性を設定することで、テストプロジェクトのアセンブリから、Silverlightプロジェクトのアセンブリのinternalで設定されているメソッドやプロパティ、クラスなどにアクセスすることが可能になります。

では、テストプロジェクトから追加したSimulateButtonClickメソッドを呼び出して、ボタンクリック時の動作をテストするコードを追加します。PageTestクラスに以下のメソッドを追加します。

[TestMethod]
public void ボタンクリックでラベルにHelloworldが表示されるべき()
{
  //①
  Assert.IsNull(page.messageTextBlock.Text);
  //②
  page.SimulateButtonClick();
  //③
  Assert.AreEqual("Hello world", page.messageTextBlock.Text);
}

①の部分でまずはデフォルトのなにも設定されていない状態を検証しています。その場合は値はnullになります。

②の部分でボタンがクリックされたことをシミュレートしています。ですのでSilverlightプロジェクトの中ではボタンがクリックされた場合と、同様の処理が行われていることになります。

最後の③の部分で、ボタンクリック後のラベルに「Hello world」が表示されているかどうかを検証しています。

それでは実行してみます。実行画面をじっくり見ていただくと一瞬ですがPageに追加したボタンとテキストブロックが実際に表示されていることも確認できます。

画像

簡単にまとめるとUIのテストは以下のような流れで作成することになります。

  1. キーボードやマウスの操作が行われた時の処理を記述
  2. Internalで上記の操作を呼ぶシミュレート用のメソッドを記述
  3. テストクラスより上記のシミュレート用のメソッドを呼ぶテストメソッドを記述

非同期のテスト

前編・後編と続けましたSilverlightの単体テストの最後のトピックとして非同期のテストの実行をご紹介します。Silverightではネットワークアクセスやアニメーションの実行はすべて非同期での処理になりますので、非同期をテストできることは非常に重要になります。

まずは非同期検証用に以下のメソッドをPageTestクラスに追加します。

[TestMethod]
[Asynchronous]  //①
public void 非同期処理のテスト()
{
}

非同期のテストには①のAsynchronous属性を設定します。この属性が設定されていないとテストは正常に実行されません。

非同期処理自体については今回はテストメソッドの中にBackgroundWorkerを使って実装します。以下のコードを先ほど追加した[非同期処理のテスト]メソッドの中に追加します。

var worker = new BackgroundWorker();
var result = false; //①
var finishedWork = false; //②
worker.DoWork += (s, e) => //③
{
  e.Result = true;
};
worker.RunWorkerCompleted += (s, e) => //④
{
  result = (bool)e.Result;
  finishedWork = true;
};

まず、①のresult変数ですが、これは検証するための変数です。この変数の値がtrueになっていればテスト成功、falseであればテスト失敗と判定します。②のfinishedWork変数は非同期処理が終了したかどうかを判定するための変数です。

③のBackgroundWorkerのDoWorkに設定された匿名デリゲートはBackgrondWorkerが実行された時に呼び出されます。この中で処理結果に true を設定しています。最後にこの処理結果をテストで判定します。

④のBackgroundWorkerのRunWorkerCompletedに設定された匿名デリゲートはBackgrondWorkerの処理が完了した時に実行されます。ここでは処理の結果をテストで検証するresult変数に代入し、finishedWork変数にtrueを代入することで、処理が終わったことを設定しています。

では、最後に検証のコードを追加します。次のコードを上記のコードの下に追加します。

this.EnqueueCallback(() => worker.RunWorkerAsync()); //①
this.EnqueueConditional(() => finishedWork); //②
this.EnqueueCallback(() => Assert.IsTrue(result)); //③
this.EnqueueTestComplete(); //④

これらのSilverlightTestクラスがもっている、 EnqueueXXXXメソッドが非同期処理のテスト用のヘルパーメソッドになります。それぞれに設定されたデリゲートはテストメソッドの実行が終了した後に、設定された順番で呼び出されます。

まず①でEnqueueCallbackにBackgroundWorkerの処理を実行するデリゲートを設定しています。ここで非同期処理を実行します。テストメソッドの実行が終了した後に、最初にこのデリゲートが呼び出されます。

次に②でEnqueueConditionalメソッドにfinishedWork変数の値が戻り値になるデリゲートを設定しています。EnqueueConditionalメソッドは戻り値の値がtrueになるまで繰り返し、設定されているデリゲートを呼び出すことで、非同期処理が終了するまでの待機を行います。

③ではEnqueueCallbackにアサートを実行するデリゲートを設定しています。ここでresult変数の値がtrueであればテストが成功になります。

最後に④でテストが終了したことを設定しています。

ではテストを実行してみてください。正常に実行されるとおもいます。非同期処理のテストはデバックなしで実行を行ってください。デバックが有効になっている場合、例外が発生した時にVisualStudioが例外をキャッチして処理を止めてしまうためです。

画像

非同期のテストはいったんデリゲートという形で処理をキューに貯めてから実行されます。大きな流れとしては以下の順番で処理をキューに設定することになります。

  1. EnqueueCallbackで非同期処理の開始
  2. EnqueueConditionalで非同期処理の終了を待機
  3. EnqueueCallbackで非同期処理の結果を検討
  4. EnqueueTestCompleteで終了

次回予告

後編の今回は前編に引き続き、残りのUIのテストと非同期のテストをご紹介しまし。今後は様々なテストフレームワークが登場することでしょう。また、ビルドや継続的インテグレーションとの統合なども実現されていくと予想されます。

次回は、SilverlightのコントロールであるDataGridを取り上げたいと思います。お楽しみに。

おすすめ記事

記事・ニュース一覧