アプリケーションのGUIの自動テストを作成する必要に迫られても、たいていはいくつかの選択肢から優れたテストパッケージを選択できるのが普通でしょう。もちろん、手元には多額の資金があり、Windowsで作業を行う場合の話ですが。Linux上で作業しており、ワシントンD.C.でロビー活動ができる企業レベルの予算を持ち合わせておらず、かつ、クローズドのプロプライエタリアーキテクチャに束縛されたくない場合は、こううまくはいきません。
そうした嘆かわしい状況は、ウェストフォード(米マサチューセッツ州)のRed Hatオフィスの技術者(Zack Cerza、Ed Rousseau、David Malcolm)と、彼らが始めたオープンソースの自動テストフレームワークのおかげで変わりつつあります。このフレームワークは、「Dogtail」と呼ばれています。この記事ではDogtailについて説明し、単純なDogtailテストスクリプトの作成と実行の例を検討します(また、初期のDogtailコードの開発については元Red Hat社員であるChris Lee氏、DogtailのグラフィックスとWebサイトの設計についてはRed Hat社員であるMairin DuffyとDiana Fongの貢献に感謝したいと思います)。
DogtailのWebサイトでは、次のように説明されています。Dogtail website(英語):
「...Dogtailは、Pythonで作成されたGUIテストツールであり、自動フレームワークです。Dogtailはアクセス可能性(A11Y1)テクノロジを利用してデスクトップアプリケーションと通信します。 DogtailのスクリプトはPythonで作成され、他のあらゆるPythonプログラムと同様に実行されます」
その小さなパラグラフをもう少し詳しく調べてみましょう。実はかなり深い意味があります。
あらゆるGUIテストフレームワークの主な機能の1つは、GUIの要素の識別です。GUI要素は、そのXおよびYマトリックス座標により特定し、識別することが可能ですが、このアプローチは、GUIレイアウトを変更しないという要件によって制約されます。より有効なアプローチは、GUI要素をテストスクリプトで操作できる別個のオブジェクトとして特定し、識別することです。Dogtailは、アプリケーションGUIのアクセス可能性情報のメタデータによって、この識別を実現しています。
Dogtailでは、Assistive Technology Service Provider Interface(AT-SPI1)アクセス可能性フレームワークを使用します。これは、Gnome Accessibility Project(GAP2)の一部です。AT-SPIは、GNOMEプラットフォームで利用可能なアシスティブテクノロジのService Provider Interface、およびアプリケーションをリンクできるライブラリを提供します。
Dogtailは、現在、GNOMEアプリケーションとその他の多数のGTK+アプリケーションをサポートしています。Dogtailでは、pyspi(DogtailがAT-SPIのC APIにアクセスできるようにするためのPyrexで書かれたモジュール)が使用されます。
Dogtailでは、現在、テストスクリプト開発用に手続き型APIとオブジェクト指向APIの2つのAPIがサポートされています。両方のAPIのHappyDoc APIマニュアルがDogtailのWebサイトで入手できます。
手続き型APIは、デスクトップアプリケーションの機能テスト用に設計されています。このAPIの設計目標は、Pythonについて基礎的経験しか持たないスクリプト作成者でも利用できるほどの単純性を維持しながら、一般的なデスクトップGUIの自動テストにも使用できる強力な機能と柔軟性を備えることでした。Pythonを初めて使用する場合や、GUIオブジェクトの微調整が要求されない環境で機能テストを実行する必要がある場合は、このAPIを使用します。調べる必要のある主なモジュールは、 dogtail.proceduralです。
オブジェクト指向APIは、GUIの「アクセス可能要素」とのやり取りを微調整でき、またサブクラス化によって容易にカスタマイズできるように設計されています。アプリケーション開発者は、テストとデバッグのために、このAPIを使用してアプリケーションを特定の状態に置くと便利な場合があります。デバッグ用の機能を容易に展開するためのアプリケーションラッパーモジュールを作成できます。このアプローチについて調べるために適したモジュールは、dogtail.treeです。
あわてる必要はありません。APIについて学ぶための最良の方法は、実際に使ってみることです。この記事の以下の項では、各APIで作成された短いテストスクリプトの例を検討します。
説明はもう十分です。上記のすべての情報を実際に使用して、テストスクリプトを作成し、実行しましょう。このプロセスには、5つのステップがあります。
まず、Dogtailに必要なパッケージがインストールされていることを確認します。:
Dogtailを使用するには、次のパッケージが必要です。:
次に、現行のDogtail rpmをインストールします。
Dogtailでアクセス可能性テクノロジの情報を利用できるようにするには、GNOMEデスクトップでアクセス可能性を有効にする必要があります。GNOMEデスクトップGUIでこれを行うには、メニュー:
アプリケーション->個人設定->アクセシビリティ->支援技術のサポート
にアクセスし、「支援技術を有効にする」チェックボックスを選択します。
さらに、対象となるアプリケーションがJavaで作成されている場合は、次の環境変数も設定する必要があります。
export GTK_MODULES="gail:atk-bridge"
対象となるアプリケーションをシェルプロンプトから起動する場合は、次のテキストが表示されます。
GTK Accessibility Module initialized
さて、面白くなってきました。テストスクリプトを作成してアプリケーションの動作を確認するには、まず、操作するGUI要素を識別する必要があります。サンプルスクリプトでは、geditテキストエディタのテストを作成して実行します。
アプリケーションGUIのアクセス可能性情報のメタデータを表示する例を確認しましょう。2つのアプローチをとることができます。
アプローチ#1:「sniff」ユーティリティ - Dogtailフレームワークには、sniffというスタンドアロンユーティリティが含まれています。このユーティリティは、デスクトップからアクセスできるあらゆるアプリケーションを検査します。アプリケーションが実際に実行中である場合、sniffはそのアクティブウィンドウを通じてアプリケーションにアクセスします。次に、geditテキストエディタについてsniffの出力例を示します。sniffがアプリケーションのすべてのGUI要素の名前を表示する方法に注意してください。sniffは、geditで編集されているファイルが変更された(しかし未保存の)状態であることも認識しています。
アプローチ#1:introspectionメソッドを直接コール - ogtailのintrospectionメソッドを直接呼び出すことによって、構造化された(非常に冗長な)フォーマットで同じ情報にアクセスできます。たとえば、geditに含まれるGUI要素をすべて表示するには、Pythonを起動し、次のステートメントを入力します。
>>>from dogtail.tree import root
>>>f = root.application('gedit')
>>>f.dump()
それから、スクロールして出力を確認します。前述のとおり、出力は冗長です。次に、その一部を示します。
Node roleName='menu item' name='New' description='' text='New' click Node roleName='menu item' name='Open...' description='' text='Open...' click Node roleName='menu item' name='Open Location...' description='' text='Open Location...' click Node roleName='separator' name='' description='' text='' click Node roleName='menu item' name='Save' description='' text='Save' click Node roleName='menu item' name='Save As...' description='' text='Save As...' click Node roleName='menu item' name='Revert' description='' text='Revert' click Node roleName='separator' name='' description='' text='' click Node roleName='menu item' name='Page Setup' description='' text='Page Setup' click Node roleName='menu item' name='Print Preview...' description='' text='Print Preview...' click
「click」とは、一体何を意味しているのでしょうか。これらは、GUI要素がサポートしているアクションです。これらは、テストスクリプトで実行するアクションです。これらについては、この記事のあとの方で詳しく説明します。
ここまでは問題ありません。さて、何かコードを書きましょう。記事のこの項では、Dogtail APIのそれぞれを使用したgedit GNOMEテキストエディタの単純なテストを検討します。このテストでは、単にファイルを読み取って保存したあと、そのファイルを問題のない既知の(「ゴールデン」)ファイルと比較します。DogtailのWebサイトからのダウンロードに利用できる2つのgeditテストスクリプトを検討します。
手続き型APIのサンプルスクリプトから始めましょう。コードは次のとおりです。
1 #!/usr/bin/env python
2 # Dogtail demo script using procedural API
3 # FIXME: Use TC.
4 __author__ = 'Zack Cerza <zcerza@redhat.com'
5
6 import dogtail.tc
7 from dogtail.procedural import *
8 from dogtail.utils import screenshot
9 from os import environ, path, remove
10
11 # Load our persistent Dogtail objects
12 TestString = dogtail.tc.TCString()
13
14 # Remove the output file, if it's still there from a previous run
15 if path.isfile(path.join(path.expandvars("$HOME"), "Desktop", "UTF8demo.txt")):
16 remove(path.join(path.expandvars("$HOME"), "Desktop", "UTF8demo.txt"))
17
18 # Start gedit.
19 run('gedit')
20
21 # Set focus on gedit
22 focus.application('gedit')
23
24 # Focus gedit's text buffer.
25 focus.text()
26
27 # Load the UTF-8 demo file. Use codecs.open() instead of open().
28 from codecs import open
29 from sys import path
30 utfdemo = open(path[0] + '/data/UTF-8-demo.txt')
31
32 # Load the UTF-8 demo file into the text buffer.
33 focus.widget.text = utfdemo.read()
34
35 # Click gedit's Save button.
36 click('Save')
37
38 # Focus gedit's Save As... dialog
39 focus.dialog('Save as...')
40
41 # click the Browse for other folders widget
42 activate('Browse for other folders')
43
44 # Click the Desktop widget
45 activate('Desktop', roleName = 'table cell')
46
47 # We want to save to the file name 'UTF8demo.txt'.
48 focus.text()
49 focus.widget.text = 'UTF8demo.txt'
50
51 # Click the Save button.
52 click('Save')
53
54 # Let's quit now.
55 click('Quit')
56
57 # We have driven gedit now lets check to see if the saved file is the same as
58 # the baseline file
59
60 # Read in the "gold" file
61 import codecs
62 try:
63 # When reading the file, we have to make sure and tell codecs.open() which
64 # encoding we're using, otherwise python gets confused later.
65 gold = open(path[0] + '/data/UTF-8-demo.txt', encoding='utf-8').readlines()
66 except IOError:
67 print "File open failed"
68
69 # Read the test file for comparison
70 filepath = environ['HOME'] + '/Desktop/UTF8demo.txt'
71 # When reading the file, we have to make sure and tell codecs.open() which
72 # encoding we're using, otherwise python gets confused later.
73 testfile = open(filepath, encoding='utf-8').readlines()
74
75 # We now have the original and saved files as lists. Let's compare them line
76 # by line to see if they are the same
77 i = 0
78 for baseline in gold:
79 label = "line test " + str(i + 1)
80 TestString.compare(label, baseline, testfile[i], encoding='utf-8')
81 i = i + 1
1行ずつ検討しましょう。
| 1~16行 | Dogtailモジュールをインポートし、テスト出力ファイルについてテスト前のクリーンアップを行います。 |
| 19行 | Geditテキストエディタを起動します。 |
| 22行 | GUIのフォーカスをGeditのテキストウィンドウに置きます。 |
| 24行 | さらに、フォーカスをGeditのテキストバッファに置きます。 |
| 29~33行 | 入力テキストファイルを読み取ります。 |
| Geditの「Save As」ダイアログボックスにアクセスします。 | |
| 42行 | ファイルを指定の出力ディレクトリに保存するために、「other folders」オプションを選択します。 |
| 45行 | ファイルをデスクトップ上に保存します。Save Asダイアログには、複数の「Desktop」エントリがあることに注意してください。roleNameを問い合わせることも必要です。詳細については、Dogtailの「tree」クラスのオンラインヘルプを参照してください。 |
| 52~55行 | ファイルを保存し、Geditをクローズします。 |
| 77~81行 | 保存したファイルを問題のない既知のファイルと比較します。行80でDogtailのログファイルに書き込まれるロギング出力を作成していることに注意してください。Dogtailのデフォルト設定では、これらのログファイルは/tmp/dogtail/logsに保存されます。 |
次に、アクセス可能性、オブジェクト指向バージョンの同じテストスクリプトを検討しましょう。
1 #!/usr/bin/env python
2 # Dogtail demo script using tree.py
3 # FIXME: Use TC.
4 __author__ = 'Zack Cerza <zcerza@redhat.com'
5
6 from dogtail import tree
7 from dogtail.utils import run
8 from time import sleep
9 from os import environ, path, remove
10 environ['LANG']='en_US.UTF-8'
11
12 # Remove the output file, if it's still there from a previous run
13 if path.isfile(path.join(path.expandvars("$HOME"), "Desktop", "UTF8demo.txt")):
14 remove(path.join(path.expandvars("$HOME"), "Desktop", "UTF8demo.txt"))
15
16 # Start gedit.
17 run("gedit")
18
19 # Get a handle to gedit's application object.
20 gedit = tree.root.application('gedit')
21
22 # Get a handle to gedit's text object.
23 textbuffer = gedit.child(roleName = 'text')
24
25 # Load the UTF-8 demo file.
26 from sys import path
27 utfdemo = file(path[0] + '/data/UTF-8-demo.txt')
28
29 # Load the UTF-8 demo file into gedit's text buffer.
30 textbuffer.text = utfdemo.read()
31
32 # Get a handle to gedit's File menu.
33 filemenu = gedit.menu('File')
34
35 # Get a handle to gedit's Save button.
36 savebutton = gedit.button('Save')
37
38 # Click the button
39 savebutton.click()
40
41 # Get a handle to gedit's Save As... dialog.
42 saveas = gedit.dialog('Save as...')
43
44 # We want to save to the file name 'UTF8demo.txt'.
45 saveas.child(roleName = 'text').text = 'UTF8demo.txt'
46
47 # Save the file on the Desktop
48
49 # Don't make the mistake of only searching by name, there are multiple
50 # "Desktop" entires in the Save As dialog - you have to query for the
51 # roleName too - see the online help for the Dogtail "tree" class for
52 # details
53 desktop = saveas.child('Desktop', roleName='table cell')
54 desktop.actions['activate'].do()
55
56 # Click the Save button.
57 saveas.button('Save').click()
58
59 # Let's quit now.
60 filemenu.menuItem('Quit').click()
このスクリプトと手続き型テストスクリプト間のコードの違いは、次のとおりです。
| 20行 | AT-SPIのtree.root.applicationからgeditアプリケーションを特定します。 |
| 33行 | geditの「File」メニュー選択を特定します。 |
| 36行 | さらに、「Save」メニュー選択を特定します。 |
| 42行 | さらに、「Save As」オプションを特定します。 |
| 各GUI要素で一連のアクションを定義します。この場合、行うことは、出力ファイルを保存できるようにDesktopの選択を アクティブ化 することだけです。アクションの詳細については、Dogtailの 「tree」クラスのオンラインヘルプを参照してください。この記事の次の項では、Dogtailの各クラスについて利用できるオンラインヘルプを説明します。 |
この記事の前の方で、DogtailがPythonで構築されているため、テストスクリプトの作成が容易になり、Dogtailのオブジェクト指向設計がサポートされたことを説明しました。DogtailをPythonで構築したことのもう1つの利点は、テストスクリプトをPythonのインタプリタでデバッグできるということです。インタプリタを使用すると、Dogtailについて学習したことをすばやく試すことができて便利です。Dogtailのスクリプトで実行できるステートメントは、すべてインタプリタに入力して実行することもできます。
次に、先ほど説明したテストスクリプトの例から取ったサンプルを示します。 savebutton 数を定義したあと、dirステートメントを入力するだけで、それについてサポートされるすべてのメソッドのリストを取得できます。
>>> # Get a handle to gedit's Save button.
... savebutton = gedit.button('Save')
>>> dir (savebutton)
['_Node__accessible', '_Node__action', '_Node__component', '_Node__hideChildren',
'_Node__nodeIsIdentifiable', '_Node__text', '__doc__', '__getattr__', '__init__',
'__module__', '__setattr__', '__str__', 'addSelection', 'blink', 'button', 'child',
'childLabelled', 'childNamed', 'click', 'contained', 'debugName', 'doAction', 'dump',
'findAncestor', 'findChild', 'findChildren', 'getAbsoluteSearchPath', 'getLogString',
'getNSelections', 'getRelativeSearch', 'getUserVisibleStrings', 'grabFocus', 'menu',
'menuItem', 'rawClick', 'rawType', 'removeSelection', 'satisfies', 'setSelection', 'tab',
'textentry', 'typeText']
さらに、DogtailをサポートするPythonモジュールに含まれるマニュアルにアクセスすることもできます。例として、次にクラスのヘルプの一部を示します。
>>> help (tree)
NAME
dogtail.tree - Makes some sense of the AT-SPI API
FILE
/usr/share/doc/dogtail-0.5.0/examples/dogtail/tree.py
DESCRIPTION
The tree API handles various things for you:
- fixes most timing issues
- can automatically generate (hopefully) highly-readable logs of what the
script is doing
- traps various UI malfunctions, raising exceptions for them (again,
hopefully improving the logs)
The most important class is Node. Each Node is an element of the desktop UI.
There is a tree of nodes, starting at 'root', with applications as its
children, with the top-level windows and dialogs as their children. The various
widgets that make up the UI appear as descendents in this tree. All of these
elements (root, the applications, the windows, and the widgets) are represented
as instances of Node in a tree (provided that the program of interest is
correctly exporting its user-interface to the accessibility system). The Node
class is a wrapper around Accessible and the various Accessible interfaces.
(すべての詳細については、実際のDogtailのヘルプを参照してください。)
Dogtailの次のステップは何でしょうか。
この記事を執筆している時点で、Dogtailの開発チームは、レコーディング/プレイバック機構を実装することに没頭しています。
来月は、その機構や他のGUI自動化の話題(ロギング、画面キャプチャと分析、Dogtailのモジュールとクラスのより詳細な検討など)について、詳しく説明します。
あなたは次に何をしますか?Dogtailをダウンロードして、適当に使ってみてください。
Len DiMaggioはDogtailの作成者ではなく、ますます増えている幸福なユーザの1人に過ぎません。Lenは、ウェストフォード(米マサチューセッツ州)のRed Hatオフィスに勤務するQEエンジニアであり、『Dr. Dobbs Journal』、『Software Development Magazine』、『IBM Developerworks』、『STQE』、その他の雑誌に、ソフトウェアのテストに関する記事を発表しています。Dogtailを作成し、開発した功績は、最初にそれを完成させ、その将来に向けた貢献を行っているコミュニティに持ち込んだRed Hatの同僚のチームに帰されます。