Skip to content

翻訳記事:

  • RED HAT Magazineは、米国発の記事を翻訳したものです。

RED HAT SPEAKS

Dogtailによる自動GUIテスト

原文(英語/2006年6月)

苦境からの脱却 ― LinuxでのGUIテスト

アプリケーションの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は現在のところ、GNOMEデスクトップを備えたRed Hat Enterprise Linux 4とFedora Core 5上で動作します。この記事に含まれる画面イメージは、Red Hat Enterprise Linux 4を実行しているシステムから取られています。

Dogtailとは

DogtailのWebサイトでは、次のように説明されています。Dogtail website(英語):

「...Dogtailは、Pythonで作成されたGUIテストツールであり、自動フレームワークです。Dogtailはアクセス可能性(A11Y1)テクノロジを利用してデスクトップアプリケーションと通信します。 DogtailのスクリプトはPythonで作成され、他のあらゆるPythonプログラムと同様に実行されます」

その小さなパラグラフをもう少し詳しく調べてみましょう。実はかなり深い意味があります。

  • Dogtailは、Pythonで作成されたGUIテストツールであり、自動フレームワークです。 ogtail自体がアプリケーションをテストするわけではありません。Dogtailは、テストの構築に利用できる、使いやすいフレームワークを提供します。
  • Dogtailは、アクセス可能性(A11Y)テクノロジを利用してデスクトップアプリケーションと通信します。 これはDogtailの設計の主要な特徴です。他のいくつかのGUIテスト自動フレームワークと異なり、Dogtailでは、テストのGUIのもとでアプリケーションを示す表示から情報を取得して所有権付きのデータストアに格納することはありません。そうではなく、Dogtailでは、アクセス可能性関連のメタデータを利用して、アプリケーションのGUI要素のメモリ内モデルを作成します(これについては、少しあとで詳しく説明します)。
  • DogtailのスクリプトはPythonで作成され、他のあらゆるPythonプログラムと同様に実行されます。 私がDogtailの使用方法を学んだときに最初に持った疑問の1つは、「インストールしたあと、どうやって使うのか、何を実行するのか」ということでした。その答えは、「自分でテストスクリプトを書き、それを実行する」です。それほど容易なことなのです(この記事のあとの方で、スクリプトの例を検討します)。
  • このパラグラフでは触れられていませんが、Dogtailはオープンソースプロジェクトであることも重要です。そのコードは、使用し、研究し、手を加えることができます。
DogtailとLinux Desktop Testing Project (LDTP) は、両方ともアクセス可能性テクノロジを利用してアプリケーション要素を識別し、特定しますが、両プロジェクトには関連はなく、次の2つの重要な違いがあります。
  • LDTPコードの大部分はCで書かれています。Dogtailは100%がPythonであり、オブジェクト指向アーキテクチャで設計されているため、カスタマイズのサポートも強化されます。
  • Dogtailでは、実行時にアクセス可能なアプリケーション要素を動的に検出します。LDTPでは、テストスクリプトを実行する前に静的な「アプリケーションマップ」が生成されます。

Dogtailの動作方法

あらゆる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で書かれたモジュール)が使用されます。

図1. アクセス可能性情報を利用するDogtailのアーキテクチャ

DogtailのAPI ― アクセス可能性APIと手続き型API

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で作成された短いテストスクリプトの例を検討します。

Dogtailの使用 ― ステップバイステップ

説明はもう十分です。上記のすべての情報を実際に使用して、テストスクリプトを作成し、実行しましょう。このプロセスには、5つのステップがあります。

ステップ1 ― Dogtailをインストールする

まず、Dogtailに必要なパッケージがインストールされていることを確認します。:

Dogtailを使用するには、次のパッケージが必要です。:

  • AT-SPI対応のデスクトップ(現時点ではGNOME)
  • Python2.3以降(ご使用のRed Hat Enterprise Linuxディストリビューションから入手可能)
  • ImageMagick6.2以降(ご使用のRed Hat Enterprise Linuxディストリビューションから入手可能)
  • rpm-pythonまたはpython-apt(ご使用のRed Hat Enterprise Linuxディストリビューションから入手可能)
  • Python対応のElementTree(ご使用のRed Hat Enterprise Linuxディストリビューションから入手可能)
  • pyspi - Python AT-SPI バインディング

次に、現行のDogtail rpmをインストールします。

Dogtailの使用は少し容易になりました。それは2006年5月1日現在、Fedora Extrasに含まれています。Fedora Core 5(FC5)またはRawhideを使用している場合は、yum install dogtailを実行するだけです。

ステップ2 ― アクセス可能性を設定する

Dogtailでアクセス可能性テクノロジの情報を利用できるようにするには、GNOMEデスクトップでアクセス可能性を有効にする必要があります。GNOMEデスクトップGUIでこれを行うには、メニュー: アプリケーション->個人設定->アクセシビリティ->支援技術のサポート にアクセスし、「支援技術を有効にする」チェックボックスを選択します。

