次の方法で共有


テストの実行

WPF アプリケーションで UI テストを自動化する

James McCaffrey

コードは MSDN コード ギャラリーからダウンロードできます。
オンラインでのコードの参照

目次

テスト対象の WPF アプリケーション
UI テスト自動化
まとめ

今月のコラムでは、Windows Presentation Foundation (WPF) アプリケーションの UI テスト自動化を記述する方法を示します。WPF アプリケーションでは新しいグラフィックス サブシステムを使用しており、従来のほとんどの UI テスト自動化手法は、WPF アプリケーションには使用できません。さいわい、Microsoft UI オートメーション (MUIA) ライブラリは WPF アプリケーションの UI 自動化を考慮してデザインされています。Microsoft UI オートメーション ライブラリを使用して、従来の Win32 アプリケーションと WinForm ベースの .NET アプリケーションを、Microsoft .NET 3.0 Framework 対応のオペレーティング システムが動作するホスト コンピュータ上でテストすることもできます。

Microsoft UI オートメーション ライブラリは古い UI 自動化方式よりも高機能で一貫性に優れ、初期の習得を終えると、はるかに簡単に使用できることを実感します。このコラムは、WPF アプリケーションの基本的な知識と中程度の C# スキルがあり、MUIA ライブラリに関する経験がない方を対象としています。

内容をわかりやすく示すため、スクリーンショットを用意しました。図 1 に、単純で典型的な WPF アプリケーションをテストしているところを示します。アプリケーションは CryptoCalc という名前で、MD5 ハッシュ、SHA1 ハッシュ、DES 暗号化という 3 つのハッシュ手法の 1 つを使用して入力文字列の暗号化ハッシュを計算します。

fig01.gif

図 1 WPF アプリケーションの UI 自動化

MD5 (Message Digest 5) ハッシュ手法は、任意のサイズのバイト配列を受け取り、さまざまな識別目的に使用できる 16 バイトのフィンガプリントを返します。SHA1 (Secure Hash Algorithm 1) ハッシュ手法は MD5 に似ていますが、SHA1 は別のアルゴリズムを使用し、20 バイトのフィンガプリントを返します。DES (Digital Encryption Standard) は、対称キー暗号化手法であり、識別バイト配列の生成にも使用できます。DES 暗号化ハッシュは、入力と同等以上のバイト数のバイト配列を返します。

図 1 の UI テスト自動化を実行するのに使用されているコンソール アプリケーションは、テスト対象のアプリケーションを起動し、Microsoft UI オートメーション ライブラリを使用してアプリケーション上のアプリケーション コントロールおよびユーザー コントロールへの参照を取得します。また、ユーザーが「Hello!」と入力し、[DES Encrypt] オプションをクリックし、[Compute] ボタン コントロールをクリックした場合をシミュレートします。次に、このテスト自動化では、結果を示すテキスト ボックス コントロールの予期される値を調べ、合否結果を出力することにより、テスト対象のアプリケーションの結果状態を確認します。図 1 のスクリーンショットは、ユーザーが [File]、[Exit] の順にメニュー項目をクリックした場合をシミュレートすることで、テスト自動化がテスト対象のアプリケーションを終了する直前に取得しました。

以降のセクションでは、テストに使用する CryptoCalc WPF アプリケーションについて簡単に説明し、テスト対象アプリケーションを起動する方法、Microsoft UI オートメーション ライブラリを使用してアプリケーション コントロールおよびユーザー コントロールへの参照を取得する方法、ユーザー操作をシミュレートする方法、およびアプリケーションの状態を確認する方法を示します。また、このコラムで示したテスト システムを各自の要件に応じて拡張および変更する方法についても説明します。読み進めていくうちに、Microsoft UI オートメーション ライブラリを使用して WPF アプリケーションをテストすると、パーソナル ツール セットに便利なツールが追加されることがおわかりいただけると思います。

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

