AWS Device Farm を使ってモバイル向け Web アプリをクラウドでテストする #アドカレ2015

advent2015-05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWS Device Farm が Web アプリに対応

AWS Device Farm (以下 Device Farm)は、モバイルアプリをクラウドでテストするためのサービスです。AWS クラウド上で、実際のスマホやタブレットを使ったテストを実施することができます。iOS / Android / Fire OS のデバイス、様々な OS バージョンの中からテストしたいデバイスを自由に選択し、自分のアプリを簡単にテストすることができます。

2015年11月に、Device Farm が Web アプリをサポートしました。これまではアプリが対象でしたが、モバイル向けの Web アプリを対象にテストすることもできます。

モバイル向けの Web アプリの品質を向上するためには、モバイルアプリと同様、様々なデバイスや OS バージョンでテストする必要があります。デバイスの大きさも、ブラウザの種類も多種多様ですので、開発中のデバイスでは正常に動いていたのに、いざ公開してみると「あるデバイスでは表示が崩れてしまう」、「このブラウザではおかしな挙動をしてしまう」といったようなことがよくあります。

Device Farm を使うことで、手元に実機を用意する必要なく、様々なデバイスを対象に簡単にテストが行えます。Web アプリの品質向上には必須のサービスと言えるでしょう。

ということで、本記事が掲載されているブログ「Developers.IO」を Device Farm で簡単にテストしてみました。

Appium とは?

Device Farm で Web アプリをテストするには Appium を使います。

appium

Appium は、主にモバイルアプリを自動でテストのためのツールです。「Selenium のアプリ版」という位置付けのツールです。なお、Selenium は PC ブラウザを自動で操作して Web サイトをテストできるツールです。Selenium でできることがモバイルアプリを対象に行えるものとなっています。

Device Farm で Web アプリをテストは現在 Appium のみ対応していますので、Appium を使って書かれたテストコードが必要になります。と言っても、そこまで難しいものではありません。

Appium の動作環境を構築する

まずは、Appium の動作環境をローカルで構築しましょう。なお、Xcode または Android Studio は導入されている前提で進めます。これらが必要な理由は、Appium は Web アプリの自動操作を iOS シミュレータまたは Android エミュレータで行うためです。

Appium は Node.js 上で動作するツールのため、Node.js をインストールしましょう。今回は v0.12.8 を使用しました。

$ brew install node
$ node -v
v0.12.8

なお、Homebrew を導入されていない場合、上記コマンドは叩けません。その場合は Homebrew をインストールしましょう。

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

次に appium をインストールします。また、Web ページを操作するためのモジュール wd もインストールします。ios-webkit-debug-proxy は Web ページをデバッグするために必要なツールです。

$ npm install -g appium
$ npm install wd
$ brew install ios-webkit-debug-proxy
$ appium -v
1.4.16

次に appium-doctor コマンドを使って、環境設定が正常かどうかチェックします。エラーが出た場合は、どの環境変数がセットされていないか詳細に表示されます。その場合は設定を見直してください。なお、--ios--android を付けると、いずれかだけ確認することができます。

$ appium-doctor
Running iOS Checks
✔︎ Xcode is installed at /Applications/Xcode.app/Contents/Developer
✔︎ Xcode Command Line Tools are installed.
✔︎ DevToolsSecurity is enabled.
✔︎ The Authorization DB is set up properly.
✔︎ Node binary found at /usr/local/bin/node
✔︎ iOS Checks were successful.

Running Android Checks
✔︎ ANDROID_HOME is set to "/Applications/android-sdk-macosx"
✔︎ JAVA_HOME is set to "/usr/bin/java."
✔︎ ADB exists at /Applications/android-sdk-macosx/platform-tools/adb
✔︎ Android exists at /Applications/android-sdk-macosx/tools/android
✔︎ Emulator exists at /Applications/android-sdk-macosx/tools/emulator
✔︎ Android Checks were successful.

✔︎ All Checks were successful

以上で、Appium の導入は完了です。

テストコードを書く

次に、テストコードを書きます。

Device Farm で Web アプリをテストするためのフレームワークは Appium + JUnitAppium + TestNG が対応しています。今回は JUnit を使ってみます。サンプルコードは GitHub に公開していますので、ぜひ参照頂ければと思います。

なお、プロジェクトは Maven を使ってビルドを行います。Maven をインストールしていない場合、以下のコマンドを叩いてインストールを行っておきます。

$ brew install maven

プロジェクトには、最低限次の3つのファイルが必要になります。

  • pom.xml : プロジェクト設定の定義ファイル (依存関係のあるライブラリの定義など)
  • zip.xml : Zip ファイル形式に圧縮する条件を指定するファイル
  • WebAppTest.java : テストコード (ファイル名、クラス名は自由に設定可能)

フォルダ構成は次のようになります。src/test/java 以下はテストクラスのパッケージに依るので、適宜読み替えてください。

.
├── pom.xml
└── src
    ├── main
    │   └── assembly
    │       └── zip.xml
    └── test
        └── java
            └── jp
                └── classmethod
                    └── dev
                        └── WebAppTest.java

まず pom.xmlzip.xml を用意しましょう。pom.xml には Appium による自動テストに必要なライブラリなどの定義を書きます。 zip.xml には JAR ファイルを対象に Zip 圧縮する定義を書きます。これらのファイルはちょっと長いので割愛します。詳細は以下に公開してあるファイルを参照してください。

次に、テストコードを書きます。以下のようなプログラムがミニマムな構成かと思います。

package jp.classmethod.dev.test;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.remote.DesiredCapabilities;
import io.appium.java_client.ios.IOSDriver;