図2.支援技術 (Assistive Technologies)の設定 ダイアログボックス
この変更を有効にするには、デスクトップセッションを再起動します。

さらに、対象となるアプリケーションがJavaで作成されている場合は、次の環境変数も設定する必要があります。

export GTK_MODULES="gail:atk-bridge"

対象となるアプリケーションをシェルプロンプトから起動する場合は、次のテキストが表示されます。

GTK Accessibility Module initialized

ステップ3 ― アプリケーションのGUI要素を識別する

さて、面白くなってきました。テストスクリプトを作成してアプリケーションの動作を確認するには、まず、操作するGUI要素を識別する必要があります。サンプルスクリプトでは、geditテキストエディタのテストを作成して実行します。

アプリケーションGUIのアクセス可能性情報のメタデータを表示する例を確認しましょう。2つのアプローチをとることができます。

アプローチ#1:「sniff」ユーティリティ - Dogtailフレームワークには、sniffというスタンドアロンユーティリティが含まれています。このユーティリティは、デスクトップからアクセスできるあらゆるアプリケーションを検査します。アプリケーションが実際に実行中である場合、sniffはそのアクティブウィンドウを通じてアプリケーションにアクセスします。次に、geditテキストエディタについてsniffの出力例を示します。sniffがアプリケーションのすべてのGUI要素の名前を表示する方法に注意してください。sniffは、geditで編集されているファイルが変更された(しかし未保存の)状態であることも認識しています。

図3. 動作中のsniff

アプローチ#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要素がサポートしているアクションです。これらは、テストスクリプトで実行するアクションです。これらについては、この記事のあとの方で詳しく説明します。

ステップ4 ― テストスクリプトを作成し、実行する

ここまでは問題ありません。さて、何かコードを書きましょう。記事のこの項では、Dogtail APIのそれぞれを使用したgedit GNOMEテキストエディタの単純なテストを検討します。このテストでは、単にファイルを読み取って保存したあと、そのファイルを問題のない既知の(「ゴールデン」)ファイルと比較します。DogtailのWebサイトからのダウンロードに利用できる2つのgeditテストスクリプトを検討します。

手続き型APIの使用

手続き型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行 入力テキストファイルを読み取ります。
36~39行 Geditの「Save As」ダイアログボックスにアクセスします。
42行 ファイルを指定の出力ディレクトリに保存するために、「other folders」オプションを選択します。
45行 ファイルをデスクトップ上に保存します。Save Asダイアログには、複数の「Desktop」エントリがあることに注意してください。roleNameを問い合わせることも必要です。詳細については、Dogtailの「tree」クラスのオンラインヘルプを参照してください。
52~55行 ファイルを保存し、Geditをクローズします。
77~81行 保存したファイルを問題のない既知のファイルと比較します。行80でDogtailのログファイルに書き込まれるロギング出力を作成していることに注意してください。Dogtailのデフォルト設定では、これらのログファイルは/tmp/dogtail/logsに保存されます。

アクセス可能性/オブジェクト指向APIの使用

次に、アクセス可能性、オブジェクト指向バージョンの同じテストスクリプトを検討しましょう。

     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() 
ここから以降のコードは、手続き型APIの例と同じです。

このスクリプトと手続き型テストスクリプト間のコードの違いは、次のとおりです。

20行 AT-SPIのtree.root.applicationからgeditアプリケーションを特定します。
33行 geditの「File」メニュー選択を特定します。
36行 さらに、「Save」メニュー選択を特定します。
42行 さらに、「Save As」オプションを特定します。
54~55行 各GUI要素で一連のアクションを定義します。この場合、行うことは、出力ファイルを保存できるようにDesktopの選択を アクティブ化 することだけです。アクションの詳細については、Dogtailの 「tree」クラスのオンラインヘルプを参照してください。この記事の次の項では、Dogtailの各クラスについて利用できるオンラインヘルプを説明します。

Pythonの2つの利点

この記事の前の方で、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の次のステップは何でしょうか。

この記事を執筆している時点で、Dogtailの開発チームは、レコーディング/プレイバック機構を実装することに没頭しています。

来月は、その機構や他のGUI自動化の話題(ロギング、画面キャプチャと分析、Dogtailのモジュールとクラスのより詳細な検討など)について、詳しく説明します。

あなたは次に何をしますか?Dogtailをダウンロードして、適当に使ってみてください。

More information

執筆者について

Len DiMaggioはDogtailの作成者ではなく、ますます増えている幸福なユーザの1人に過ぎません。Lenは、ウェストフォード(米マサチューセッツ州)のRed Hatオフィスに勤務するQEエンジニアであり、『Dr. Dobbs Journal』、『Software Development Magazine』、『IBM Developerworks』、『STQE』、その他の雑誌に、ソフトウェアのテストに関する記事を発表しています。Dogtailを作成し、開発した功績は、最初にそれを完成させ、その将来に向けた貢献を行っているコミュニティに持ち込んだRed Hatの同僚のチームに帰されます。