テスト自動化の目標と、UI テスト自動化に影響するデザインの問題を理解していただくために、テスト対象の WPF アプリケーションについて簡単に説明します。CryptoCalc アプリケーションは、文字列の暗号化ハッシュを計算する単純な単一ウィンドウ ユーザー アプリケーションです。アプリケーションは、最大 24 文字のユーザー入力文字列を TextBox コントロールで受け取り、入力文字列をバイト配列に変換し、入力バイトの 3 種類の暗号化ハッシュの 1 つを計算して、ハッシュされたバイトを 2 つ目の TextBox コントロールに表示します。

テスト対象のアプリケーションは、Visual Studio 2008 および C# を使用してデザインし、Project CryptoCalc という名前を付けました。WPF テンプレートは、スケルトン アプリケーション UI 定義を XAML ファイルとして生成します。

<Window x:Class="CryptoCalc.Window1"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1" Height="300" Width="300">
  <Grid></Grid>
</Window>

最上位の Window コントロール定義には Name 属性が含まれないことに注意してください。このことは重要です。なぜなら、テスト自動化を記述するときに MUIA ライブラリを使用してコントロールへの参照を簡単に取得する方法は、コンパイラによってコントロールの Name 属性から生成される AutomationId プロパティにアクセスすることだからです (これについては、じきにおわかりいただけます)。XAML Name 属性のないコントロールは、AutomationId プロパティを受け取りません。この概念は、セキュリティ、拡張性、テスト自動化などに対して、アプリケーション デザインの問題を考慮することが重要であるという具体的で初歩的な例です。

次に、Visual Studio Toolbox からデザイン画面に項目をドラッグすることで、Label コントロールと TextBox コントロールを追加しました。

<Label Height="28" HorizontalAlignment="Left"
 Margin="10,33,0,0" Name="label1" VerticalAlignment="Top"
 Width="120">Enter string:</Label>
<TextBox MaxLength="24" Height="23" Margin="10,57,51,0"
 Name="textBox1" VerticalAlignment="Top" /> 

既定では、これらのコントロールは通常の Name 属性 (それぞれ label1 と textBox1) を受け取ることに注意してください。次に、GroupBox コントロールの内部に 3 つの RadioButton コントロールを配置しました。古い WinForms GroupBox コントロールとは異なり、WPF GroupBox は項目を 1 つだけ受け取るため、3 つの RadioButton コントロールを 1 つの StackPanel コンテナ (これには複数の項目を含めることができます) にラップしました。

<GroupBox Header="Crypto Type" Margin="10,91,118,127"
 Name="groupBox1">
<StackPanel Height="52" Name="stackPanel1" Width="127">
 <RadioButton Height="16" Name="radioButton1" Width="120">
  MD5 Hash</RadioButton>
 <RadioButton Height="16" Name="radioButton2" Width="120">
  SHA1 Hash</RadioButton>
 <RadioButton Height="16" Name="radioButton3" Width="120">
  DES Encrypt</RadioButton>
</StackPanel>
</GroupBox>

次に、暗号化ハッシュ計算をトリガする Button コントロールと結果を保持する TextBox コントロールをメインの Window コントロールに配置しました。

<Button Height="23" Margin="10,0,0,90" Name="button1"
 VerticalAlignment="Bottom" Click="button1_Click"
 HorizontalAlignment="Left" Width="89">Compute</Button>
<TextBox Height="63" Margin="10,0,51,13" Name="textBox2"
 VerticalAlignment="Bottom" TextWrapping="Wrap" />

WPF アプリケーションについて筆者がとても気に入っている機能の 1 つは、新しい Menu コントロール パラダイムです。これは WinForm メニュー項目とは異なり、メニューとサブメニューを通常のユーザー コントロールとして扱います。まず、メインの Menu コンテナ コントロールを CryptoCalc アプリケーションの最上位部分にドラッグしました。

  <Menu Height="22" Name="menu1"
    VerticalAlignment="Top" IsMainMenu="True" >
  </Menu>

Visual Studio 2008 では、子 MenuItem コントロールのドラッグ アンド ドロップ デザインがサポートされないため、最上位の [File] メニュー項目を追加することでメニュー項目を XAML 定義ファイルに手動で追加しました。

  <MenuItem Header="_File">
    <MenuItem Header="_New" Name="fileNew" />
  <MenuItem Header="_Open" Name="fileOpen" />
  <Separator />
  <MenuItem Header="E_xit" Name="fileExit"
    InputGestureText="Alt-F4" ToolTip="Exit CryptoCalc"
    Click="OnFileExit" />
