Intelligent Technology's Technical Blog

株式会社インテリジェントテクノロジーの技術情報ブログです。

Appiumでモバイルアプリのテストを自動化【iOSネイティブアプリ編】

【追記2014/7/7】
記事中のAppiumのドキュメントへのリンク先は削除されてしまったようです。その代わりに公式サイトのドキュメントが整備されています。

【追記2014/7/16】
Appiumの新しいバージョン(1.2.0)に関する記事を書きました。

こんにちは、間藤です。

年跨ぎとなってしまいましたが、予告通りAppiumのテストを実際に試していきます。今回は、ネイティブアプリの自動テストについて、iOSシミュレータと実機で動作をさせてみたいと思います。

実行環境等

前回紹介したように、appiumリポジトリにはテストのサンプルが付属しています。テスト対象となるネイティブアプリも提供されており、それをテストするためのスクリプト(今回はJavaを利用します)も用意されています。
以下に示したパスは、リポジトリ内の相対パスになります。

ホストPC OS X Mavericks
Xcode 5.0.2
Appium 0.12.0
iOSシミュレータ 7.0.3
iOS実機 7.0.4
テスト対象アプリ sample-code/apps/TestApp
テストスクリプト sample-code/examples/java/junit/src/test/java/com/saucelabs/appium/SimpleTest.java

テスト対象となるアプリ

テキストフィールドが2つあり、それぞれに数字を入力して、「Compute Sum」ボタンを押下すると、足し算した結果が下に表示されるというものです。それ以外の機能もあるようですが、テストを理解するうえでは、とりあえずこの挙動だけ押さえておけばよいと思います。

f:id:IntelligentTechnology:20131217101618p:plain:h380

このアプリをテスト実施前にビルドしておく必要があります。reset.shを利用することができます。
※--devオプションを指定しなければならないようです。

> cd appium
> ./reset.sh --ios --dev

もし、このコマンドが正常に動かない(途中でエラーになってしまう)ようであれば、TestAppのビルドだけを行ってみてください。

> cd sample-code/apps/TestApp
> xcodebuild -configuration Release -sdk iphonesimulator7.0

テストスクリプト

TestAppのテスト用に、JUnitで書かれたテストが提供されていますので、これを利用します。各テストの実行前には、Driverクラスのインスタンスを生成します。

@Before
public void setUp() throws Exception {
    // set up appium
    File appDir = new File(System.getProperty("user.dir"),
                           "../../../apps/TestApp/build/Release-iphonesimulator");
    File app = new File(appDir, "TestApp.app");
    DesiredCapabilities capabilities = new DesiredCapabilities();
    capabilities.setCapability(CapabilityType.BROWSER_NAME, "");
    capabilities.setCapability(CapabilityType.VERSION, "6.0");
    capabilities.setCapability(CapabilityType.PLATFORM, "Mac");
    capabilities.setCapability("device", "iPhone Simulator");
    capabilities.setCapability("app", app.getAbsolutePath());
    driver = new SwipeableWebDriver(new URL("http://127.0.0.1:4723/wd/hub"),
                                    capabilities);
    values = new ArrayList<Integer>();
}

SwipeableWebDriverクラスは、インナークラスでorg.openqa.selenium.remote.RemoteWebDriverクラスを継承しています。Driverのコンストラクタには、AppiumのURLを渡します。また、Capabilitiesにテスト対象のアプリを指定しています。これには、先ほどビルドしたアプリのパスを指定しています。

SimpleTestクラスには、複数のテストが書かれていますが、最初のテストに注目してみます。

private void populate() {
    //populate text fields with two random number
    List<WebElement> elems = driver.findElements(By.tagName("textField"));
    Random random = new Random();
    for (WebElement elem : elems) {
        int rndNum = random.nextInt(MAXIMUM - MINIMUM + 1) + MINIMUM;
        elem.sendKeys(String.valueOf(rndNum));
        values.add(rndNum);
    }
}

@Test
public void testUIComputation() throws Exception {
    // populate text fields with values
    populate();
    // trigger computation by using the button
    WebElement button = driver.findElement(By.tagName("button"));
    button.click();
    // is sum equal ?
    WebElement texts = driver.findElement(By.tagName("staticText"));
    assertEquals(texts.getText(), String.valueOf(values.get(0) + values.get(1)));
}

2つのテキストフィールドへの入力は、populateメソッドで行っています。入力対象とするテキストフィールドは、findElementメソッドで取得しています。

List<WebElement> elems = driver.findElements(By.tagName("textField"));

取得した要素に対し、sendKeysメソッドで数字を入力しています。

for (WebElement elem : elems) {
    int rndNum = random.nextInt(MAXIMUM - MINIMUM + 1) + MINIMUM;
    elem.sendKeys(String.valueOf(rndNum));
    values.add(rndNum);
}

計算を実行させるために、ボタン要素を取得し、clickメソッドを呼び出します。

WebElement button = driver.findElement(By.tagName("button"));
button.click();

最後に計算結果が正しいことを検証します。

