Appiumを使ってスマートフォンアプリのテストを自動化する - Python編


## 1. はじめに

前回、[「Appiumを使ってスマートフォンアプリのテストを自動化する - 概要編」](http://qiita.com/skinoshita/items/fab6373f95836eceb177)でAppiumでスマートフォンアプリのテストを自動化できることを書きました。

今回はAppiumとPythonを使って、スマートフォンアプリのテストコードの作成方法について書いていきたいと思います。

(補足)
現時点(2013/7/20)では、iOSアプリのみを対象としています。
Androidアプリに関しては、後日記載します。

## 2. 前提条件

今回、使用した環境は以下のとおりです。

|ソフトウェア|バージョン|
|:----:|:----:|
|OS|MacOS X バージョン 10.8.4|
|Python|2.7.4|
|pip|1.2.1|
|XCode|4.5.3|

なお、Appiumのインストールについては前回記事：[「Appiumを使ってスマートフォンアプリのテストを自動化する - 概要編」](http://qiita.com/skinoshita/items/fab6373f95836eceb177)をご覧ください。

## 3. サンプルコードを動かす

まず始めにサンプルコードを使って動作を確認します。ここからの操作はターミナル上で行います。

GitHubからAppiumのソース一式を取得します。

```
git clone https://github.com/appium/appium.git
```

Appiumのソース一式を取得したら、TestAppのフォルダに移動します。

```
cd appium/sample-code/apps/TestApp/
```

xcodebuildコマンドを使って、iOSアプリをビルドします。ビルドに使用するiOSのSDKはiPhone Simulatorにします。

```
xcodebuild -sdk iphonesimulator
```

Pythonのサンプルコードが格納されているフォルダに移動します。

```
cd ../../examples/python/
```

サンプルコード実行前に、Pythonのseleniumモジュールが入っているかを確認してください。
入っていない場合はpipを使ってseleniumモジュールをインストールします。

```
# Pythonのseleniumモジュールが入っているかを確認します
pip freeze

# seleniumモジュールが入っていない場合、以下のコマンドを実行します
sudo pip install selenium
```

準備が整ったので、サンプルコードを動かします。

```
appium &
python simple.py
```

## 4. サンプルコードからテストコードの書き方を学ぶ

いろいろと探したのですが、Pythonでのテストコードの書き方に関するチュートリアルが見つからなかったのでサンプルコードをもとにテストコードの書き方をまとめたいと思います。
→ チュートリアルのようなものを見つけたらここの記事の内容を書き直します。

なお、サンプルコードの格納先は「appium/sample-code/examples/python/」になります。

サンプルコードを読むとselenuimモジュールのWebDriver APIを介してiOSアプリを動かしていることが分かります。

### (1) unittest.TestCaseクラスを継承したテストクラスを作成します

```python
import unittest


class AppiumSampleTest(unittest.TestCase):

	def setUp(self):
		pass

	def tearDown(self):
		pass
```

### (2) テストクラスのsetUpメソッドで起動するiOSアプリをAppiumに登録します

```python
def setUp(self):
	# iOSアプリが配置されている場所のパスを取得します
	# (osモジュールをインポートしておくこと)
	app = '(iOSアプリが配置されているパス)'
	
	# seleniumのwebdriverを使ってAppiumにiOSアプリを認識させます
	# (seleniumモジュールからwebdriverをインポートしておくこと)
	# command_executorで指定するドメイン部分とポート部分は
	# 利用している環境によって変わります
	self.driver = webdriver.Remote(
				command_executor = 'http://127.0.0.1:4723/wd/hub',
				desired_capabilities = {
					'browserName' : 'iOS',
					'platform' : 'Mac',
					'version' : '6.0',	# iOSのバージョン
					'app' : app		# 実行するiOSアプリが配置されているパス
				})
```

### (3) テストクラスのtearDownメソッドでiOSアプリを終了します

```python
def tearDown(self):
	self.driver.quit()
```

### (4) テストメソッドを追加します

```python
def test_ui_operation(self):
	# 1.スライダーの操作
	# 1-1.スライダーを取得する
	sliders = self.driver.find_elements_by_tag_name('slider')
	# 1-2.スライダーの初期値を評価する
	self.assertEqual(sliders[0].get_attribute('value'), '50%')
	# 1-3.タッチイベントを取得する
	drag = webdriver.common.touch_actions.TouchActions(self.driver)
	# 1-4.スライダーの設定値を-50%にする
	drag.flick_element(sliders[0], -0.5, 0, 0)
	# 1-5.スライダーを動かす
	drag.perform()
	# 1-6.スライダーの設定値が0%になっていることを評価する
	self.assertEqual(sliders[0].get_attribute('value'), '0%')

	# 2.テキストフィールドの操作
	# 2-1.テキストフィールドを取得する
	textFields = self.driver.find_elements_by_tag_name('textField')
	# 2-2.1番目のテキストフィールドに1を入力する
	textFields[0].send_keys(1)
	# 2-3.2番目のテキストフィールドに2を入力する
	textFields[1].send_keys(2)
	# 2-4.1番目のテキストフィールドと2番目のテキストフィールドの値の合計が3であることを評価する
	self.assertEqual(int(textFields[0].get_attribute('value'))
                         + int(textFields[1].get_attribute('value')), 3)

	# 3.ボタンの操作
	# 3-1.ボタンを取得する
	buttons = self.driver.find_elements_by_tag_name('button')
	# 3-2.ボタンをクリックする
	buttons[0].click()

	# 4.ラベルの操作
	# 4-1.ラベルを取得する
	texts = self.driver.find_elements_by_tag_name('staticText')
	# 4-2.ラベルの内容が3であることを評価する
	self.assertEqual(int(texts[0].get_attribute('value')), 3)
```

seleniumモジュールでのiOSアプリの画面部品の取り扱いをまとめると

#### 画面部品の取得方法
|画面部品|取得方法|
|:----:|:----|
|UITextField|self.driver.find_elements_by_tag_name('textField')|
|UIButton|self.driver.find_elements_by_tag_name('button')|
|UILabel|self.driver.find_elements_by_tag_name('staticText')|
|UISlider|self.driver.find_elements_by_tag_name('slider')|

#### 画面部品の操作方法
|画面部品|操作内容|操作方法|
|:----:|:----:|:----|
|UITextField|値を入力する|send_keysメソッドを使用する|
|UITextField|値を取得する|get_attributeメソッドを使用する|
|UIButton|ボタンをクリックする|clickメソッドを使用する|
|UILabel|ラベルの表示内容を取得する|textプロパティを参照する|
|UISlider|設定値を変更する|TouchActionsのflick_elementメソッドで値を設定する。設定後、TouchActionsのperformメソッドでスライダーを動かす。|

となります。

(補足) 上記の表は、随時更新していきます。

なお、ソースコードの全体像は以下のとおりです。

```python
# coding: utf-8

import unittest
import os
from selenium import webdriver


class AppiumSampleTest(unittest.TestCase):

    def setUp(self):
        # iOSアプリが配置されている場所のパスを取得します
        app = os.path.join('/tmp/appium',
                    'sample-code/apps/TestApp/build/Release-iphonesimulator',
                    'TestApp.app')
        app = os.path.abspath(app)

        # seleniumのwebdriverを使ってAppiumにiOSアプリを認識させます
        # command_executorで指定するドメイン部分とポート部分は
        # 利用している環境によって変わります
        self.driver = webdriver.Remote(
            command_executor='http://localhost:4723/wd/hub',
            desired_capabilities={
                'browserName': 'iOS',
                'platform': 'Mac',
                'version': '6.0',
                'app': app
            })

    def test_ui_operation(self):
        # スライダーの操作
        sliders = self.driver.find_elements_by_tag_name('slider')
        self.assertEqual(sliders[0].get_attribute('value'), '50%')
        drag = webdriver.common.touch_actions.TouchActions(self.driver)
        drag.flick_element(sliders[0], -0.5, 0, 0)
        drag.perform()
        self.assertEqual(sliders[0].get_attribute('value'), '0%')

        # テキストフィールドの操作
        textFields = self.driver.find_elements_by_tag_name('textField')
        textFields[0].send_keys(1)
        textFields[1].send_keys(2)
        self.assertEqual(int(textFields[0].get_attribute('value'))
                         + int(textFields[1].get_attribute('value')), 3)

        # ボタンの操作
        buttons = self.driver.find_elements_by_tag_name('button')
        buttons[0].click()

        # ラベルの操作
        texts = self.driver.find_elements_by_tag_name('staticText')
        self.assertEqual(int(texts[0].get_attribute('value')), 3)

    def tearDown(self):
        self.driver.quit()


if __name__ == '__main__':
    unittest.main()
```

## 5. 参考

* Python Seleniumモジュール - WebDriver API ：[http://selenium-python.readthedocs.org/en/latest/api.html](http://selenium-python.readthedocs.org/en/latest/api.html)