Ubuntu Weekly Recipe

第257回AutopilotでかんたんUIテスト

GUIアプリケーションにおいてユーザーインターフェースのテストは重要です。今回はUnityとUbuntuのインストーラーのUIテスト自動化のために開発された、Autopilotの使い方について紹介します。

Autopilotとは

冒頭でも述べたとおりUIのテストは重要なのですが、ユーザーの操作が必要になることから自動化が難しい分野でもあります。例えば過去のインストーラーの品質評価では、テスト項目を記載して複数のユーザーに手動でインストールを実行してもらい、問題を洗い出すという対応を行っていました[1]⁠。

しかしこれでは作業コストがかかりすぎてしまいますし、アルファやベータリリースのようなマイルストーン毎でないとなかなかテストユーザーが集まりません。このためこれまでも、少なくともインストーラーのテストの自動化は急務であるという認識がありました。さらに、UbuntuはUnityという独自のインターフェースを開発・採用していることもあって、UI全般のテスト自動化の必要性も高まっていました。

そこで開発されたのがAutopilotです。AutopilotはPythonで書かれた、GUIユニットテストフレームワークであり、python-testtoolsやpython-xlibをベースにテストフレームワークの実行やインプットデバイスのエミュレーションができるようになっています。

また、DBusを使って各種ツールキットの状態を取得できるイントロスペクションも実装されているため、既存のアプリケーション側を変更することなく、ユーザー操作に対するアプリケーションの動作を確認することができるようになる予定です。ただし現時点ではこの部分はほぼUnityのみの対応になっています。

Autopilotを使うと、Ubuntu上のUIテストをPythonを書くだけでかんたんに自動化できるのです。

インストールとUnityのテスト

それではまずAutopilotをインストールしましょう。Autopilotの動作にはUbuntu 12.04以上が必要です。12.10以上であれば公式のリポジトリにパッケージが存在しますので、12.10以上を使うことをおすすめします。

$ sudo apt-get install python-autopilot unity-autopilot

最後のunity-autopilotパッケージは、Unityのテストスクリプト集です。そこで実際にこのテストスクリプトを実行して、どんなことができるか確認してみましょう。listコマンドを使うと、実行可能なテストIDの一覧が得られます。例えばUnityのテストスイートの一覧を取得する場合は次のコマンドを実行してください。

$ autopilot list unity
    unity.tests.launcher.test_capture.LauncherCaptureTests.test_launcher_capture_while_not_sticky_and_hidden
    unity.tests.launcher.test_capture.LauncherCaptureTests.test_launcher_capture_while_sticky_and_hidden_moving_right
    unity.tests.launcher.test_capture.LauncherCaptureTests.test_launcher_captures_while_sticky_and_revealed
    unity.tests.launcher.test_capture.LauncherCaptureTests.test_launcher_not_capture_while_not_sticky_and_hidden_moving_right
    unity.tests.launcher.test_capture.LauncherCaptureTests.test_launcher_not_capture_while_not_sticky_and_revealed
(以下略)

同じテストを複数のテストパターン(シナリオ)で実行する場合は、テストIDの前に数字が表示されます。

実際にいくつかのテストを試してみましょう。UIテスト時は、自動的にマウスやキーボードの操作が行われます。不用意な設定変更やデータの消去を避けるためにも、ゲストアカウントでログインしてから実行した方が良いでしょう。ゲストアカウントは右上の歯車アイコンから「ゲストセッション」を選ぶことでログインできます。

ゲストセッションに移行したらrunコマンドに実行したテストIDを指定します。端末を開いて次のコマンドを実行してください。

$ autopilot run unity.tests.test_dash.DashKeyNavTests.test_lensbar_enter_activation
.
----------------------------------------------------------------------
Ran 1 test in 7.302s

OK

これは、Dashを開いてカーソルキーでフォーカスを移動し、最終的にアプリLensをEnterキーで表示させるテストです。AutopilotはDBus経由でUnityの状態を監視し、期待通りアプリLensのアイコンにフォーカスがあたること、アプリLensを表示後はアプリLensアイコンのフォーカスが外れる(入力欄にフォーカスが移動する)ことを確認します。