</MenuItem>

WPF デザインでは、_File などの通常のアクセラレータ キー構文がサポートされます。<Separator /> タグは明確で簡単です。ToolTip 属性は、関連付けられた MenuItem コントロールをユーザーがマウスでポイントしたときに短いヘルプ メッセージを表示するコードを生成します。コンパイルされると、Click OnFileExit 属性は次のシグネチャを持つイベント ハンドラを予期する C# コードを生成します。

public void OnFileExit(object sender, RoutedEventArgs e) {
  //...       
}

このため、通常のアプリケーション ロジック コードを CryptoCalc アプリケーションに追加した後で、イベント ハンドラを手動で追加し、this.Close をメソッド本体に配置することで機能を提供しました。[New] および [Open] メニュー項目コントロールには、この時点で関連付けられているイベントはありません。UI テスト自動化は一般に多くのアプリケーション機能が完成していない開発中に行われることを指摘するために、この処理を行いました。最上位の Help コントロールのデザインには、File コントロールと同じパターンを使用しています。

<MenuItem Header="_Help">
 <MenuItem Header="Help Topics" />
 <Separator />
 <MenuItem Header="About CryptoCalc" />
</MenuItem>

Menu コンテナと MenuItem コントロールの定義では、図 2 のスクリーンショットに示すユーザー インターフェイスを生成する C# コードが生成されます。UI デザインが完了してから、コード ビューに切り替えました。次に、Visual Studio によってファイル Window1.xaml.cs に生成された using ステートメントに 2 つの using ステートメントを追加して、名前を完全に修飾しなくても暗号化クラスとメソッドにアクセスできるようにしました。

using System.Security.Cryptography;
using System.IO;

fig02.gif

図 2 WPF アプリケーションの [File] メニュー

単純な CryptoCalc アプリケーションの主要機能は button1_Click メソッドに記述されており、その内容を図 3 に示します。コードでは、最初に textBox1 コントロールのテキストを取得し、テキストをバイト配列に変換します。コード例を簡潔にするために、運用環境で実行する通常のエラー チェックは省略します。

図 3 StatCalc アプリケーション コード

private void button1_Click(object sender, RoutedEventArgs e)
{
  string input = textBox1.Text;
  byte[] inputBytes = Encoding.UTF8.GetBytes(input);

  if (radioButton1.IsChecked == true) { 
    MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
    byte[] hashedBytes = md5.ComputeHash(inputBytes);
    textBox2.Text = BitConverter.ToString(hashedBytes);
  }
  else if (radioButton2.IsChecked == true) { 
    SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider();
    byte[] hashedBytes = sha.ComputeHash(inputBytes);
    textBox2.Text = BitConverter.ToString(hashedBytes);
  }
  else if (radioButton3.IsChecked == true) { 
    DESCryptoServiceProvider des = new DESCryptoServiceProvider();
    byte[] blanks = System.Text.Encoding.UTF8.GetBytes("        "); // 8 spaces
    des.Key = blanks;
    des.IV = blanks;
    des.Padding = PaddingMode.Zeros;
    MemoryStream ms = new MemoryStream();
    CryptoStream cs =
      new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
    cs.Write(inputBytes, 0, inputBytes.Length);
    cs.Close();
    byte[] encryptedBytes = ms.ToArray();
    ms.Close();
    textBox2.Text = BitConverter.ToString(encryptedBytes);
  }
} 

アプリケーション ロジックは、選択された RadioButton コントロールに応じて分岐します。興味深いことに、IsChecked プロパティは ?bool (Null を許容する Boolean) 型を返すため、プロパティが true と等しいかどうかを明示的にチェックする必要がありました。MD5 ハッシュおよび SHA1 ハッシュのコードについては改めて説明するまでもないでしょう。

