こんにちは、キャスレーコンサルティングのSI(システム・インテグレーション)部:藤沢です。
今回は、UI Automationを使用してC#のアプリケーションのUI部分のテストの自動化を行います。
UI Automationは、デスクトップ上のほとんどの user interface (UI) 要素へのプログラムによるアクセスを提供し、
スクリーン リーダーなどの補助技術製品が UI に関する情報をエンド ユーザーに提供したり、
標準入力方式以外の方法で UI を操作したりできるようにします。
また、UI オートメーションは、自動テスト スクリプトが UI と対話できるようにします。(MSDNより)
この機能を利用することでアプリケーションのUIを操作が行えます。
今回の環境
- Windows10
- Visual Studio 2015
- テストアプリケーション(WPF, .Net FrameWork4.5)
- NUnit(GuNet)
テストアプリケーション
簡単な電卓のようなアプリケーションを作成し、そのUI部のテストを行います。
簡単な電卓の仕様として、テキストボックス(2個)とコンボボックス(演算子)と
ボタンで解答を求めるアプリケーションにします。
下記のような画面になっています。
テストプロジェクト
VisualStudioの拡張機能を使用してVS上でテスト実行します。
そのため、「NUnit3 Test Adapter」をインストールします。
「ツール」 – 「拡張機能と更新プログラム」から「NUnit3 Test Adapter」を検索
テストプロジェクトを作成します。
「ファイル」-「追加」-「新しいプロジェクト」から「単体テストプロジェクト」を選択し作成します。
この作成したプロジェクトでNUnitを使用できるようにするため、NuGetパッケージマネージャーもしくは、
パッケージマネージャーコンソールからNuGetからNUnitをインストールします。
このテストプロジェクトにUI Automationを使用するための参照を追加します。
追加するアセンプリは、「UIAutomationClient」「UIAutomationTypes」を選択します。
テストシナリオ・テストコード
- 入力テキストボックスに値が正しく入力されるか。
- テキストボックスに「1234」と「5678」を入力し、その2つを減算した時の解答が正しく表示されるか。
using NUnit.Framework; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Windows.Automation; namespace SimpleCalcTest { [TestFixture] public class SimpleCalc { /// <summary> /// テスト対象EXE /// </summary> private string exePath = Path.Combine(”****”, "****.exe"); /// <summary> /// UI Automation要素 /// </summary> private AutomationElement aeWindow; /// <summary> /// テストセットアップ /// サンプル対象アプリケーションを起動 /// </summary> [SetUp] public void SetUp() { // アプリケーションを起動 var app = Process.Start(exePath); // 起動するまで待つ(1秒) Thread.Sleep(1000); aeWindow = AutomationElement.FromHandle(app.MainWindowHandle); } /// <summary> /// 終了 /// テスト対象アプリケーションを終了 /// </summary> [TearDown] public void TearDown() { var wp = aeWindow.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern; wp.Close(); } /// <summary> /// テストメソッド /// テキストボックスAに"123456789"を入力し正しいか /// </summary> [Test] public void TestTextBoxA() { var inputText = "123456789"; // テキストボックスAに入力し入力が正しいか var inputA = findElementById(aeWindow, "txtA"); var vpTextA = inputA.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern; vpTextA.SetValue(inputText); Assert.AreEqual(inputText, vpTextA.Current.Value); } /// <summary> /// テストメソッド /// テキストボックスBに"987654321"を入力し正しいか /// </summary> [Test] public void TestTextBoxB() { var inputText = "987654321"; // テキストボックスAに入力し入力が正しいか var inputB = findElementById(aeWindow, "txtB"); var vpTextB = inputB.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern; vpTextB.SetValue(inputText); Assert.AreEqual(inputText, vpTextB.Current.Value); } /// <summary> /// テストメソッド /// 計算結果が正しく表示されるか /// </summary> [Test] public void TestMethod() { // テキストボックスに"1234"を入力 var inputA = findElementById(aeWindow, "txtA"); var vpTextA = inputA.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern; vpTextA.SetValue("1234"); // テキストボックスに"5678"を入力 var inputB = findElementById(aeWindow, "txtB"); var vpTextB = inputB.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern; vpTextB.SetValue("5678"); // コンボボックスを"-"に選択 var inuptOpe = findElementById(aeWindow, "cmbOpe"); // コンボボックスを開く var ecpOpe = inuptOpe.GetCurrentPattern(ExpandCollapsePattern.Pattern) as ExpandCollapsePattern; ecpOpe.Expand(); // コンボボックスの項目を取得 var itemsOpe = inuptOpe.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem)); // "-"の項目を取得 var setMinus = itemsOpe.Cast<AutomationElement>().First(w => "-".Equals(w.Current.Name)); // "-"を選択 var sipOpe = setMinus.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern; sipOpe.Select(); // コンボボックスを閉じる ecpOpe.Collapse(); // ボタン var button = findElementById(aeWindow, "button"); var ipButton = (InvokePattern)button.GetCurrentPattern(InvokePattern.Pattern); ipButton.Invoke(); // テキストボックス(解答) var inputAns = findElementById(aeWindow, "txtAns"); var vpTextAns = inputAns.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern; var ans = vpTextAns.Current.Value; // テスト結果 Assert.AreEqual("-4444", ans); } /// <summary> /// UI Automation要素からIDを指定してUI Automationを取得する /// </summary> /// <param name="element">UI Automation</param> /// <param name="automationId">ID</param> /// <param name="treeScope">スコープ</param> /// <returns></returns> private static AutomationElement findElementById( AutomationElement element, string automationId, TreeScope treeScope = TreeScope.Element | TreeScope.Descendants) { var target = element.FindFirst( treeScope, new PropertyCondition( AutomationElement.AutomationIdProperty, automationId)); return target; } } }
テスト実行
VisualStudioの「テスト」-「実行」-「すべてのテスト」でテストを実行します。
実行するとテスト対象アプリケーションが起動し、コードで記述した手順でUIが操作されます。
今回は、3回アプリケーションが起動しテストが実行されます。実行結果は下記のようになります。
さいごに
簡単なアプリケーションのUIのテストをコードで実行が行えることができました。
UI Automationですべてのテストを行うことは難しく、どの範囲でテストを行うか決めておくことをおすすめします。
さらにUI Automationで対応していないコントロールもあるため、
開発段階で使用できるコントロールを制限するなど考えてください。
今回は、自分で作成したアプリケーションが対象でしたが、
spy++などを使用することによりアプリケーションの構造を調査することも可能になります。
最後までご高覧頂きまして有難うございます。