キーボードやマウスの操作をエミュレートできるため、ibus-anthyを使った日本語入力に関係するテストも可能です。次のテストは、DashやHUDから日本語を入力し[2]⁠、その結果が期待通りになっているかを、いくつかのパターンで確認します。

$ autopilot run unity.tests.test_ibus.IBusTestsAnthy.test_anthy
............
----------------------------------------------------------------------
Ran 12 tests in 86.568s

OK

ちなみにunityテストスイートのすべてのテストを実行する場合は、テストスイート名を指定します。ただしかなり時間がかかります。

$ autopilot run unity

もっと簡単なサンプル

今度は実際にテストを作ってみましょう。Pythonの知識さえあれば誰でもかんたんに作成できます。ユニットテストに触れた経験があるなら、すぐにでも書き始められるでしょう。

まず、autopilotパッケージですが、Autopilot開発チームのPPAに不具合修正やドキュメントの追加が行われた、より新しいパッケージが存在するので今後はそちらを使用します[3]⁠。

$ sudo add-apt-repository ppa:autopilot/ppa
$ sudo apt-get update
$ sudo apt-get upgrade
もしくは
$ sudo apt-get python-autopilot

ひな形を作る

Autopilotでは、ある機能に対する一連のテストを「テストケース⁠⁠、複数のテストケースやテストスイートを一つのカテゴリーとしてまとめたものを「テストスイート」と呼称しています。

そこでファイルの構成としてまずテストスイート名のフォルダーを作り、その中にテストケースごとにPythonのソースコードファイルを作成すると良いでしょう。今回はテストスイート名を「Recipe⁠⁠、テストケース名を「Sample」とします。

以下のようなフォルダーとファイルを作成してください。

.
└── Recipe
    ├── __init__.py
    └── test_sample.py

__init__.pyは、Autopilotにこのフォルダーがテストスイート用に作られることを知らせるために作っています。今回は空ファイルのままで問題ありません。

編集すべきファイルはtest_sample.pyのみです。テストケースはAutopilotTestCaseのサブクラスとして実装しますので、test_sample.pyを次のように編集してください。

# -*- coding: utf-8 -*-
from autopilot.testcase import AutopilotTestCase
class SampleTests(AutopilotTestCase):

    def test_keyboard(self):
        self.keyboard.type("Hello autopilot")

たったこれだけで「"Hello autopilot"をキーボードで入力する」テストの完成です。先ほどと同じように、テスト一覧を表示させてみましょう。

$ autopilot list Recipe
Loading tests from: /home/shibata/Sample

    Recipe.test_sample.SampleTests.test_keyboard

 1 total tests.

runコマンドで実行すると[4]⁠、"Hello autopilot"と入力されることがわかります。

$ autopilot run Recipe.test_sample.SampleTests.test_keyboard
Loading tests from: /home/shibata/Sample

Tests running...
Hello autopilot
Ran 1 test in 3.200s
OK

setUp()とtearDown()

一つのテストケースに複数のテストメソッド(今回の場合はtest_keyboard())を記述できます。そうすると、テストメソッドごとの初期化処理や終了処理を共通化したいこともあるでしょう。

そんな時に使えるのがsetUp()とtearDown()と呼ばれるテストフィクスチャーです。テストケースのクラスでオーバーライドすることで、テストメソッドごとの初期化(setUp())と終了処理(tearDown())を設定できます[5]⁠。

前節の例では実行した端末上でキーボード入力を行っていました。しかし本来は端末上ではなく、例えばテキストエディター上でキーボード入力のテストを行いたいかもしれません。その場合はテストごとにテキストエディターを起動する必要があります。これは、setUp()を使って次のように書けます。

# -*- coding: utf-8 -*-

from autopilot.testcase import AutopilotTestCase

class SampleTests(AutopilotTestCase):

    def setUp(self):
        super(SampleTests, self).setUp()
        self.app = self.start_app("Text Editor")

    def test_keyboard(self):
        self.keyboard.type("Hello autopilot")