DES 暗号化アルゴリズムには、64 ビット キーが必要です。ハッシュには、エンコードとデコードではなく DES を使用しているため、8 つの空白文字で生成されたダミー キーを使用します。ハッシュの目的に対称キー暗号化を使用する場合、通常は { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } の null キーを使用しますが、DESCryptoServiceProvider オブジェクトはこれを既知の弱いキーとしてフラグを設定し、例外をスローします。同じブランク スペースのバイト配列を使用して、いわゆる初期化ベクタの値を指定します。

DES 暗号化は 8 バイトのブロックを処理するため、入力のサイズが 8 バイトの倍数になるまで入力にゼロがパディングされるように、PaddingMode.Zeros を指定します。ゼロでパディングすると復号化できないため、エンコードとデコードには PaddingMode.Zeros を使用しないことに注意してください (復号化アルゴリズムは、暗号テキスト中のどのゼロがパディングで、どのゼロが元のプレーンテキストの一部であるかを判断できません)。

UI テスト自動化

Microsoft UI オートメーション ライブラリを使用してテスト自動化を記述する場合は、テスト対象のアプリケーションでコントロールを識別する方法を理解しておく必要があります。そのための優れた方法は、UISpy ツールを使用することです。ご存じない方のために説明すると、UISpy は古い Spy++ ツールに相当する WPF の機能で、これを使用して WPF アプリケーションの UI コンポーネントのプロパティを調べることができます。UISpy ツールは Microsoft Windows SDK の一部であり、microsoft.com/downloads から無料でダウンロードできます。

図 4 のスクリーンショットは、CryptoCalc アプリケーションで Button コントロールをターゲットに設定した結果を示しています。テスト自動化の観点から、アプリケーション コントロールの識別に重要なフィールドは、ControlType (この場合は ControlType.Edit)、AutomationId (button1)、および Name (Compute) の値です。アプリケーション コントロールを操作するのに重要なフィールドは、ControlPatterns リスト (Invoke) です。

fig04.gif

図 4 UISpy でのコントロールの検証

この CryptoCalc アプリケーションのような小さいアプリケーションでさえ、ユーザー インターフェイスを使用して手動でテストするとなると、面倒で、問題が発生しやすく、手間がかかって非効率的です。入力を行い、[Compute] ボタン コントロールをクリックし、応答を目で確認し、合否結果を手動で記録する必要があります。これよりはるかに優れた方法で、Microsoft UI オートメーション ライブラリを使用してユーザーのアプリケーション操作をシミュレートし、アプリケーションが正しく応答したかどうかを確認するテスト自動化を記述できます。面倒なテスト ケースを自動化することで、経験や直感が大きくものを言う、もっとテストしがいのある有益な手動のテスト ケースに空いた時間を費やすことができます。

図 1 のような出力を生成したテスト ハーネスの全体的な構造を図 5 に示します。Visual Studio 2008 を起動し、新規のコンソール アプリケーション プログラムを作成しました。サンプルでは C# を使用しましたが、このテスト自動化のコードを必要に応じて Visual Basic .NET に変換することも簡単にできます。次に、UIAutomationClient.dll ライブラリおよび UIAutomationTypes.dll ライブラリへのプロジェクト参照を追加しました。これらのライブラリは .NET Framework 3.0 に含まれていますが、既定では Visual Studio プロジェクトには表示されません。ライブラリは通常 C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0 ディレクトリにあります。UIAutomationClient.dll ライブラリには、テスト自動化に必要な主要クラスが含まれています。UIAutomationTypes.dll ライブラリには、MUIA テスト自動化で使用される各種の型定義が含まれています。

図 5 UI テスト自動化のコード構造

using System;
using System.Diagnostics; 
using System.Threading; 
using System.Windows.Automation; 

namespace Harness {
  class Program {
    static void Main(string[] args) {
      try {
        Console.WriteLine("\nBegin WPF UIAutomation test run\n");
        // launch CryptoCalc application
        // get reference to main Window control
        // get references to user controls
        // manipulate application
        // check resulting state and determine pass/fail
        Console.WriteLine("\nEnd automation\n");
      }
      catch (Exception ex) {
        Console.WriteLine("Fatal: " + ex.Message);
      }
    } // Main()
  } // class
} // ns

