はじめに
iOS, Android 両対応の自動UIテストツールとして Calabash と Appium を試してみました。Calabash はテストを自然言語(つまり日本語でも)で書けるのが良いのですが、
- テスト対象に calabash.framework を仕込むので、実際にリリースするものとは別にビルドを分ける必要がある。
- デフォルトで用意されているコマンドでは日本語キーボードでのテキスト入力がうまくできない
という問題がありました。その後の試行錯誤で後者の日本語入力は何とかできるようになりましたが、この記事執筆開始時点ではまだ解決方法が分かっていませんでした。Calabash-iOS については「WKWebViewを使うアプリのUI自動テストにCalabash-iOSを使ってみた」という記事を書いていますので参考にしてください。
Calabash は Cucumber というテストツールを iOS, Android モバイルアプリのテストに利用できるようにしたものです。つまり__日本語でテストが書けるのは Cucumber の機能__です。
一方 Appium は複数のプログラミング言語に対応しているという特徴があり、Ruby にも対応しています。じゃあ Appium の Ruby クライアントを Cucumber から利用することで、Appium のテストを日本語で書けるんじゃないかと考えました。
以下は Calabash と Appium + Cucumber の大雑把な構成を図示したものです。
なお今や Cucumber は Ruby 版以外も存在する みたいです。Appium 本体は Node.js で動作するので、JavaScript 版を利用すれば Ruby 環境構築の手間が省けます。WKWebViewを使うアプリのUI自動テストにCalabash-iOSを使ってみた(JavaScript編)を参照してください(結論としてはオススメでないです)。
Cucumber から Appium を使うというアイデアは何の目新しさもないみたいです。こんな英語の学習コースまで用意されているくらい。しかも評価高い。この記事には ¥12,000 の価値があると思って読んでください(笑)。
関係する記事は以下です。
- 環境構築編 (この記事)
- JavaScript編
- 色々実験編
- Appiumの仕組みと使い方
最初に記事を作成した時の環境は El Captan, Xcode 7.3.1 利用でしたが、macOS sierra, Xcode 8.2.1にしたら、色々と動かなくなりました。特に Xcode 8 では UIAutomation が利用できないため、Appium が裏で利用するドライバを XCUITest 利用のものにする必要があります。
また XCUITest を利用するドライバは iOS 9.3 以降でないと動作しません。つまり Xcode 8 を利用する場合、iOS 9.3 未満は Appium でテストできなくなっています。
記事の内容は Xcode 8 利用想定で書き換えました。
Node.js環境の準備
Appium を動かすための Node.js を Homebrew 1を使ってインストールします。Homebrew はインストール済みの想定です。
もし複数バージョンの node.js を管理したい場合や、指定バージョンの node.js をインストールしたい場合は、node ではなく nodebrew をインストールしてそちらで管理してください。
$ brew update
$ brew install node
Appiumをインストール
公式の手順では -g を付けてグローバルインストールしています。それに倣います。
$ npm install -g appium
実機にアプリをインストールするのに ideviceinstaller というツールが使われます。これを Homebrew でインストールしておきます。頭に --HEAD を付けて最新ソースコードを GitHub から取ってきてコンパイル&インストールしないと、/var/db/lockdown のパーミッションエラーで実行できません。
$ brew install --HEAD ideviceinstaller
appium-xcuitest-driver の説明によると、iOS 10 では ideviceinstaller が動作しないようで、代わりに ios-deploy を入れろと書いてあります。
$ npm install -g ios-deploy
また appium-xcuitest-driver は、実デバイスと通信するのに facebook/WebDriverAgent を利用します。これは appium-xcuitest-driver が必要になると勝手にビルドしてくれるのですが、その際に Carthage を利用しますので、インストールしておきます。
$ brew install carthage
Ruby環境の準備
今回は Appium も Cucumber も Ruby で利用するので、まずは Ruby 自体をインストールします。
インストール方法は WKWebViewを使うアプリのUI自動テストにCalabash-iOSを使ってみた(環境構築編) - Ruby環境の準備 を参考にしてください。
AppiumのRubyクライアントをインストール
rbenv でプロジェクトで利用する Ruby バージョンを設定しておきます。プロジェクトディレクトリ直下で以下を実行します。バージョンはインストールしたものに置き換えてください。
$ rbenv local 2.3.1
Appium の Rubyクライアントをインストールします。ここでは Bundler を使います。Gemfile を作成して以下を記述します。
source "https://rubygems.org"
gem "appium_lib"
念のため bundle コマンドが間違ってシステムにプリインストールされているものを指していないか確認しておきます。
$ which bundle
/Users/username/.rbenv/shims/bundle
大丈夫でした。以下のコマンドを実行します。もし bundle がシステムのもの (/usr/local/bin/bundle) を指しちゃってたら rbenv exec を前に付けてください。
$ bundle install --path vendor/bundle
Nokogiri2 のビルド時に、依存している libxml2 のビルドに失敗してエラーになりました。途中で表示される NOTICE によると、システムにある libxml2 を使うようにもできるとのこと。でも試してもダメでした。
エラーログをよく読むと、libxml2 のビルドで LZMA_OK が undefined というエラーです。"LZMA_OK" でググると OSX への nokogiri 1.6.8.rc3 の install でハマったメモ が見つかりました。これに従います。
$ brew install libxml2
libxml2 2.9.4 が入りました。この libxml2 を使うように設定してリトライ。
$ brew link --force libxml2
$ bundle config build.nokogiri --use-system-libraries
$ bundle install --path vendor/bundle
うまくいきました。
試しに動かしてみる
Appium は Apple が提供している UIAutomation を利用しているので、テストする端末の
設定 - デベロッパ - Enable UI Automation
を有効にしておいてください。
まずは Appium Server を起動します。停止するには Ctrl + C でいいです。
$ appium
別のコンソールを立ち上げます。
まずはデバイスの名前とUDIDを調べます。デバイスのリストが表示され、各デバイスのデバイス名、iOSバージョン, UDIDがわかります。
$ xcrun instruments -s devices
Known Devices:
デバイス名 (iOSバージョン) [UDID]
とりあえず動かしてみましょう。ファイル sample.rb に以下のテストコードを記述します。deviceName は実デバイスの名前に、udid は実デバイスの UDID に、app はアプリの Bundle ID に置き換えてください。
require 'appium_lib'
desired_caps = {
caps: {
platformName: 'iOS',
deviceName: 'username の iPhone',
udid: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
app: 'com.example.MyApp',
automationName: 'XCUITest',
xcodeOrgId: 'AAAAAAAAAA',
xcodeSigningId: 'iPhone Developer'
},
appium_lib: {
wait: 10
}
}
Appium::Driver.new(desired_caps).start_driver
Appium.promote_appium_methods Object
sleep 5
button_element = find_element(:id, 'Menu')
button_element.click
sleep 5
driver_quit
iOS 9.3 でも XCUITest を利用するように automationName: 'XCUITest' を caps に追加しています。利用する端末が iOS 10 なら platformVersion: 10 を指定すると automationName を指定しなくても XCUITest が使われます。
また実デバイスの場合は facebook/WebDriverAgent のビルドに必要な開発チームと署名の設定が必要です。 xcodeOrgId に開発チームID(注:「開発チーム名」ではなく10桁の「開発チームID」3)と、xcodeSigningId に署名IDを指定しておきます(普通は'iPhone Developer'でいい)。
ただし Appium 1.6.3 は appium-xcuitest-driver 2.4.2 をインストールするのですが、このバージョンでは xcodeOrgId, xcodeSigningId を認識してくれませんでした。2.4.2 から 2.0.36 にダウングレードしたら動いたという報告があったので、それを試しましたがうまくいかず、この時点での最新版 2.5.3 に置き換えました。
cd /usr/local/lib/node_modules/appium
npm uninstall appium-xcuitest-driver
npm install appium-xcuitest-driver@2.5.3
アプリは事前にインストールしておきます。または app の部分にアーカイブしたアプリへのパスを指定しておくと、インストールされていなければインストールしてくれます。
ここでは 'Menu' という名前(accessibilityIdentifier に 'Menu' と設定してあります)のボタンをクリックしていますが、適当に他のクリックできるものに置き換えるか、コメントアウトするかしてください。
さて実行します。Bundler でインストールした gem を使う場合は、頭に bundle exec を付ける必要があります(私は面倒なのでシェルに be で済むようにエイリアスを設定しています)。
$ bundle exec ruby sample.rb
Cucumberをインストールする
Gemfile に Cucumber を追加します。
source "https://rubygems.org"
gem "appium_lib"
gem "cucumber"
インストールします。--path の指定は覚えていてくれるので、二回目からは指定する必要はありません。
$ bundle install
プロジェクト用のテスト環境を作成します。
$ bundle exec cucumber --init
こんなディレクトリ構造のものが出力されます。
features
├── step_definitions
└── support
└── env.rb
CucumberからAppiumを使ってみる
まずテストシナリオを実行する度に、実行前にアプリを起動して、実行後にアプリを終了する設定を行いたいです。Cucumber にはこのようなフック処理が書ける仕組みが用意されています。
Cucumber Hooks によると、support の中にある env.rb に書けばいいんですが、別に env.rb じゃなくてもここに置いたファイルは全部認識してくれるようです。
とりあえず features/support/env.rb に以下を書いてみます。
require 'appium_lib'
# 単に@@driverをグローバルアクセスさせたくないために用意したモジュール。
# もっと言うと単にCalabashの真似をしただけ。
module Launcher
@@driver = nil
def self.start
desired_caps = {
caps: {
platformName: 'iOS',
deviceName: 'username の iPhone',
udid: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
app: 'com.example.MyApp',
automationName: 'XCUITest',
xcodeOrgId: 'AAAAAAAAAA',
xcodeSigningId: 'iPhone Developer'
},
appium_lib: {
wait: 10
}
}
@@driver ||= Appium::Driver.new(desired_caps)
@@driver.start_driver # こいつの戻り値の型はSelenium::WebDriver::Driver
end
def self.quit
@@driver.driver_quit
end
def self.driver
@@driver
end
end
# Appium::Driver を appium という名前で取得できるようにする
def appium
Launcher.driver
end
Before do |senario|
Launcher.start
end
After do |senario|
Launcher.quit
end
Appium の API には Appium::Driver 経由でアクセスします。これを appium という名前で取得できるようにしています。
次に features/sample.feature ファイルに以下を記述します。feature ファイルは Gherkin 言語で記述します。書き方は Cucumber のフィーチャの文法 - Gherkin が参考になります。
# language: ja
フィーチャ: 起動
シナリオ: 起動してメイン画面が表示される
前提 アプリが実行中
ならば "./screenshots/test.png"というファイル名でスクリーンショットを撮る
で、ステップの定義ファイルを作成します。features/step_definitions/appium_steps.rb に以下を記述します。今回は screenshot しか使ってませんが、どんな API が用意されているかは、iOS 版で提供されている機能 を参照してください(これらが全て Appium::Driver のメソッドとして提供されているのか未確認)。
require 'appium_lib'
Given /^アプリ(?:が|を)?実行中$/ do
# 特にすることない
end
Then /^"([^\"]*)"というファイル名でスクリーンショットを撮る$/ do |filename|
appium.screenshot filename
end
正規表現で関数を書いてるような感じです。スクリーンショットを撮るステップ定義を見てください。() で囲まれたところにマッチした文字列が do の後の引数 (filename) として渡されます。
なお (?: ) にするとマッチしたものを引数に渡しません。ここでは単に複数の書き方を許容するようにしているだけです。
- アプリが実行中
- アプリを実行中
- アプリ実行中
のどれを書いても、この正規表現にマッチします。
書式は Ruby リファレンスマニュアル - 正規表現 を参照してください。
スクリーンショットの出力先ディレクトリを作成しておきます。
$ mkdir screenshots
じゃあ、実行してみましょう。
$ bundle exec cucumber
うまくテストがパスしました。ちゃんとスクリーンショットも撮れています。
まとめ
Appium + Appium Rubyクライアント + Cucumber でテストを書いて動かす環境を構築しました。あとはステップファイルを充実させて、テストを書いていくことになります。
ただしまだ WKWebView を扱えるかとか、キーボードで日本語入力ができるかとか、特定のシナリオ実行前にアプリを再インストールできるかとか、まだ試したいことがあります。それらは
Appium+CucumberでiOSの自動UIテストを日本語で書く(色々実験編)
に書きました。