WebElement texts = driver.findElement(By.tagName("staticText"));
assertEquals(texts.getText(), String.valueOf(values.get(0) + values.get(1)));

シミュレータで実行

それでは、テストを実行してみます。
まず、Appiumサーバを起動します。起動すると、ポート4723でクライアントからの接続を待ち受けます。

> appium
info: Welcome to Appium v0.12.0 (REV a9d21807874190ae6d19f8251375cfe4da7b2ae5)
info: Appium REST http interface listener started on 0.0.0.0:4723
   info  - socket.io started

テスト実行には、Mavenを利用できます。以下は、testUIComputationだけを実行するようにしています。

> cd sample-code/examples/java/junit
> mvn test -Dtest=com.saucelabs.appium.SimpleTest#testUIComputation

必要な環境は、Mavenが揃えてくれます。Seleniumのドライバ(selenium-java-2.39.0.zip)もその中に含まれるため、Seleniumについては基本的に最新バージョンを利用することになります。
最初の起動時には、以下のようなダイアログが表示されます。

f:id:IntelligentTechnology:20131217130712p:plain

これが表示されるないようにするため、authorize_iosコマンドを利用できます。

> sudo authorize_ios

詳細は、以下をご参照ください。

iOSシミュレータは、最新バージョンで起動するようになっています。私の環境では、iOS7.0.3のシミュレータが起動します。例えば、iOS6.1でテストしたい場合、古いバージョンのXcodeに切り替える必要があるそうです。詳細は、以下の情報を参照してください。

実機で実行

まず、テスト対象アプリを実機にインストールしておきます。私は、Xcodeを使いました。シミュレータの場合は、Appiumがアプリのインストールまで行ってくれましたが、実機の場合はあらかじめインストールします。(これについては後で補足します)
Appiumサーバを起動する際、実機のUDIDをパラメータに指定します。

> appium -U (実機のUDID)

テスト実行は、先ほどと同じです。

> mvn test -Dtest=com.saucelabs.appium.SimpleTest#testUIComputation

なお、Capabilitiesのdeviceに"iPhone Simulator"を指定しているのですが、Appiumサーバの起動時に実機のUDIDを指定しておけば、実機でのテストが実行されるようです。

capabilities.setCapability("device", "iPhone Simulator");

(補足)実機へのアプリインストール
Appiumには、ipaファイルをインストールするための起動オプションが用意されているようなので試してみたのですが、うまく機能しませんでした。
※テストスクリプト側では、Capabilitiesにテスト対象のアプリを指定しないようにします。

> appium -U (実機のUDID)--ipa (ipaファイルのパス) --app io.appium.TestApp  

このように起動した上でテスト実行すると、ideviceinstallerを使ってアプリを実機にインストールしようとしますが、エラーになってしまいました。

{ [Error: Command failed: dyld: Library not loaded: @executable_path/../lib/libimobiledevice.3.dylib
  Referenced from: /usr/local/lib/node_modules/appium/build/libimobiledevice-macosx/ideviceinstaller
  Reason: image not found
] killed: false, code: null, signal: 'SIGTRAP' }

私の環境設定が何かしら間違っているのかもしれませんが、今回は深追いしていません。

キャプチャの取得

自動テストのシナリオの中で画面キャプチャを取得したいというニーズがあると思います。
キャプチャを行うには、TakesScreenshotインターフェースを利用します。 FirefoxDriverクラスなどは、このインターフェースを実装していますが、RemoteWebDriverはなぜか実装をしていません。そこで、Augmenter というクラスを使って、DriverのインスタンスにTakesScreenshot のメソッドを追加します。但し、サンプルのテストスクリプトのように、RemoteWebDriverを継承したクラスを利用すると、なぜかエラーとなってしまいます。原因はよくわかっていません。RemoteWebDriverを直接利用してもテストは実行できるので、Driverの生成部分を以下のように書き換えます。
※必要となるクラスはインポートしてください

//driver = new SwipeableWebDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
driver = new RemoteWebDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
Augmenter augmenter = new Augmenter();
driver = augmenter.augment(driver);

キャプチャ取得用のメソッドを追加します。

private void getScreenshotAs(String path) {
    TakesScreenshot ts = (TakesScreenshot) driver;
    try {
        FileUtils.copyFile(
                ts.getScreenshotAs(OutputType.FILE),
                new File(path));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

このメソッドを任意のタイミングで呼び出せば、キャプチャが取得できます。

public void testUIComputation() throws Exception {
・・・(テストを実行)・・・
    getScreenshotAs("capture.jpg");
}

シミュレータ、実機ともにこれでキャプチャを取得できました。

トラブルシューティング

最後に、動作確認をするうえでの注意点を補足しておきます。
iOSの自動テストでは、Appiumは内部でInstruments(Automation)を利用しています。テスト実行に失敗したときなどに、Instrumentsのプロセスが残ってしまうことがあるので、そういったプロセスはkillするようにしてください。

次回は、「ハイブリットアプリ編」を投稿します。