利便性を考えて、Process クラスを簡単に使用するための System.Diagnostics 名前空間および Thread.Sleep() メソッドを簡単に使用するための System.Threading 名前空間を参照する using ステートメントを追加しました。また、テスト自動化の一般的な処理として、ハーネスを最上位の try/catch ブロックでラップして致命的なエラーに対処しています。テスト自動化のサンプル コードでは、まずテスト対象アプリケーションを起動しています。

Console.WriteLine("Launching CryptoCalc application");
Process p = null;
p = Process.Start("..\\..\\..\\CryptoCalc\\bin\\Debug\\CryptoCalc.exe");

ここで、テスト自動化を試行する前に、テスト対象の CryptoCalc アプリケーションに関連付けられているプロセスがホスト マシンに登録されていることを確認する必要があります。Thread.Sleep ステートメントを挿入するだけでテスト ハーネスを一時停止できますが、一時停止の長さを認識するための適切な方法がありません。この場合は、スマート遅延ループを使用する方が賢明です。

int ct = 0;
do {
  Console.WriteLine("Looking for CryptoCalc process. . . ");
  ++ct;
  Thread.Sleep(100);
} while (p == null && ct < 50);

ここでは、遅延ループを通過するたびに 100 ミリ秒停止します。プロセス オブジェクトが null 以外になってプロセスが見つかったことが示されるか、ループが 50 回実行されると、ハーネスが遅延ループを抜けます。図 6 に、遅延ループがタイムアウトになったか、または AUT プロセスが見つかったかを判断する方法を示します。

図 6 何が起きたかを判断する

if (p == null)
  throw new Exception("Failed to find CryptoCalc process");
else
  Console.WriteLine("Found CryptoCalc process");

// Next I fetch a reference to the host machine's Desktop as an
// AutomationElement object:

Console.WriteLine("\nGetting Desktop");
AutomationElement aeDesktop = null;
aeDesktop = AutomationElement.RootElement;
if (aeDesktop == null)
  throw new Exception("Unable to get Desktop");
else
  Console.WriteLine("Found Desktop\n");

テスト対象の WPF アプリケーションのすべてのコントロールは、メイン アプリケーションの Window コントロールの子と考えることができます。メインの Window は、全体的な Desktop の子であるため、アプリケーションへの参照を取得するには Desktop への参照が必要です。ここで、FindFirst メソッドを使用してテスト対象のアプリケーションにアタッチします。

AutomationElement aeCryptoCalc = null;
int numWaits = 0;
do {
  Console.WriteLine("Looking for CryptoCalc main window. . . ");
  aeCryptoCalc = aeDesktop.FindFirst(TreeScope.Children,
    new PropertyCondition(AutomationElement.NameProperty, "CryptoCalc"));
  ++numWaits;
  Thread.Sleep(200);
} while (aeCryptoCalc == null && numWaits < 50);

一時停止の長さを制御できない Sleep 手法ではなくスマート遅延ループ手法を使用します。FindFirst メソッドは、探しているコントロールが単一のコントロールである場合に使用します。FindFirst は 2 つの引数を受け取ります。最初の引数はスコープ値です。テスト自動化で使用される最も一般的な 3 つのスコープ型は、TreeScope.Children、TreeScope.Descendants、および TreeScope.Parent です。CryptoCalc アプリケーションは Desktop の直接の子であるため、TreeScope.Children スコープを使用します。

FindFirst の 2 つ目の引数は、探しているコントロールを識別する情報を表すオブジェクトです。ここで、値が "CryptoCalc" の Name プロパティを持つコントロールを探していることを指定します。後で簡単に説明しますが、AutomationId プロパティも使用できます。次に、メイン アプリケーションの Window コントロールへの参照があることを確認します。

if (aeCryptoCalc == null)
  throw new Exception("Failed to find CryptoCalc main window");
else
  Console.WriteLine("Found CryptoCalc main window");

アプリケーションが完成したら、その参照を使用して、テスト自動化によって操作または検証されるすべてのユーザー コントロールへの参照を取得します。まず、Button コントロールを取得します。

