Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9749

UIテスト自動化の話

$
0
0

いきなりですが

  最近Windows GUIアプリケーションの開発に関わるようになりました。自動テストに関して、一応ないわけではないですが、メンテ追い付かず動いたり、動かなかったりする、まぁよくある話ですね。私自身もそこまでテスト熱心なわけではないですが、何せデグレに恐れて夜眠れなくなる弱い人間なわけで、そんな心の声に従い長く付き合える自動テストを求める旅にでました。

Friendlyとの出会い

  UIの自動テスト、マウス操作をレコードし、繰り返す実行するいわゆるキャプチャリプレイーはまず思いつくでしょう。RPAという呼び方も最近のはやりらしいが、これはダメですね(RPAがだめなわけではない)、断言しましょう。アプリの反応が早かったり、遅かったりするので、確実に捕まえる保証はないからです。まぁ、実際一回見ればすぐわかる話です。というわけで最初段階で候補から外しました。
  ご存じのとおり、Windows GUIアプリ(Win32,WinForm,WPF)にはUI AutomationというUIの各種操作をシミュレートする機能を持つFWがあり、UIツリー構造を表示するVSの機能にも使用されているもので、一応自動テストに応用した記事をご紹介しますが、正直使いこなせる自信がないですね。
https://www.atmarkit.co.jp/fdotnet/special/uiautomation/uiautomation_01.html
  そんなわけでいろいろ調べて、これ以上手がかりなければもうそろそろ諦めるという自分の勘所に来てこころが曇ってきたある日、Friendlyというものが目に入りました、最初に飛び込んだのはかずきさんの記事でした、まだFriendlyそのものの存在はしならい。
https://blog.okazuki.jp/archive/category/Friendly

初見の感想

  かずきさんは足し算アプリの自動テストを紹介しました。UIを捕まえるにはByBindingという見慣れないもので、全部のUIが都合よくBinding使っているわけではないし、文字列なのでインテリセンス効かないし、変わってもビルド時気づかないし、どういうものかいまいちよさ理解できない自分がいました(ByTypeがあるのは知らないだけでした)。とはいえいままでにない期待感がわいてきましたので、早速入れてみることに

実際使ってみる

 同時にFriendlyというキーワードを意識し、ネット記事の探しまくりです。そこでQiitaの2014年のAdvent Canlenderにたどり着き、作者は日本の方で、制作するまでのいきさつも語られてて、Frienldyを詳しくしりたいならこの方ブログがおすすめです。
https://qiita.com/advent-calendar/2014/friendly
http://ishikawa-tatsuya.hatenablog.com/
 
 前置き長くなりました、本題に入ります。
 まずここのソースを実際動かして感覚つかめることにしました。主要API使い方のを中心に、かなり洗練されている感じです、最初の人はまず見ておいたほうがいいと思います。
https://github.com/Ishikawa-Tatsuya/WPFFriendlySampleDotNetConf2016

 いくつかポイントをご紹介します。詳しい説明ではなく、メモ程度のものなので、ご容赦ください。

//このXamlを前提に話します()<Windowx:Class="WpfApp1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfApp1"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><Grid><TextBoxx:Name="_text1"Text="{Binding Text1}"/><TextBoxx:Name="_text2"Text="{Binding Text2}"/><ButtonContent="Command1"Command="{Binding Command1}"/><ButtonContent="Command2"Command="{Binding Command2}"/></Grid></Window>
//Friendlyの足場を作る作業vardir=Path.GetFullPath("../../../WpfApp1/bin/Release");varpathExe=dir+"/WpfApp1.exe";varinfo=newProcessStartInfo(pathExe){WorkingDirectory=dir};Process=Process.Start(pathExe);//対象アプリケーションの起動app=newWindowsAppFriend(Process);//魔法の時間、これで相手のプロセスに潜り込んでやりたい放題//UIインスタンスをつかんでみる//おなじみのApplication.Current.MainWindowですね、//MainWindowここにきてるように見えますが、実はきてません(相手プロセスにいます)AppVarmainWindow=app.Type<Application>().Current.MainWindow;// 明らかに一個しかない場合はこれで特定できるが、上のXamlではエラーになります。varonlyOne=mainWindow.LogicalTree().ByType<TextBox>().Single();// 普通はもう一段ByBinding書いて対象を絞る。    // ByType,ByBidingはDependencyObjectのコレクションを返すのでメソッドチェンできるvartextbox1=mainWindow.LogicalTree().ByType<TextBox>().ByBinding("Text1").Single();// 型参照できない場合は文字列のインターフェスを使うvarunkwownType=mainWindow.LogicalTree().ByType("ThirdPartyTextBox").Single();// ちなみにあんまりおすすめできないがインデックスアクセスできます    varcommand1Button=mainWindow.LogicalTree().ByType<Button>()[0];
    //x:Nameでの捕まえ方、実はリフレクションを使ったフィールドアクセス(と思います)    vartextbox2=mainWindow.Dynamic()._text2;// リフレクションなのでVisablity関係ないですから、DataContextもとれる// MVVMを採用しれいれば、通常DataContextが内部API詰まってるので、ユニットテストに活用する手もありです// ちなみに、これは結合した状態の生きたインスタンスなので、普段ユニットテストで足場を作る作業は不要ですよ。    vardataContext=mainWindow.Dynamic().DataContext;// staticメンバーへのアクセス    //インスタンス前提で話してきたが、staticメンバーの場合はこれでアクセスできます。(結構はまりました)    varstaticMember=app.Type<MainWindowVM>().StaticMember
//UIのふるまいをシミュレートするWPFTextBoxwpfTextBox=newWPFTextBox(textbox1);   wpfTextBox.EmulateChangeText("NewValue");WPFButtonBasewpfButtonBase=newWPFButtonBase(command1Button);wpfButtonBase.EmulateClick();

UIテストの難所

  いかがでしょうか、初歩的なこと一通り書きましたが、まぁ、実際のUIの操作は複雑でこんな一筋にはいきません。たとえばモーダルダイアログはどうでしょう。スレッドが止まるから相手プロセスに行ったきりですよね。そこでWindowControlとAsyncという仕組みが用意されていました。

http://ishikawa-tatsuya.hatenablog.com/entry/2015/01/05/230614
詳しくはここに書かれていますので、要点だけ

varmainWindow=newWindowControl(app);varmodalDialogButton=app.LogicalTree().ByType<ModalDialogButton>().Single;//Asyncでクリックする、スレッドは止まらないvarasync=newAsync();buttonModal.EmulateClick(async);//モーダルダイアログが表示されるのを確実に待ち合わせるvardlg=mainWindow.WaitForNextModal();//ダイアログ上のボタンを押す、dlgからしか取れません。varbuttonOK=newWPFButtonBase(dlg.Dynamic()._buttonOK);buttonOK.EmulateClick();//非同期で実行したモーダルボタン押下の処理が完全に終了するのを待つasync.WaitForCompletion();

  これで一般的なモーダルダイアログパターンを突破できたわけですが、実はダイアログが表示されるか不確定な場合は結構厄介で悩みところです。表示されない場合のWaitForNextModal()は現在のTopのダイアログが返されて。dynamic型なので確実に型を判定できないのでそのあとの処理できず結構焦りました。

最後

 そんなところですが、UIテスト自動化の戦いがまだまだ続きそうです。これからもFriendlyと末永く付き合いたいので、また新たなネタがありましたら書いていきたいと思います。


Viewing all articles
Browse latest Browse all 9749

Trending Articles