レベル: 中級 大川 昌弘 (mohkawa@jp.ibm.com), IM&Lotus開発, ソフトウェア開発研究所, IBM 中嶋 剛 (gnaka@jp.ibm.com), IM&Lotus開発, ソフトウェア開発研究所, IBM
2009年 3月 27日 IBM Rational® Functional Tester (RFT)は、テストを自動化するためのツールで、さまざまな機能を提供します。IBMのQSE(Quality Software Engineering)チームは、メンテナンス性を考慮し、RFTによるテスト自動化手法として、QSE Frameworkと呼ばれる階層フレームワークを提唱しています。この記事では、メンテナンス性と生産性を考慮し、QSE FrameworkをベースにRFTの機能を効率的に利用する手法を紹介します。また、複数言語環境でのテスト方法やその考慮点についても紹介します。
はじめに
IBM Rational® Functional Tester (RFT)は、テストを自動化するための機能を提供しています。レコーダー機能を使用するとテスト内容を記録することができ、その後、記録されたスクリプトを実行することで、そのテストを自動的に実施することができます。データを入力する場面では、レコーディング中にテスト・データプール機能を利用することで、レコーディング終了後に入力する値を変えて記録されたスクリプトを実行することができます。レコーディング中に検査ポイント機能を使用することで期待値と実際の値を比較することができます。スクリプト実行後、各検査ポイントの比較結果が一覧表示されるので、テストに成功したかどうかが一目瞭然です。
しかし、レコーダー機能を使用して作成したテスト自動化スクリプトは、テスト対象アプリケーションの画面部品(オブジェクト)の情報とスクリプトが密接に結びついているため、テスト対象アプリケーションの修正やバージョンアップなどにより、再度レコーディングが必要となる場合があります。
そこで、IBMのQSE(Quality Software Engineering)チームは、メンテナンス性向上のためのフレームワーク(QSE Framework)を考案しました。QSE Frameworkでは、GUIのオブジェクトとテストケースを分離することで、アプリケーション変更時のスクリプトへの影響を抑えます。QSE Frameworkでは、IBM Rational Functional Testerが提供するオブジェクト・マップ機能を使用して、GUIのオブジェクトを取得します。またGUIに対するアクションは、取得したオブジェクトが保持するメソッドを使用します。QSE Frameworkは、以下の3層から構成されています。
- “appobjects” フォルダーには、オブジェクト・マップ機能を利用して得られたGUIのオブジェクトが定義されたクラスを保持します。
- “tasks” フォルダーは、“appobjects” のメソッドからGUIのオブジェクトを取得し、GUIに対する操作を行うクラスを保持します。各テストケースが行うGUIの操作を、内容で分類し、タスクとして実装してゆきます。例えば、ログインする処理を1つのタスクとして実装します。
- “testcases” フォルダーは、“tasks” の機能を使用してテストを実行し、結果をログするテストケースを保持します。
QSE Frameworkの考え方を適用することで、レコーダー機能を使用した場合と比べ、メンテナンス性の向上が図れます。
この記事では、メンテナンス性と生産性を考慮し、QSE FrameworkをベースにRFTの機能を効率的に利用する手法を紹介します。また、複数言語環境でのテスト方法やその考慮点についても紹介します。
GUIの操作メソッドの提供
例えば、図1のGUIを持つアプリケーションのテストを行うとします。
図1. テスト対象アプリケーション画面
図1のテスト対象アプリケーションでは図2のように、“Actions”メニューのサブメニューとして“Create New Set…”などがあります。これらのメニューに対するオブジェクトはオブジェクト・マップで取得することができます。
図2. テスト対象アプリケーションのメニュー
オブジェクト・マップで取得したオブジェクトは、図3のようなメソッドで取得することができます。
図3. オブジェクト・マップとオブジェクトを得るためのプログラム
“Actions”をクリックし、続けて”Create New Set…”をクリックする処理は以下のようになります。
MainGUI mainGui = new MainGUI();
mainGui.getActions().click();
mainGui.getActionsCreateNewSet().click();
|
このように、GUIのオブジェクトを使用して簡単にGUIの操作を行うことができます。
しかしながら、GUIを操作するアクションによっては、GUIのオブジェクトのどのメソッドを使用して実現するか不明なケースもあります。例えば、図4のようにツリーのノードを右クリックすることでメニューを表示させる処理を考えてみます。
図4. ツリーノードのプルダウンメニュー
ツリーの場合、各ノードをGUIのオブジェクトとして取得することはできません。図5のようなツリー全体のオブジェクトのメソッドを使用して実現します。
図5. ツリーのオブジェクトのキャプチャー
このようにどのメソッドを使用して行うか不明な場合は、レコーダーを使用して操作を行い、その結果を参考にします。レコーダーを使用してツリーの該当ノードを右クリックすると、以下のコードが得られます。
public void testMain(Object[] args)
{
// Frame: Database Relationship Analyzer
grdTree().click(RIGHT, atPath("SAMPLE->ORDER->TRACE_ANALYSIS"));
}
|
このようなプログラムを、“tasks” のプログラムで必要な都度書いてゆくのは効率的ではありません。そこで、新たに“apputils” フォルダーを設け、“appobjects” のサブクラスとして、上記を実現するための汎用的なメソッドを定義します。これはFunctional Testスクリプトとして作成する必要はありません。Javaのクラスとして作成すれば十分です。上記を実現するための汎用的なメソッドは以下のようになります。(ツリーのオブジェクトを取得するgrdTree()メソッドは、親クラスで定義したものに置き換えます。)
public class MainGUIUtil extends MainGUI {
public void rightClickView(String dbName, String setName, String viewName) {
String s = dbName + "->" + setName + "->" + viewName;
getGrdTree().click(RIGHT, atPath(s));
}
|
“tasks” のプログラムは、上記のようなメソッドを使用して、直感的にGUIの操作を記述してゆくことができます。
GUIのデータ入力操作
QSE Frameworkにより、GUIのデータ入力を行う場合の手順は、以下のようになります。
- オブジェクト・マップで、各入力フィールドのオブジェクトを取得する。
- それぞれのオブジェクトを取得するためのメソッドを定義する。
- それぞれのメソッドを使用して、入力値をセットする。
Wizardのようなデータを入力する操作は、テスト・データプールを利用することで、効率的に行うことができます。図6は、1画面からなる処理実行前の入力画面の例です。この入力画面に値をセットする処理は、以下のように自動化することができます。
図6. テスト対象アプリケーションの入力画面
この画面を開いた後に、レコーダーを開始し、データ駆動型コマンドの挿入により、このGUI全体をキャプチャーします。その後、一時停止し、OKボタンを有効化するためにフィールドにダミーデータを挿入した後、レコーダーを再開し、OKボタンを押し、レコーダーを停止すると、図7の結果が得られます。複数画面からなるWizardの場合、画面を切り替える毎に、データ駆動型コマンドの挿入を行うことで、それぞれの画面の入力データをパラメータ化することができます。
図7. テスト・データプールの使用により自動生成されたプログラム
このレコーディングで得られたスクリプトをメソッド化し、そのメソッドをコールすることで、テスト・データプールに入力された値がセットされます。図7に示されているように、dpString()メソッドでテスト・データプールの値を取得しています。
この代わりに、入力値をメソッドの引数で渡すようにすることで、汎用的に使用することができます。この例において、開始時間と終了時間についてはテスト・データプールの値を利用し、その他のすべての値を汎用的にセットする場合、以下のようなメソッドを定義します。
public void runTraceAnalyzer(String schema,
String table,
String startDate,
String endDate)
{
schemaName().setText(schema);
tableName().setText(table);
startDate().setText(startDate);
startTime().setText(dpString("StartTime"));
endDate().setText(endDate);
endTime().setText(dpString("EndTime"));
ok().click();
}
|
検査ポイントの利用
静的なデータの検査手法
GUI上に処理結果が表示される場合、レコーダーを開始し、検査ポイントを取得すると簡単に期待値と実際の値を比較することができます。
レコーダーを使用して図8の表を検査ポイントとして取得すると、図9のスクリプトが生成されます。
図8. アプリケーションの処理結果として得られる表
図9. 検査ポイントの使用により自動生成されたプログラム
これを以下のようにメソッド化し、そのメソッドをコールすることで、登録されている期待値と実際の値を比較することができます。
public void verifyResult() {
table().performTest(ResultTableVP());
}
|
動的なデータの検査手法
動的なデータを検査したい場合、IFtVerificationPoint vpManual (java.lang.String vpName, java.lang.Object expected, java.lang.Object actual).performTest()メソッドが使用できます。このメソッドを使用することで、期待値を動的にセットし、実際の値と比較することができます。(文字列の場合は検査ポイントの期待値としてデータプールの値を参照することもできます。)vpManual()の第1引数にプロジェクトでユニークな検査ポイント名、第2引数に期待値のオブジェクト、第3引数に実際の値のオブジェクトを指定します。
この例のように表の比較を行う場合は、com.rational.test.ft.vp.impl.TestDataTableクラスオブジェクトを期待値と実際の値のオブジェクトとして使用します。
期待値のオブジェクトは以下のように作成することができます。比較するエリアを指定する必要があり、setComparisonRegions()メソッドを利用します。
TestDataTable tbl = new TestDataTable();
tbl.setColumnHeader(0, "Table Name");
…
tbl.insert(new String[7], 0);
tbl.setCell(0, 1, "CUSTINFO");
…
TestDataTableRegions regions = new TestDataTableRegions();
regions.addRegion(TestDataTableRegion.allCells());
tbl.setComparisonRegions(regions.getRegions());
|
ここでは、先ほどの例で作成した検査ポイントResultTableを取得した後、修正を加えて、実際の値と比較してみます。実際の値は、オブジェクト・マップで得られたtable()オブジェクトのgetTestData(String testDataType)で得られます。testDataTypeは、検査ポイントを開いて、検査ポイント・エディターに表示されるプロパティー “type” の値をセットします。この例では “visible contents” です。
public void verifyResult() {
TestDataTable tbl = (TestDataTable)ResultTableVP().getBaselineData();
tbl.setCell(0, 1, "CUSTHIST2");
vpManual("VP1", tbl, table().getTestData("visible contents")).performTest();
}
|
上記の記述では、“CUSTHIST”がセットされるところを、“CUSTHIST2”に変更しています。これを実行すると検査結果が失敗となり、詳細情報として図10が得られます。
図10. 検査ポイントによる検査結果の詳細情報(表の比較)
上記では表の比較を例に述べましたが、その他の比較可能なクラスとして以下が挙げられます。
| GUIの種類 | 値を保持するクラス |
|---|
| ツリー | com.rational.test.ft.vp.impl.TestDataTree |
|---|
| リスト | com.rational.test.ft.vp.impl.TestDataList |
|---|
| メニューバー | com.rational.test.ft.vp.impl.TestDataTree |
|---|
| 文字列 | String |
|---|
ツリーの場合において、キャプチャーした期待値に、1つのノードを追加するプログラムは以下のようになります。
public void verifyResult() {
TestDataTree tree = (TestDataTree)GRDTreeVP().getBaselineData();
TestDataTreeNodes nodes = (TestDataTreeNodes)tree.getTreeNodes();
TestDataTreeNode node = (TestDataTreeNode)nodes.getRootNodes()[0];
node = (TestDataTreeNode)node.getChild(0);
node = (TestDataTreeNode)node.getChild(0);
node = (TestDataTreeNode)node.getChild(0);
TestDataTreeNode node2 = new TestDataTreeNode();
node2.setNode("GROUP2");
TestDataTreeNode[] n = new TestDataTreeNode[2];
n[0] = (TestDataTreeNode)node.getChild(0);
n[1] = node2;
node.setChildren(n);
vpManual("TreeVP1", tree, grdTree().getTestData("tree")).performTest();
}
|
これを実行すると、図11が得られます。
図11. 検査ポイントによる検査結果の詳細情報(ツリーの比較)
リストの場合において、キャプチャーした期待値に、1つの要素を追加するプログラムは以下のようになります。
public void verifyResult() {
TestDataList list = (TestDataList)TestListVP().getBaselineData();
TestDataElementList elemList = (TestDataElementList)list.getData();
TestDataElement elem = new TestDataElement();
elem.setElement("list3");
elemList.add(elem);
vpManual("ListVP1", list, list().getTestData("list")).performTest();
}
|
これを実行すると、図12が得られます。
図12. 検査ポイントによる検査結果の詳細情報(リストの比較)
複数言語環境のテスト
複数言語環境のテストを行う場合、Javaのリソースバンドルで使用するリソースファイルのように、GUIに表示する文字列をキーと値([キー名]=[値])で管理するリソースファイルをRFTに組み込んで使用することができます。構成手順は以下の通りです。
- RFTのbinディレクトリにある ivory.properties ファイルに記述されている以下のプロパティーを有効化します。(デフォルトではコメントアウトされています。)
# When enabled this property allows string look up in a localized string table,
if available
rational.test.ft.services.enable_localization=true
|
- キーと値からなるリソースファイルを、基底名がRFTのプロジェクト名となるようにファイル名を変更して、RFTプロジェクトのresourcesフォルダー下にコピーします。あるいは、コピーせずにRFT用に作成しても良いです。リソースファイル名の書式は以下のようになります。
- [RFTプロジェクト名]_[言語].properties
例えば、RFTプロジェクト名が"DRAProject"の場合、日本語のリソースファイル名は以下のようになります。
- RFTを再起動します。
- RFTのテスト・オブジェクト・マップを開いて、使用しているすべてのGUIのプロパティーのうち、画面に表示される文字列を値として使用しているプロパティー値を、リソースファイルのキー名に変更します。該当するプロパティー名としては以下のようなものがあります。サブメニューの場合、親メニューの文字列と_で結合した値となっている箇所があります。その場合、その重みを0に変更します。
accessibleContext.accessibleName
.captionText
name
text
|
例えば、日本語のリソースファイルがまだ作成されていないアプリケーションの日本語環境をテストするために、英語のリソースファイルを日本語用にコピーして、すべての値に“ゼソ”を先頭に付加してみると、アプリケーション画面は図13のようになります。
図13. 日本語テスト用のアプリケーション画面
これらのリソースファイルをRFTで利用するために、RFTプロジェクトのresourcesフォルダー下にコピーして、RFTを再起動します。
その後、テスト・オブジェクト・マップで、各オブジェクトのプロパティーの値として、画面に表示される文字列がセットされている箇所を、リソースファイルに定義されているキー名に置き換えます。
図14. テスト・オブジェクト・マップを利用したリソースファイルのキー名の入力
置き換え後にエンターキーを押すと、図15のように実際の値が表示されます。内部的にはキー名で保持されており、再度ダブルクリックして編集モードにすると、キー名が表示されます。
リソースファイルのプロパティー値を変更した場合、それを反映するためには、RFTを再起動する必要があります。再起動後、テスト・オブジェクト・マップの該当プロパティー値を確認すると、変更後の値になります。
図15. リソースファイルのキー名から値への自動変換
図16のように、サブメニューの “name” プロパティーの値は、親メニューの値と_で結合されているので、その重みを0にします。
図16. GUIのオブジェクトを探す際の重み付けの修正
まとめ
この記事では、QSE Frameworkの基本的な考え方に則りつつ、GUIを操作するためのメソッドが不明な場合の対応方法や、RFTのテスト・データプールや検査ポイントの効果的な利用方法について述べました。GUIを操作するためのメソッドが不明な場合は、レコーダーの機能でシミュレーションします。その結果として生成されたプログラムを参考にし、サブクラスを定義し、GUIの操作が容易にプログラムできるように、理解しやすいメソッドを提供します。また、テスト・データプールや検査ポイントの利用により、効率的にデータ入力やテスト結果の検証を行うことができます。
さらに、RFTは複数言語環境のテストも考慮しており、この記事では、その利用方法や考慮点について述べました。RFTは、Javaのリソースバンドルで使用するリソースファイルのように、GUIに表示する文字列をキーと値で管理するリソースファイルを組み込んで利用することができ、複数言語環境のテストも容易に行うことができます。
参考文献
著者について  | |  | 大川 昌弘は、ソフトウェア開発研究所のIM&Lotus開発に所属するソフトウェアエンジニアで、現在は主にデータベースツール製品の開発やテストに従事しています。 |
 | |  | 中嶋 剛は、ソフトウェア開発研究所のIM&Lotus開発に所属するソフトウェアエンジニアで、現在は主にデータベースツール製品の開発やテストに従事しています。 |
記事の評価
|