Console.WriteLine("\nGetting all user controls");
AutomationElement aeButton = null;
aeButton = aeCryptoCalc.FindFirst(TreeScope.Children,
  new PropertyCondition(AutomationElement.NameProperty, "Compute"));
if (aeButton == null)
  throw new Exception("No compute button");
else
  Console.WriteLine("Got Compute button");

メインの Window への参照を取得するために使用したのと同じパターンを使用します。ここでは、Button コントロールはメイン Window アプリケーション コントロールの直接の子です。Button コントロールは静的コントロールであるため、コントロールへの参照にアクセスする前に遅延ループ手法を使用する必要はありません。動的コントロールの場合は、遅延ループ手法を使用する必要があります。

次に、2 つの TextBox コントロールへの参照を取得します。2 つの TextBox コントロールには、textBox1 および textBox2 という名前が付いていますが、コントロールは Name プロパティを受け取らないため、Button コントロールに使用したのと同じ NameProperty パターンは使用できません。ただし、TextBox コントロールは、次のように、コントロールへの参照を取得するために使用できた AutomationId プロパティを受け取ります。

aeTextBox1 = aeCryptoCalc.FindFirst(TreeScope.Children,
  new PropertyCondition(AutomationElement.AutomationIdProperty, "textBox1"));

しかし、主にデモンストレーションの目的で、別のアプローチを使用することにしました。コントロールの name プロパティを使用して単一のコントロールを識別するのではなく、FindAll メソッドを使用してコントロールの型によってコントロールのコレクションをフェッチします。結論を言えば、TextBox コントロールは ControlType.Edit 型であるため、コードではすべての TextBox コントロールを取得します。

AutomationElementCollection aeAllTextBoxes = null;
aeAllTextBoxes = aeCryptoCalc.FindAll(TreeScope.Children,
  new PropertyCondition(AutomationElement.ControlTypeProperty,
  ControlType.Edit));
if (aeAllTextBoxes == null)
  throw new Exception("No textboxes collection");
else
  Console.WriteLine("Got textboxes collection");

このコレクションを取得すると、配列インデックス付けを使用して各 TextBox にアクセスできます。

AutomationElement aeTextBox1 = null;
AutomationElement aeTextBox2 = null;
aeTextBox1 = aeAllTextBoxes[0];
aeTextBox2 = aeAllTextBoxes[1];
if (aeTextBox1 == null || aeTextBox2 == null)
  throw new Exception("TextBox1 or TextBox2 not found");
else
  Console.WriteLine("Got TextBox1 and TextBox2");

一般に、Name プロパティまたは AutomationId プロパティを使用してコントロールの参照を取得することは、ControlType を使用するよりも優れた方法ですが、場合によっては選択肢がないことがあります。次に、テスト シナリオで使用する RadioButton コントロールへの参照を取得します。

AutomationElement aeRadioButton3 = null;
aeRadioButton3 = aeCryptoCalc.FindFirst(TreeScope.Descendants,
  new PropertyCondition(AutomationElement.NameProperty,
   "DES Encrypt"));
if (aeRadioButton3 == null)
  throw new Exception("No RadioButton");
else
  Console.WriteLine("Got RadioButton3");

Button コントロールへの参照を取得するために使用したのと同様のパターンを使用します。ただし、TreeScope.Children ではなく TreeScope.Descendants を指定していることに注意してください。RadioButton コントロールは GroupBox コントロールの子であり、メインの Window コントロールの直接の子ではないため、このようにしています。別の方法として、最初に GroupBox コントロールへの参照を (メインの Window コントロールの子として) 取得してから、その参照を使用して RadioButton コントロールへの参照を取得することもできます。コントロールへの参照を入手すると、テスト対象のアプリケーションの操作を開始できます。まず、ユーザーによる TextBox1 コントロールへの入力をシミュレートします。

Console.WriteLine("\nSetting input to 'Hello1!'");
ValuePattern vpTextBox1 =
  (ValuePattern)aeTextBox1.GetCurrentPattern(ValuePattern.Pattern);
vpTextBox1.SetValue("Hello!");

