03-13-2012, 08:01 PM
以前、Window Tester Proを使って、我々の製品であるCurl のIDEテスト自動化に適用できるかの検証をやってみましたので、GoogleがWindowTester Proをオープンソース化というニュースが最近出て、少し「あれっ?」と思いました。なぜかと言うと、googleのこの決定は2010年12月に出たので、ちょっと違和感(いまだに?)がある気がしました。が、ネットでうろうろしたら、あっ、オープンソース後の初めてのリリースがこのニュースのきっかけかと気づき納得しました。Windows Tester Proを検証する時に手掛かりが少なく、その時にもしオープンソース版が出たら、ぶつかった壁がもう少し和らいだかもしれません(笑)。
しかし、先ほどWindow Tester Proの本家 に行ってよく確認してみたら、Whats's newを読んでも、別に新しい機能なり、バグ修正なりの情報が全くありませんでした。あれれ、このオープンソース後の初期リリース は何の為だった?バージョンを5.1から6.0に挙げただけ?数か月前に検証した状態と変わってないじゃん、なぞなぞですね...。
Dutch (Belgium)
French (France)
Spanish (Spain)
Swedish (Sweden)
Japanese (Japan)
ほかにもたくさんのケースを試した結果、結局解消できない:
ui.enterText("C:\\ReadOnlyDir");
// C:\Ra になる、また、画面上誤動作が引き起こす
// (旧メーカ掲示板情報による対応、ALT+Shortcutキーのような場合発生する、ログによると、e -> e + ALT になる)
プラットフォーム制限
“Finished”ボタンが使用可能の判断条件:
ui.assertThat("Finish button should be enabled", new
IsEnabledCondition(new ButtonLocator("Finished"), true));
ui.assertThat("The error message is wrong", new
HasTextCondition(new WizardErrorMessageLocator(), "some wizard
message"));
//conditions processed before menu selection
[*]その他メモ
[*]デフォルトはEclipseのWelcome画面がテストされません、テストする必要になる場合、次のように、セットアップメソッドのカスタマイズなどが必要です。例:
テストケースで保存されてないEditorバッファがある場合の対応。未保存のEditorバッファがある場合、テストが終了できない、または終了に時間がかかる(UIの生成がある為)。それを回避するために、コードでバッファを保存するような処理を書く必要がある。
[*]対象外部品の特定
しかし、先ほどWindow Tester Proの本家 に行ってよく確認してみたら、Whats's newを読んでも、別に新しい機能なり、バグ修正なりの情報が全くありませんでした。あれれ、このオープンソース後の初期リリース は何の為だった?バージョンを5.1から6.0に挙げただけ?数か月前に検証した状態と変わってないじゃん、なぞなぞですね...。
さて、この日本ではあまりなじみのないツールをせっかく検証しましたので、ちょっと古くなったところがあるかもしれませんが(先ほどの確認によると、この可能性もあまりないですね)、ご参考になる方もいるかもしれませんので、情報をみなさんとシェアしたいと思います。
Window Tester Proを利用したい方は、ここのサイトから最新のコードを入手してください:
目次
- 1 概要
- 2 開発元
- 3 セットアップ
- 3.1 Eclipse のインストール(検証はEclipse3.6.2を利用する)。
- 4 制限事項
- 4.1 言語サポート
- 4.2 プラットフォーム制限
- 4.3 OSの制限
- 5 利用方法
- 5.1 記録機能の設定
- 5.2 CDEテスト時の注意点
- 5.3 記録問題
- 5.3.1 Curl側の記録ができない
- 5.3.2 Drag&Dropが正しく記録できないケースがある
- 5.3.3 記録漏れがある
- 5.3.4 ご操作などによって余計な操作を記録される場合がある
- 5.3.5 ロケールの問題
- 5.3.6 記録コードが実行できない場合がある(文法エラー)
- 5.4 検証条件の追加
- 5.4.1 Assertion Inspectorの使用
- 5.4.2 検証の詳細
- 5.4.2.1 検証条件
- 5.4.2.2 普通のUI部品の属性検証(使用可否・テキスト・選択・可視性・フォーカス)
- 5.4.2.3 検証用ファクトリ
- 5.4.2.4 条件確保
- 5.4.2.5 手動で条件検証を作成する
- 5.4.3 条件と条件ハンドラー
- 5.4.3.1 条件
- 5.4.3.2 ハンドラー
- 5.5 再生時の問題
- 5.5.1 JavaのUIでも、再生する時に、停止されている場合がある
- 5.5.2 記録した入力が記録時と異なるコードが再生されてしまう
- 5.6 Welcomeページのテスト
- 5.7 UI部品の特定方法
- 5.7.1 MatcherとLocator
- 5.7.2 UI部品の検索範囲
- 5.7.3 UIの階層構造の調査
- 5.7.4 部品の特定
- 5.7.5 対象外部品の対応
- 6 RCP/SWTに組み込まれたSwing 部品のテストについて
- 7 GEF (Graphical Editing Framework)について
- 8 ほかに重要な参照情報
- 9 CDEテストプランの自動化対象
概要
Window Tester ProはEclipseベースのGUI自動テストツールであり、EclipseのPluginテスト、RCPアプリテスト、SWING・SWTアプリテストなどに対応されています。主な機能は記録、テストケース生成、再生、Code Coverage確認などの機能が提供されている。生成されるテストコードはJUnit テストのフォーマットに準処している。本資料では、テスト関連の機能のみ対象とする。
開発元
Instantiationsに開発され、googleに買収された後に、Apacheにコントリビューされました。今はApache2ライセンスのオープンソースです。セットアップ
Eclipse のインストール(検証はEclipse3.6.2を利用する)。
CDE をEclipseにインストールする
Window Testerを次のURLからEclipseにインストールできる
Eclipse 3.6 (Helios)
Eclipse 3.5 (Galileo)
Eclipse3.7の正式版はまだリリースされてなく、次のサイトから、ベータ版のインストールができる
制限事項
言語サポート
次のの言語のみサポートされている(2011年/10/24, ver.6.0.0)
English
German (Germany)Dutch (Belgium)
French (France)
Spanish (Spain)
Swedish (Sweden)
Japanese (Japan)
その他: enterText の問題及び回避策
":" と日本語入力の問題,について、次の回避策は":"の問題のみ対応可能で, 日本語入力の問題はまだ対応策を見つかってません, このニュースグループWT news groupに質問したんですが、返事がすぐに来なくて、数週間たってやっと来た感じですが、もう検証が終わってしまい、対策の検証ができなかった)
/**
* Use this if ui.enterText(String
s) does not enter some chars correctly.
s) does not enter some chars correctly.
*
* @param s
* The text to be entered
* @param language
* examples: "en" for English,
"de" for German
"de" for German
* @param country
* examples: "US" for USA,
"DE" for Germany
"DE" for Germany
*/
private void
enterTextWithTempLocale(String s, String language, String country)
enterTextWithTempLocale(String s, String language, String country)
{
Locale defaultLocale =
Locale.getDefault(); // remember previous Locale
Locale.getDefault(); // remember previous Locale
Locale.setDefault(new
Locale(language, country));
Locale(language, country));
WT.setLocaleToCurrent();
ui.enterText(s); // enter the
text in the ui
text in the ui
Locale.setDefault(defaultLocale); // reset to previous locale
WT.setLocaleToCurrent();
}
上記の回避策はここから持ってきた: http://forums.instantiations.com/topic-5-3938.html
追加情報:
enterTextは上記の対策で日本語キーボードに対応でき、かな入力に対応できそうになるが、どうもenterText自体は、文字一つは一つのキーとして入力しようとする為、“プロジェクト“や“日本語”など、入力できません。具体的に、次のコードで再生する時に、Fatal Error が発生し、Eclipseがクラッシュしてしまう:
enterTextは上記の対策で日本語キーボードに対応でき、かな入力に対応できそうになるが、どうもenterText自体は、文字一つは一つのキーとして入力しようとする為、“プロジェクト“や“日本語”など、入力できません。具体的に、次のコードで再生する時に、Fatal Error が発生し、Eclipseがクラッシュしてしまう:
enterTextWithTempLocale(
"C:\\Curl マニフェスト名\\myfile.text",
"ja","JP");
また、次のようなコードで再生すると、”:”の問題が解消できたが、その後ろの文字が入力出来なくなってしまった。
enterTextWithTempLocale(
"C:\\temp\\files\\myfile.text", "ja","JP");ほかにもたくさんのケースを試した結果、結局解消できない:
//
C*\ReadOnlyDirになる(標準APIのデフォルト)
System.out.println("********ui.enterText:");C*\ReadOnlyDirになる(標準APIのデフォルト)
ui.enterText("C:\\ReadOnlyDir");
// C:\Ra になる、また、画面上誤動作が引き起こす
// (旧メーカ掲示板情報による対応、ALT+Shortcutキーのような場合発生する、ログによると、e -> e + ALT になる)
System.out.println("********LocaleTools.enterTextWithTempLocale(\"C:\\ReadOnlyDir\",\"ja\",\"JP\",ui):");
LocaleTools.enterTextWithTempLocale("C:\\ReadOnlyDir","ja","JP",ui);System.out.println("********LocaleTools.enterTextWithTempLocale(\"C:\\ReadOnlyDir\",ui):");
LocaleTools.enterTextWithTempLocale("C:\\ReadOnlyDir",ui);プラットフォーム制限
WTは現状次のEclipseバージョンのみ対応している: Eclipse3.4, 3.5, 3.6 Eclipse 3.7の対応ステータスはベータ(検証してません)
OSの制限
特になし、Linux上でも利用可。ただし、ファイルパスなど、OS依存の部分がテストケースにある場合、分岐処理をカスタマイズして対応する必要がある。なお、ロケールの問題もあり、メッセージ・ラベルなど英語・日本語両方対応する必要がある為、同じく分岐処理が必要(OSではなく、OSのデフォルトロケールによって処理を振り分け)。
利用方法
WindowTester Pro User Guide(Eclipseのヘルプ画面)で習得できます。
記録機能の設定
[*]記録機能を利用する前に、設定が必要です、ユーザガイドに設定方法が紹介されていますが、本検証中に利用している設定を次の通りに例としてリストする。Record Configurations --- main
Record Configurations --- Arguments
Record Configurations --- Plugins
Record Configurations --- Configuration
CDEテスト時の注意点
[*]Recordする前に、CurlのRTEを終了させる必要がある、CDEのテストを記録する為のEclipseでCDEを起動しない、万が一起動されてしまったら、CurlのRTEを強制でも終了させてください。[*]記録時の環境は英語環境がお勧めです。テストコード・テスト条件・テストケースの作成時に、よく利用するメッセージの特定は英語で確認しやすいためだ。(日本語環境の場合、UIのメッセージ・テキストが日本語になっている為、propritiesファイルが特定のツールを使わないと読めない。 例: ファイルeclipse\com.curl.eclipse.plugin\src\com\curl\eclipse\ui\CurlUIMessages_ja.properties記録問題
Curl側の記録ができない
JavaのUIしかレコードできない為、VLEやVLEE、ほかのCurlでできたツールのレコードができない。解決策:なし
Drag&Dropが正しく記録できないケースがある
(手動で追加できるか、確認する必要がある)全く記録されないか、部分的に記録される、再生に利用できないコードになっている。解決策:手動で記録コードを追加する。例:to be added.
記録漏れがある
記録中に、記録漏れが発生する場合がある。例:ダイアログの表示・非表示解決策:手動で記録コードを追加する。例:ui.wait(new ShellShowingCondition("Curlメディエーターの開始中")); ui.wait(new ShellDisposedCondition("Curlメディエーターの開始中"));
ご操作などによって余計な操作を記録される場合がある
例:Welcome画面の閉じる操作など。解決策:不要なオペレーションが記録された場合に、該当コードをコメントアウトする。
ロケールの問題
記録時に、ダイアログやUIのIDは記録した時の言語設定によって、日本語か英語になるが、再生の環境によって、言語設定が記録時と異なる場合がある。解決策:CDE pluginのパッケージをインポートし、CDE pluginのパッケージに定義されているロケール対応の文字列定数を利用して、対応可能。例:ui.wait(new
ShellShowingCondition(InstallHandlerMessages.InstallHandler_Starting_Mediator)); この行のコードにあるInstallHandler_Starting_Mediatorは記録時に、日本語環境の場合、ui.wait(new ShellShowingCondition("Curlメディエーターの開始中"));になるが、パッケージクラスcom.curl.eclipse.InstallHandlerMessagesをインポートすれば、定数を直接利用し、Eclipseのローカライズ機能で対応できる。
ShellShowingCondition(InstallHandlerMessages.InstallHandler_Starting_Mediator)); この行のコードにあるInstallHandler_Starting_Mediatorは記録時に、日本語環境の場合、ui.wait(new ShellShowingCondition("Curlメディエーターの開始中"));になるが、パッケージクラスcom.curl.eclipse.InstallHandlerMessagesをインポートすれば、定数を直接利用し、Eclipseのローカライズ機能で対応できる。
上記プロパティファイルで対応できるのが、Dialogや、ラベルのメッセージのみで、メイン画面のMenuの場合、plugin.xmlで定義されており、この方法で対応できません。代替案が必要と思われます(制限付きの対応になるなど)
記録コードが実行できない場合がある(文法エラー)
例:Curlのコード行がある行の横のルーラをダブルクリックする処理を記録したコードは次になっているが、文法エラーです。
AnnotationRulerColumn$5.class,
1, new SWTWidgetLocator(
1, new SWTWidgetLocator(
CompositeRuler$CompositeRulerCanvas.class)),
4, 143));
4, 143));
CompositeRulerCanvasがパッケージアクセス属性の為、定義されたパッケージ外からアクセスできない!(のに、記録された)
また、$5もなぜ記録コードに入っているか不明(インナークラスのようだが。。。)、CompositeRuler$CompositeRulerCanvasにある$もdot(.)にすべき。
対策:不明
検証条件の追加
Assertion
Inspectorの使用
Inspectorの使用
記録時に記録バーにあるassertion inspector tool(虫眼鏡アイコン)を使えば、記録すると同時に検証条件を追加できる。このツールを起動したら、検証対象のコントロールにマウスホバーをすると、該当対象の検証できる属性が表示されるので、検索条件を記録コードに追加することができる。
制限事項:
1. メニュー・メニューアイテムに対応されてない。
2. Editorの左側のルーラ(AnnotationRulerColumn)対応できない($5などが追加される)
3. CurlEditorのコンテンツの検証ができない(色、下線などのスタイル、選択、クイックメニュー、コード補完)
1. メニュー・メニューアイテムに対応されてない。
2. Editorの左側のルーラ(AnnotationRulerColumn)対応できない($5などが追加される)
3. CurlEditorのコンテンツの検証ができない(色、下線などのスタイル、選択、クイックメニュー、コード補完)
検証の詳細
検証条件
特定の対象のプロパティを条件に満たすか検証する。例:
ui.assertThat(new ShellShowingCondition("Input Dialog"));
WTは複数種類の条件を検証できる。
ui.assertThat(new ShellShowingCondition("Input Dialog"));
WTは複数種類の条件を検証できる。
普通のUI部品の属性検証(使用可否・テキスト・選択・可視性・フォーカス)
次の属性条件が用意されている。
部品のenabled検証
IsEnabledConditionを利用する。IsEnabledインターフェースを実装した LocatorはIsEnabledConditionを使って、enabled属性の検証ができる。
部品のテキスト検証
HasTextConditionを利用する。HasTextインターフェースを実装した LocatorはHasTextConditionを使って、部品に格納されるテキストの検証ができる。同様に、上記以外に次のような条件も用意されている:
IsSelected and IsSelectedCondition
IsVisible and IsVisibleCondition
HasFocus and HasFocusCondition
検証条件例:“Finished”ボタンが使用可能の判断条件:
ui.assertThat(new IsEnabledCondition(new ButtonLocator("Finished"),
true));
true));
某ウィザードダイアログのUIにエラーメッセージを含まれているかの判断条件:
ui.assertThat(new HasTextCondition(new WizardErrorMessageLocator(), "エラーメッセージ"));
ui.assertThat(new HasTextCondition(new WizardErrorMessageLocator(), "エラーメッセージ"));
または、条件に合わない場合検証エラーのメッセージ付きの判断条件は次のように書く:
ui.assertThat("Finish button should be enabled", new
IsEnabledCondition(new ButtonLocator("Finished"), true));
ui.assertThat("The error message is wrong", new
HasTextCondition(new WizardErrorMessageLocator(), "some wizard
message"));
§
実際に、実装された条件であれば、どれでも検証可能だ。
実際に、実装された条件であれば、どれでも検証可能だ。
検証用ファクトリ
便利にコードを書く為、一部のLocatorに条件を生成するファクトリが実装されている。上記の二つの例だと、次のようにも書ける:
ui.assertThat(new ButtonLocator("Finished").isEnabled());
ui.assertThat(new WizardErrorMessageLocator().hasText("some wizard
message"));
ui.assertThat(new ButtonLocator("Finished").isEnabled());
ui.assertThat(new WizardErrorMessageLocator().hasText("some wizard
message"));
条件確保
一部の条件で、属性をtrueになっていることを確保(保証)するような書き方もある。それらの条件はcom.windowtester.runtime.condition.IConditionHandler
を実装していて、IUIContext.ensureThat(..)
に渡している。
を実装していて、IUIContext.ensureThat(..)
に渡している。
Welcomeページをクローズされていることを確保する実装例:
public class BaseTest extends UITestCaseSWT {
protected void setUp() throws Exception
{
{
closeWelcomePageIfNecessary();
}
}
protected void closeWelcomePageIfNecessary() throws Exception {
IWidgetLocator[]
welcomeTab = getUI().findAll(new CTabItemLocator("Welcome"));
welcomeTab = getUI().findAll(new CTabItemLocator("Welcome"));
if (welcomeTab.length == 0)
return;
getUI().close(welcomeTab[0]);
}
手動で条件検証を作成する
既存の条件(ICondition)で検証目的に達成できない場合、独自に実装することも可能。ただし、条件を検証する処理は、テストスレッドで実装してはいけなくて、UIスレッドで実装する必要がある。テストスレッド側で、検証処理をRunnableを経由して呼び出す必要がある。
実装例:
ツリーアイテムをドラッグして、別のツリーアイテムにドロップし、一つ目のラベルが期待したラベルになる検証。
//select a tree item
ui.click(new TreeItemLocator("treeItem2"));
//drag and drop it on another
ui.dragTo(new XYLocator(new
TreeItemLocator("treeItem2"), 5, 5));
TreeItemLocator("treeItem2"), 5, 5));
//perform assertions safely on the UI thread
Display.getDefault().syncExec( new Runnable() {
public void run() {
//get
the tree widget
the tree widget
Tree
tree = (Tree)((IWidgetReference)ui.find(new
SWTWidgetLocator(Tree.class))).getWidget();
tree = (Tree)((IWidgetReference)ui.find(new
SWTWidgetLocator(Tree.class))).getWidget();
//the
first item in the tree should now have the EXPECTED label
first item in the tree should now have the EXPECTED label
TestCase.assertEquals(EXPECTED_LABEL,
tree.getItems()[0].getText());
tree.getItems()[0].getText());
}
});
上記の条件検証方法があるが、テストケースのメンテナンス性や、実行時の健全性などを考えると、IConditionで実装し、WTの検証条件の仕組みを利用すべき。次の節も参照してください。
条件と条件ハンドラー
条件
条件は特定の状態が存在しているかをチェックする為のオブジェクト、ハンドラーは条件がtrueになる場合に起こすべき処理をカプセル化する。条件はテストの実行とテストされているアプリケーションの実行を同調させることができる、あるいはテストで特定のシチュエーションを検証する。
例えば、UIスレッドのある処理を待つ為に、Thread.sleep(5000);処理より、条件を使えば、テストスレッドとアプリケーションスレッドの同調の信頼性が高くなる。
ui.wait(new ShellShowingCondition("MyDialog"));
ui.wait(new ShellDisposedCondition("My
Dialog"));
Dialog"));
ui.wait(new IsEnabledCondition(new
ButtonLocator("Finish")));
ButtonLocator("Finish")));
ui.wait(new FileExistsCondition("MyProject/newFile.txt")),
true);
true);
あるいはSwingの場合
ui.wait(new WindowShowingCondition("My
Dialog"));
Dialog"));
ui.wait(new WindowDisposedCondition("My
Dialog"));
Dialog"));
ui.wait(new IsEnabledCondition(new
JButtonLocator("Finish")));
JButtonLocator("Finish")));
ほかのICondition, IUIConditionの対応状況は次のAPI Refを参考してください:
com.windowtester.runtime.condition
com.windowtester.runtime.swing.condition
com.windowtester.runtime.swt.condition
com.windowtester.runtime.swt.condition.eclipse
com.windowtester.runtime.swt.condition.shell
com.windowtester.runtime.swing.condition
com.windowtester.runtime.condition
com.windowtester.runtime.swing.condition
com.windowtester.runtime.swt.condition
com.windowtester.runtime.swt.condition.eclipse
com.windowtester.runtime.swt.condition.shell
com.windowtester.runtime.swing.condition
上記以外に、Condition ファクトリを利用することも可能。例
ui.assertThat(new ButtonLocator("OK").isEnabled());
ui.assertThat(new ButtonLocator("OK").isEnabled());
ハンドラー
条件/ハンドラーはペアでIConditionMonitorまたはIShellMonitorに登録され、予想できないイベントへの対処を頑丈にできる。次のサンプルはテストの過程で稀にライセンスダイアログが表示されるケースがあってもテストが正常に遂行できるようにライセンスダイアログが表示されたら"OK"ボタンをクリックさせる為の条件/Handlerを作成し、モニターに登録する。
monitor = (IConditionMonitor)
ui.getAdapter(IConditionMonitor.class);
ui.getAdapter(IConditionMonitor.class);
monitor.add(new
ShellShowingCondition("License*"), new IHandler() {
ShellShowingCondition("License*"), new IHandler() {
public handle(IUIContext ui) {
ui.click(new ButtonLocator("OK"));
}
});
条件は常にUIのアクションより先に処理される//conditions processed before menu selection
ui.click(new MenuItemLocator("New/File"));
//conditions processed during wait
ui.wait(New ShellShowingCondition("New File"));
//conditions processed before click
ui.click(new ButtonLocator("Cancel"));
//conditions processed during wait
ui.wait(new ShellDisposedCondition("New File"));
Swing Conditionの場合、CDEで利用しない為、省略。
再生時の問題
JavaのUIでも、再生する時に、停止されている場合がある
対策:ユーザが止まっている個所を確認し、手動でその操作を追加し、その後の再生が継続される。たとえば、New Curl
Projectの選択、再生されない場合がある。それは再生時に、Mediatorの起動に時間がかかり、Curlのビューが選択された後に、CurlのメニューがまだEclipseに作成されてない為、次のステップはCurlの新規Projectを作成する処理で失敗する(該当メニューが見つからない)為だ。
Projectの選択、再生されない場合がある。それは再生時に、Mediatorの起動に時間がかかり、Curlのビューが選択された後に、CurlのメニューがまだEclipseに作成されてない為、次のステップはCurlの新規Projectを作成する処理で失敗する(該当メニューが見つからない)為だ。
この問題の対応は、ui.waitの追加が必要。つまりMediatorにつなぐダイアログの表示・非表示を待たせて、このダイアログが消えたら、次のステップに進むようにテストの記録コードに処理を追加する必要がある。
例
ui.wait(new ShellShowingCondition(
InstallHandlerMessages.InstallHandler_Starting_Mediator));//"Curlメディエーターの開始中"
ui.wait(new ShellDisposedCondition(
InstallHandlerMessages.InstallHandler_Starting_Mediator));//"Curlメディエーターの開始中"
記録した入力が記録時と異なるコードが再生されてしまう
例えば、CurlEditorに手動で入力したコード
{define-proc public {test-code
in:String}:void
in:String}:void
が、再生する時に、次のコードで入力されてしまう:
{define-proc public {test-codein*String}*void
対策:不明
[*]テスト結果[*]テストが失敗する場合に、失敗した時のscreen shotsが自動的に作成される。[*]その他メモ
Welcomeページのテスト
[*]デフォルトはEclipseのWelcome画面がテストされません、テストする必要になる場合、次のように、セットアップメソッドのカスタマイズなどが必要です。例:
public class BaseTest extends UITestCaseSWT {
@Override
protected
void setUp() throws Exception {
void setUp() throws Exception {
closeWelcomePageIfNecessary();
}
}
//Handling the “Welcome” page involves the following
steps:
steps:
//Test if it is visible
//If it is visible, dismiss it
//The closeWelcomePageIfNecessary() method does just
that:
that:
protected void closeWelcomePageIfNecessary() throws
WidgetSearchException {
WidgetSearchException {
IUIContext
ui = getUI();
ui = getUI();
IWidgetLocator[]
welcomeTab = ui.findAll(new CTabItemLocator("Welcome"));
welcomeTab = ui.findAll(new CTabItemLocator("Welcome"));
if
(welcomeTab.length == 0)
(welcomeTab.length == 0)
return;
ui.close(welcomeTab[0]);
}
テストケースで保存されてないEditorバッファがある場合の対応。未保存のEditorバッファがある場合、テストが終了できない、または終了に時間がかかる(UIの生成がある為)。それを回避するために、コードでバッファを保存するような処理を書く必要がある。
例:(SWTテストの例、Pluginテストは同様だと思われる)
public class DirtyEditorCondition implements ICondition
{
{
public boolean test() {
final boolean result[] = new boolean[] { false };
Display.getDefault().syncExec(new Runnable() {
public void run() {
result[0] = anyUnsavedChanges0();
}
});
return result[0];
}
protected static boolean anyUnsavedChanges0() {
IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows();
for (int i = 0; i < windows.length; i++) {
IWorkbenchPage[] pages = windows.getPages();
for
(int j = 0; j < pages.length; j++) {
(int j = 0; j < pages.length; j++) {
IEditorReference[]
editorRefs = pages[j].getEditorReferences();
editorRefs = pages[j].getEditorReferences();
for
(int k = 0; k < editorRefs.length; k++) {
(int k = 0; k < editorRefs.length; k++) {
IEditorReference
each = editorRefs[k];
each = editorRefs[k];
if
(each.isDirty())
(each.isDirty())
return
true;
true;
}
}
}
return
false;
false;
}
}
//(The details are unsurprising. The one thing to note
is that we need to ensure that
is that we need to ensure that
//we safely access the workbench windows on the UI
thread.)
thread.)
//With our DirtyEditorCondition in hand, we can hook
into teardown like this:
into teardown like this:
public class BaseTest extends UITestCaseSWT {
... same
as above
as above
@Override
protected
void tearDown() throws Exception {
void tearDown() throws Exception {
saveAllIfNecessary();
}
protected
void saveAllIfNecessary() throws WidgetSearchException {
void saveAllIfNecessary() throws WidgetSearchException {
if
(anyUnsavedChanges())
(anyUnsavedChanges())
getUI().click(new
MenuItemLocator("File/Save All"));
MenuItemLocator("File/Save All"));
}
private
boolean anyUnsavedChanges() {
boolean anyUnsavedChanges() {
return
new DirtyEditorCondition().test();
new DirtyEditorCondition().test();
}
}
UI部品の特定方法
MatcherとLocator
WindowTesterでMatcherとLocatorでUI部品を特定する。
LocatorはMatcherの子供
public interface IWidgetMatcher {
boolean
matches(Object widget);
matches(Object widget);
}
//Widget locators are matchers.
public interface IWidgetLocator extends IWidgetMatcher { ... }
UI部品の検索範囲
部品の検索はWindowTester の実行環境がwidget finderに代理され、アクティブになっているアプリケーション画面のみ、検索対象にする。テストされているアプリが画面(Window)を持ってない場合、例外がfinderにスローされる。検索は外から中へ、上から下へ、左から上への順で探す、次のイメージ:(button-> label -> text -> the second composite -> the last text widget)
UIの階層構造の調査
SWTDebugHelperでUIの階層構造を標準コンソールに出力することが可能。
例: EclipseのAboutダイアログのUI階層をコンソールに出力するサンプル
public void testSpelunkAboutWindow() throws Exception {
IUIContext
ui = getUI();
ui = getUI();
ui.click(new
MenuItemLocator("Help/About Eclipse SDK"));
MenuItemLocator("Help/About Eclipse SDK"));
ui.wait(new
ShellShowingCondition("About Eclipse SDK"));
ShellShowingCondition("About Eclipse SDK"));
new
DebugHelper().printWidgets();
DebugHelper().printWidgets();
ui.click(new
ButtonLocator("OK"));
ButtonLocator("OK"));
ui.wait(new
ShellDisposedCondition("About Eclipse SDK"));
ShellDisposedCondition("About Eclipse SDK"));
}
部品の特定
§
部品を特定する簡易的な方法は2つある:
部品を特定する簡易的な方法は2つある:
IUIContext.find(..) または IUIContext.findAll(..))
IUIContext.find(ILocator)
一つ以上の部品を条件に合った場合、
MultipleWidgetsFoundException がスローされる。注意:IUIContext.click(ILocator) がIUIContext.find(ILocator)に代表されるのでILocatorが一つ以上存在する場合、同じようにMultipleWidgetsFoundException
がスローされる。
MultipleWidgetsFoundException がスローされる。注意:IUIContext.click(ILocator) がIUIContext.find(ILocator)に代表されるのでILocatorが一つ以上存在する場合、同じようにMultipleWidgetsFoundException
がスローされる。
IUIContext.findAll(ILocator)
マッチしたすべての部品がアレイにセットされ返却される。
幾つか例を見よう。
例えば、“OK”ボタンを探すために、ui.find(new
ButtonLocator("OK"));を使えばいい(もちろん、見つからない、または二つ以上見つかった場合に、例外がスローされる)。
ButtonLocator("OK"));を使えばいい(もちろん、見つからない、または二つ以上見つかった場合に、例外がスローされる)。
対照的にui.findAll(new ButtonLocator("OK"));を使うと、見つかった“OK”ボタンがすべてアレイにセットされる(空かもしれないが)
一般的に、“OK”ボタンが同じ画面上に一個しかないのが普通ですが、複数ある場合に、取得したいボタンの場所、UIの階層情報を利用して、特定できる。
例:new ButtonLocator("OK", new
SWTWidgetLocator(Composite.class)) このコードは、Compositeの中に入っているボタンを取得する(ほかの“OK”ボタンがこのCompositeに入ってない場合)。
SWTWidgetLocator(Composite.class)) このコードは、Compositeの中に入っているボタンを取得する(ほかの“OK”ボタンがこのCompositeに入ってない場合)。
§
確実に部品を特定する方法は部品にタグを付けさせることで、テストケースの開発段階で該当UIにユニークな名前を付けると特定しやすくなる。例:widget.setData("name", "widgetName");
確実に部品を特定する方法は部品にタグを付けさせることで、テストケースの開発段階で該当UIにユニークな名前を付けると特定しやすくなる。例:widget.setData("name", "widgetName");
テストケースで呼び出す時に次のように利用できる:
NamedWidgetLocator widgetLocator = new
NamedWidgetLocator("widgetName");
NamedWidgetLocator("widgetName");
IWidgetReference widgetRef = (IWidgetReference)ui.find(widgetLocator);
assertNotNull(widgetRef);
ui.click(widgetLocator); //if you want to click the
widget
widget
[*]対象外部品の特定
既存Locatorの動作を変えたい場合や、対象外の部品を特定する為に、Locatorをカスタマイズする必要がある。
§
動作を変えたい場合、IWidgetMatcherを継承し実装する。SWTの場合、SWTWidgetLocator.buildMatcher() で該当Matcherを呼び出す。
動作を変えたい場合、IWidgetMatcherを継承し実装する。SWTの場合、SWTWidgetLocator.buildMatcher() で該当Matcherを呼び出す。
例(選択されたボタンを特定する)
class SelectedButtonMatcher implements IWidgetMatcher {
private
final IWidgetMatcher defaultMatcher;
final IWidgetMatcher defaultMatcher;
SelectedButtonMatcher(IWidgetMatcher
defaultMatcher) {
defaultMatcher) {
this.defaultMatcher
= defaultMatcher;
= defaultMatcher;
}
public
boolean matches(Object widget) {
boolean matches(Object widget) {
return
isSelected(widget) && matchesDefaultCriteria(widget);
isSelected(widget) && matchesDefaultCriteria(widget);
}
private
boolean matchesDefaultCriteria(Object widget) {
boolean matchesDefaultCriteria(Object widget) {
return
defaultMatcher.matches(widget);
defaultMatcher.matches(widget);
}
private
boolean isSelected(Object widget) {
boolean isSelected(Object widget) {
if
(!(widget instanceof Button))
(!(widget instanceof Button))
return
false;
false;
final
Button button = (Button)widget;
Button button = (Button)widget;
final
boolean[] result = new boolean[1];
boolean[] result = new boolean[1];
Display.getDefault().syncExec(
new
Runnable() {
Runnable() {
public
void run() {
void run() {
result[0]
= button.getSelection();
= button.getSelection();
}
});
return
result[0];
result[0];
}
}
//(Note that here we are decorating the default
matcher. A simpler implementation would
matcher. A simpler implementation would
//forego a reference to the default matcher.)
//With this matcher defined a SelectedButtonLocator
could be defined like this:
could be defined like this:
public class SelectedButtonLocator extends
ButtonLocator {
ButtonLocator {
private static final long
serialVersionUID = 1L;
serialVersionUID = 1L;
public
SelectedButtonLocator(String label) {
SelectedButtonLocator(String label) {
super(label);
}
@Override
protected
IWidgetMatcher buildMatcher() {
IWidgetMatcher buildMatcher() {
IWidgetMatcher
defaultMatcher = super.buildMatcher();
defaultMatcher = super.buildMatcher();
return
new SelectedButtonMatcher(defaultMatcher);
new SelectedButtonMatcher(defaultMatcher);
}
}
//Using this locator looks just like using the standard
button locator:
button locator:
ui.click(new ButtonLocator("OK")); //standard
ui.click(new SelectedButtonLocator(".*"));
//custom
//custom
//(Notice that you can use a wildcard just like the
standard locator since we are
standard locator since we are
// decorating the default matching criteria. The custom
locator reference reads:
locator reference reads:
// "click the selected button with any
text".)
text".)
対象外部品の対応
対象外の部品を対応する為に、新しいLocatorを作成する必要がある。作成方法について、一番単純なものはSWTWidgetLocator を継承するだけで対応可能。例えば、次の例で、EclipseのSection(org.eclipse.ui.forms.widgets.Section)部品の検出に役立つLocatorが作成されている。
public class SectionLocator extends SWTWidgetLocator {
private
static final long serialVersionUID = 621335057837701982L;
static final long serialVersionUID = 621335057837701982L;
public
SectionLocator(String text) {
SectionLocator(String text) {
super(Section.class,
text);
text);
}
public
SectionLocator(String text, SWTWidgetLocator parent) {
SectionLocator(String text, SWTWidgetLocator parent) {
super(Section.class,
text, parent);
text, parent);
}
public
SectionLocator(String text, int index, SWTWidgetLocator parent) {
SectionLocator(String text, int index, SWTWidgetLocator parent) {
super(Section.class,
text, index, parent);
text, index, parent);
}
}
上記の実装は、SWTWidgetLocatorのデフォルト機能を利用している。ただ、より複雑なロジックについて、例えばツリーの項目選択の制御など、まだドキュメントされてない。【2011/10/25現在】
理解を深める為に、より複雑な対象外部品
org.eclipse.swt.browser.Browser の対応例もWTのヘルプに載っている。部品BrowserにはHTMLのコンテンツが入っており、このサンプルの実装で、次のようなテストケースが作成できる:
ui.assertThat(new BrowserLocator().htmlContains(EXPECTED_URL));
org.eclipse.swt.browser.Browser の対応例もWTのヘルプに載っている。部品BrowserにはHTMLのコンテンツが入っており、このサンプルの実装で、次のようなテストケースが作成できる:
ui.assertThat(new BrowserLocator().htmlContains(EXPECTED_URL));
本対応のコツは、Browserインスタンスの為にIWidgetReference をカスタマイズして、特殊なBrowserReferenceを作成する。
また、ほかのサンプルについて、次のリンクを参照してみてください:http://code.google.com/p/wt-commons/wiki/WTSnippets
BrowserLocatorの実装例
また、ほかのサンプルについて、次のリンクを参照してみてください:http://code.google.com/p/wt-commons/wiki/WTSnippets
BrowserLocatorの実装例
public class BrowserLocator extends SWTWidgetLocator {
private
static final long serialVersionUID = 1L;
static final long serialVersionUID = 1L;
public
BrowserLocator() {
BrowserLocator() {
super(Browser.class);
}
@Override
public
IWidgetLocator[] findAll(IUIContext ui) {
IWidgetLocator[] findAll(IUIContext ui) {
IWidgetLocator[]
refs = super.findAll(ui);
refs = super.findAll(ui);
BrowserReference[]
browsers = new BrowserReference[refs.length];
browsers = new BrowserReference[refs.length];
for
(int i = 0; i < browsers.length; i++) {
(int i = 0; i < browsers.length; i++) {
browsers
= new BrowserReference((IWidgetReference)refs);
= new BrowserReference((IWidgetReference)refs);
}
return
browsers;
browsers;
}
}
BrowerReferenceの実装例:
public class BrowserReference implements
IWidgetReference {
IWidgetReference {
private class
HtmlContainsCondition implements ICondition {
HtmlContainsCondition implements ICondition {
String
expectedText;
expectedText;
public
HtmlContainsCondition(String expectedText) {
HtmlContainsCondition(String expectedText) {
this.expectedText = expectedText;
}
public
boolean test() {
boolean test() {
String html = getHTML();
if (html == null)
return false;
return html.contains(expectedText);
}
}
private final
IWidgetReference browserWidget;
IWidgetReference browserWidget;
public
BrowserReference(IWidgetReference browserWidget) {
BrowserReference(IWidgetReference browserWidget) {
this.browserWidget = browserWidget;
}
public Object
getWidget() {
getWidget() {
return
browserWidget.getWidget();
browserWidget.getWidget();
}
/**
* @since
Eclipse 3.4 where Browser.getText() is introduced
Eclipse 3.4 where Browser.getText() is introduced
*/
public String
getHTML() {
getHTML() {
final
String text[] = new String[1];
String text[] = new String[1];
Display.getDefault().syncExec(new Runnable() {
public void run() {
text[0] = getText(getBrowser());
}
});
return
text[0];
text[0];
}
private
static String getText(Browser browser) {
static String getText(Browser browser) {
if
(browser == null)
(browser == null)
return null;
try {
Method m = browser.getClass().getMethod("getText", (Class[])
null);
null);
m.setAccessible(true);
return (String) m.invoke(browser, (Object[]) null);
} catch
(SecurityException e) {
(SecurityException e) {
} catch
(NoSuchMethodException e) {
(NoSuchMethodException e) {
} catch
(IllegalArgumentException e) {
(IllegalArgumentException e) {
} catch
(IllegalAccessException e) {
(IllegalAccessException e) {
} catch
(InvocationTargetException e) {
(InvocationTargetException e) {
}
return
null;
null;
}
public void
execute(final String script) {
execute(final String script) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
getBrowser().execute(script);
}
});
}
public void
setURL(final String url) {
setURL(final String url) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
getBrowser().setUrl(url);
}
});
}
public
ICondition htmlContains(String expectedText) {
ICondition htmlContains(String expectedText) {
return
new HtmlContainsCondition(expectedText);
new HtmlContainsCondition(expectedText);
}
public
Browser getBrowser() {
Browser getBrowser() {
return
(Browser)getWidget();
(Browser)getWidget();
}
/*
(non-Javadoc)
(non-Javadoc)
* @see
com.windowtester.runtime.locator.IWidgetLocator#findAll
com.windowtester.runtime.locator.IWidgetLocator#findAll
* (com.windowtester.runtime.IUIContext)
*/
public
IWidgetLocator[] findAll(IUIContext ui) {
IWidgetLocator[] findAll(IUIContext ui) {
return
browserWidget.findAll(ui);
browserWidget.findAll(ui);
}
/*
(non-Javadoc)
(non-Javadoc)
* @see
com.windowtester.runtime.locator.IWidgetMatcher#matches
com.windowtester.runtime.locator.IWidgetMatcher#matches
* (java.lang.Object)
*/
public boolean matches(Object widget) {
return browserWidget.matches(widget);
}
}
RCP/SWTに組み込まれたSwing 部品のテストについて
Eclipse上のWTのヘルプを参照してください("Embedded Swing Components"で検索)。
GEF
(Graphical Editing Framework)について
(Graphical Editing Framework)について
GEFはCDEに利用されてない為、GEF関係のガイドを省略する。
ほかに重要な参照情報
API Ref: WindowTester Pro User
Guide > Reference
Guide > Reference