テストを実行してみると、テキストエディター(gedit)が起動し、⁠Hello autopilot」と入力されることがわかるでしょう[6]⁠。

マウスの操作

マウス操作用のクラスも存在します。test_sample.pyのSampleTestsクラスにまず次のメソッドを追加してください。

    def test_mouse(self):
        self.keyboard.press_and_release("Super+Ctrl+Up")
        self.mouse.move(0, 0)
        self.mouse.click(button=1)

新しく追加したテストを実行すると、geditが開き、最大化され、マウスが左上に移動して閉じるボタンを押すはずです。

$ autopilot run Recipe.test_sample.SampleTests.test_mouse

keyboard.press_and_relase()はショートカットキーのように複数のキーを同時に押したい場合に使います。"Super+Ctrl+Up"はUnityにおけるウィンドウを最大化するためのショートカットです。mouse.move()でカーソルを左上に移動し、mouse.click()で左ボタンをクリックさせています。最大化した場合、閉じるボタンが左上に来ているために、これによりウィンドウが閉じられるというわけです[7]⁠。

テスト結果の確認

ここまでの作業では「自動化」をしただけであって、⁠テスト結果の確認」は行っていません。例えばSuperキーでDashを開くかどうかのテストを行う際、Superキーの入力を行ったあとに、⁠実際にDashが開いたかどうか」を調べる必要があります。

Autopilotでは各GUIツールキットのイントロスペクションを提供することで、UnityやGtk/Qtアプリケーションの「状態」をDBus経由で取得できるようになっています。

また、テスト本体とテスト対象のアプリケーションは非同期的に動作するため、テスト本体から行ったキー入力の結果がすぐにアプリケーションに反映されるわけではありません。このDBusによる状態の取得と非同期処理を隠蔽するために、AutopilotではEventuallyというクラスを用意しています。

例えば、gedit上で入力した文字列を全選択した上でコピーを行い、クリップボードに期待通りのデータが入っているか確認するテストを実装してみましょう。test_sample.pyの冒頭と、SampleTestsクラスに次のコードを追加します。

from autopilot.testcase import AutopilotTestCase
from autopilot.emulators.clipboard import get_clipboard_contents
from autopilot.matchers import Eventually
from testtools.matchers import Equals

(中略)

    def test_clipboard(self):
        self.keyboard.type("Hello autopilot")
        self.keyboard.press_and_release("Ctrl+a")
        self.keyboard.press_and_release("Ctrl+c")
        self.assertThat(get_clipboard_contents, Eventually(Equals("Hello autopilot")))

新しく追加したテストを実行しましょう。画面上で文字(Hello autopilot)が入力されたあとに、選択(Ctrl+a⁠⁠・コピー(Ctrl+c)されるはずです。最後のassertThat()では、クリップボードから取得したデータ(get_clipboard_contetnts()メソッド戻り値)が、"Hello autopilot"に一致する(Equals())かどうかを確認します。

$ autopilot run Recipe.test_sample.SampleTests.test_clipboard
Loading tests from: /home/shibata/Sample

Tests running...

Ran 1 test in 5.277s
OK

まとめ

AutopilotでUIテストを書くための必要最低限の知識を紹介しました。これをもとにそれぞれのアプリケーションごとにテストを書き始めるわけですが、実際に書いてみると「Gtk/Qt用のメソッドやサンプルが少ない」⁠ドキュメントがほとんどない」という壁にあたると思います。

前者については、現在Unity向けがメインであるためかなかなか進んでいない状況で、ひょっとすると13.04がリリースされたあたりでも変わってないかもしれません。後者については少しずつ改善はされています。現在は、Unityのテストスクリプトを参照することが一番の近道でしょう。

UIテストとしての機能もまだ十分ではありませんが、イメージマッチングを使ったUIテストフレームワークであるXpresserをAutopilotからも使えるようにする予定があるなど、13.04に向けてそしてUbuntu Test Automation Harnessの実現に向けてさまざまな計画が進行中です。

まだ機能が複雑でないうちに、UIテストの自動化を試してみてはいかがでしょうか?

おすすめ記事

記事・ニュース一覧