SetValue メソッドの使用方法はお馴染みだと思いますが、ここでは aeTextBox1 オブジェクトで直接 SetVaue() にアクセスしていないことに注意してください。代わりに、中継手段として ValuePattern オブジェクトを使用しています。ValuePattern などの AutomationPattern オブジェクトの概念は、Microsoft UI オートメーション ライブラリに馴染みのないエンジニアにとって最も理解しにくい概念でしょう。パターン オブジェクトは、コントロールの型や外観から独立したコントロールの機能を公開する抽象化と考えることができます。つまり、ValuePattern などの特定の AutomationPattern インスタンスを使用して、コントロールの特定の機能を有効にすることができるということです。

さらにわかりやすく言えば、コントロールの ControlType はコントロールの種類を公開し、コントロールの Pattern はコントロールが実行可能な機能を公開します。同様の方法を使用して、ユーザーによる RadioButton3 コントロールの選択をシミュレートします。

Console.WriteLine("Selecting 'DES Encrypt' ");
SelectionItemPattern spSelectRadioButton3 =
  (SelectionItemPattern)aeRadioButton3.GetCurrentPattern(
    SelectionItemPattern.Pattern);
spSelectRadioButton3.Select();

ここでは、SelectionItemPattern を使用して、選択を有効にしています。GetCurrentPattern メソッドは、MUIA ライブラリの初心者にとっては紛らわしい名前です。テスト自動化の観点から見ると、このメソッドは特定の AutomationPattern を取得するのではなく、設定します。ところが、クライアント/サーバーの観点から見ると、自動化クライアント コードは、テスト対象アプリケーションのサーバー コードから特定のプロパティをフェッチする処理を行います。

[Calculate] ボタン コントロールのクリックをシミュレートする次のコード例を見れば、話がわかりやすくなります。

Console.WriteLine("\nClicking on Compute button");
InvokePattern ipClickButton1 =
  (InvokePattern)aeButton.GetCurrentPattern(
    InvokePattern.Pattern);
ipClickButton1.Invoke();
Thread.Sleep(1500);

つまりここでは、InvokePattern を使用してボタン クリックを有効にし、Invoke メソッドでクリックを実行しています。アプリケーションが応答するまで 1.5 秒停止します。代わりに、遅延ループを使用して、結果を示す textBox2 フィールドが空であるかどうかを定期的にチェックすることもできます。ここまで、テスト自動化のサンプル コードでは、テスト対象アプリケーションを起動し、入力 TextBox コントロールに「Hello!」と入力し、[DES Encrypt] ラジオ ボタン コントロールを選択し、[Compute] ボタン コントロールをクリックしました。

次に、TextBox2 コントロールに予期される適切な値が入力されているかどうかを確認します。

Console.WriteLine("\nChecking TextBox2 for '91-1E-84-41-67-4B-FF-8F'");
TextPattern tpTextBox2 =
  (TextPattern)aeTextBox2.GetCurrentPattern(TextPattern.Pattern);
string result = tpTextBox2.DocumentRange.GetText(-1);

ここでは TextPattern を使用して、GetText メソッドの呼び出しを準備します。また、DocumentRange プロパティを使用して GetText を間接的に呼び出しています。これによりドキュメント (この場合は単一のテキスト ボックス) のメイン テキストのテキスト範囲が返されます。返される文字列の最大長が制限されないように、GetText に -1 という引数を使用しています。TextBox2 コントロールの内容を読み取る場合、GetCurrentPropertyValue メソッドを使用することもできます。

string result =
  (string)aeTextBox2.GetCurrentPropertyValue(ValuePattern.ValueProperty);

テスト ハーネスへのテスト ケース入力はハードコーディングしました。より柔軟な方法は、いくつかの外部データ ストアからテスト ケースの入力と予期される値を読み取ることです。ここで、手元のテスト対象アプリケーションの実際の値を使用して予期される値と入力値を照合し、テスト シナリオの合否結果を判定します。