import java.net.URL;
import java.io.File;

import static org.junit.Assert.*;

public class WebAppTest {

    private WebDriver driver;

    @Before
    public void setUp() throws Exception {
        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setCapability("deviceName", "iPhone 6");
        capabilities.setCapability("platformName", "iOS");
        capabilities.setCapability("platformVersion", "9.1");
        capabilities.setCapability("browserName", "safari");
        driver = new IOSDriver<WebElement>(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
    }

    @Test
    public void runTest() throws Exception {
        // Web サイトにアクセス
        driver.get("http://dev.classmethod.jp");
        // 1秒待機
        Thread.sleep(1000);
        // class が "logo" のエレメントを探す 
        WebElement idElement = driver.findElement(By.className("logo"));
        // 取得できたかチェックする
        assertNotNull(idElement);
        // "Developers.IO" になっているかチェックする
        assertEquals("Developers.IO", idElement.getText());
        // スクリーンショットを撮る
        takeScreenshot("Top Page");
    }

    public boolean takeScreenshot(final String name) {
       String screenshotDirectory = System.getProperty("appium.screenshots.dir", System.getProperty("java.io.tmpdir", ""));
       File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
       return screenshot.renameTo(new File(screenshotDirectory, String.format("%s.png", name)));
    }

    @After
    public void tearDown() throws Exception {
        driver.quit();
    }
}

解説します。まずメンバーとして定義している WebDriver は、シミュレータ(またはエミュレータ) を操作するためのインターフェースです。このインターフェースに定義されているメソッドを使うと、Web ページにアクセスしたり、エレメントを取得したりすることができます。

setUp() では、テストを実施する前の準備を行います。このメソッドの最後の行で driver に IOSDriver を代入していますが、これは iOS シミュレータを対象とする場合の設定です。Device Farm で動作する上では iOS と Android のいずれも対象とすることができますが、ここの設定は良しなにやってくれているようです(おそらく) *1

runTest() が、実際のテスト内容になります。今回は、Developers.IO にアクセスし、class が logo のエレメントを取ってきて、そのテキストが「Developers.IO」であるかどうかをテストしています。対象のエレメントは以下のような感じです。

<a class="logo" 
   href="http://dev.classmethod.jp/" 
   title="AWS/iOS技術者の必読メディア:クラスメソッド株式会社ブログ" 
   rel="home">Developers.IO</a>

最後の行ではスクリーンショットの撮影処理を行っています。TakeScreenshot はスクリーンショットを撮影するためのクラスです。appium.screenshots.dir というプロパティにアクセスすると、スクリーンショットの保存場所が取得できます。

ここまで出来たら、テストをローカルで試しつつ Zip 形式にまとめましょう。まず Appium を起動します。--no-reset はテスト終了後にシミュレータ(エミュレータ)を初期化しないためのオプションです。

$ appium --no-reset &

次に、プロジェクトのルート (pom.xml が置いてあるディレクトリ) で以下のコマンドを実行します。

$ mvn clean package

iOS シミュレータが自動で起動し、テストが実行されると思います。なお、テストの実行をスキップしたい場合は -DskipTests=true オプションを付与します。なお、このオプションを付けてパッケージングした場合は Appium は使われません *2

$ mvn clean package -DskipTests=true

テストが終わると、必要なファイル一式を Zip 形式に圧縮する処理が行われます。すべての処理が終わると targets ディレクトリが生成され、その中に zip-with-dependencies.zip というファイルが生成されます。このファイルを Device Farm にアップロードすることになります。

appium-archive

Device Farm でテストを実施する

いよいよ Device Farm の登場です。先ほど作成した zip-with-dependencies.zip をアップロードし、クラウド上でテストを実施しましょう。

まずプロジェクトを作成し、新規の Run を作ります。Run name は「Developers.IO」としました。

device-farm-web-app-01

次にテスティングフレームワークを選択します。今回は「Appium Java JUnit」を選択し、「Upload」をクリックし、zip-with-dependencies.zip をアップロードします。

device-farm-web-app-02

次にデバイスの選択です。今回は、iOS と Android、デバイスが1台ずつ含まれる Device Pool を作成しました。計2台のデバイスを対象となっています。

device-farm-web-app-03

最後は全設定の確認画面です。「Confirm and start run」をクリックし、いよいよテストの実行です。

device-farm-web-app-04

テストが成功すると、緑色でわかりやすく表示されます。

device-farm-web-app-05

スクリーンショットもバッチリ撮れていますね *3

device-farm-web-app-06

まとめ

Appium の知識が若干必要ですが、手順通り進めれば良いのでそこまでハードルは高くありません。JUnit のテストコードを書きましたが、対象のエレメントが期待した結果になっているか確認するだけであればそこまで手間にならないと思います。また、Selenium をすでに導入されているのであれば、テストコードはほとんど流用できるでしょう。ぜひ、利用してみてください。

明日(12月6日)は都元ダイスケの Amazon SNS に関する記事です。お楽しみに!

参考情報

本記事の内容を調査するにあたって、参考になった記事をまとめました。私が Appium を触ったこと自体なかったので、Device Farm に直接的に関係のない記事も含まれています。

概要

Appium の使い方

Selenium の使い方

Device Farm の使い方

サンプルコード

脚注

  1. 詳細な情報が見つけられませんでした。お分かりの方はコメントください!
  2. ローカルでテストする必要がない場合は Appium をインストールする必要はありません。
  3. タイトル以外のフォントをクリスマス限定で変更しているのですが、読み込みが遅かったため表示されていませんね…。勉強になります!!