if (result == "91-1E-84-41-67-4B-FF-8F") {
  Console.WriteLine("Found it");
  Console.WriteLine("\nTest scenario: Pass");
} 
else {
  Console.WriteLine("Did not find it");
  Console.WriteLine("\nTest scenario: *FAIL*");
}

単純に、コマンド シェルにテスト シナリオの結果を表示します。運用環境では、通常は結果を外部ストレージに書き込みます。

完成したテスト シナリオを使用して、Menu コントロールを調べることでテスト対象のアプリケーションを終了できます。まず、最上位の File MenuItem コントロールを取得します。

Console.WriteLine("\nClicking on File-Exit item in 5 seconds");
Thread.Sleep(5000);
AutomationElement aeFile = null;
aeFile = aeCryptoCalc.FindFirst(TreeScope.Descendants,
  new PropertyCondition(AutomationElement.NameProperty, "File"));
if (aeFile == null)
  throw new Exception("Could not find File menu");
else
  Console.WriteLine("Got File menu");

File MenuItem コントロールは Menu コンテナ コントロールのサブコントロールであるため、TreeScope.Descendants スコープを使用しています。ここで、ユーザーによる [File] 項目のクリックをシミュレートします。

Console.WriteLine("Clicking on 'File'");
ExpandCollapsePattern expClickFile =
  (ExpandCollapsePattern)aeFile.GetCurrentPattern(ExpandCollapsePattern.Pattern);
expClickFile.Expand();

サブメニューを持つ MenuItem コントロールは、予想どおりに Invoke パターンを公開しません。Expand パターンを公開します。[File] サブメニュー項目がレンダリングされて表示されたら、Exit コマンドへの参照を取得できます。

AutomationElement aeFileExit = null;
aeFileExit = aeCryptoCalc.FindFirst(TreeScope.Descendants,
  new PropertyCondition(AutomationElement.NameProperty, "Exit"));
if (aeFileExit == null)
  throw new Exception("Could not find File-Exit");
else
  Console.WriteLine("Got File-Exit");

これで、Exit サブメニューに InvokePattern を使用して、テスト対象の CryptoCalc アプリケーションを終了できます。

InvokePattern ipFileExit =
  (InvokePattern)aeFileExit.GetCurrentPattern(InvokePattern.Pattern);
ipFileExit.Invoke();
Console.WriteLine("\nEnd automation\n");

これでサンプルのテスト自動化は完成し、テスト ケースの結果を記録して、別のテスト シナリオを起動できるようになりました。

まとめ

今月のコラムでは、WPF アプリケーションのカスタム テスト自動化を作成するのに十分な基礎を説明しています。MUIA ライブラリは非常に広範で、ほとんどの単純なテスト シナリオを処理できます。

ここで示した単純な例を調整して独自の WPF アプリケーションを簡単にテストできます。WPF アプリケーションを作成する場合は、AutomationID が生成されるようにするために、すべてのコントロールに XAML Name 属性があることを確認してください。ユーザー コントロールの識別および操作方法を判断するには、UISpy ツールを使用します。ユーザー コントロールの状態と値の検証に使用できる MUIA パターンを判断します。この情報を入手すると、最も基本的な UI テスト自動化のシナリオを処理できます。

すべてのテスト状況で、テスト自動化を作成するために必要な作業と、自動化によって得られる利益とを比較検討する必要があります。筆者の経験によると、WPF UI テスト自動化は、一般に比較的単純なシナリオの回帰テストに使用するのが最適です。これにより、複雑なシナリオの手動テストに集中し、開発で新機能を追加したときに誤って混入された明らかなバグを見失うことを心配せずに、新しい小さなバグを発見できます。

一般に、このコラムで説明した軽量タイプのテスト自動化では、テスト自動化の作成にかかる時間が 4 時間未満の場合に、時間の投資に対して妥当な価値が得られます。もちろん、環境はそれぞれ異なるので、ポイントは、テスト リソースを最大限に利用する方法はどのような場合でも UI テスト自動化であると思い込まないことです。WPF は比較的新しいテクノロジです。しかし、WPF アプリケーションが増えるにつれて、このコラムで説明した UI テスト自動化手法が、より優れたソフトウェアの作成に役立つ可能性が高くなります。

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