Appiumでモバイルアプリを自動化テストしてみた

 

お久しぶりです、テックコンサルのホセです。

2014年W杯!ドイツが優勝して幕を閉じましたね!今回のW杯で日本がグループリーグを突破できなく残念でした。。。自分はコロンビアと日本のハーフなのでよくどちらを応援するかと聞かれますが、関係なく勝っている方を応援していました!

さて今回紹介したいのはAppiumです!以前Selenium作者のJason Hugginsが来日された時にも少し紹介しました。

 

Appiumとは!?

名前の由来は「Selenium for Application」ということもあって、Seleniumでできることをモバイルアプリケーションにも実現しようということから来ています。何ができるかというとモバイルアプリ(NativeとHybrid)のテストを自動化できる便利なツールです。対応しているモバイルOSはAndroid、iOSとFireFoxOSです。

 

他の自動化ツール(MonkeyTalk, Calabashなど)もありますが....

Appiumの一番の特徴はテストするアプリに全く手を加えないところになります。他の自動化ツールを試しましたが、SDKをアプリに組み込む必要があったりするため、SDKの組み込みや取り外す手順でミスったり、SDKとの相性問題があったりなど、テストが明確にアプリのみではなくなってしまう問題がありました。AppiumではSDKを組み込む必要はなく、テストを自動化することが可能です!

ただSDKを組み込む自動化ツールにあるようなレコード&リプレイ機能は利用できなかったりします。

 

どうやって動いている?

Appium自体はHttpServerであり、iOSの場合はこのサーバーからInstrumentsを経由してクライアント(端末側)を操作しています。Androidでも同様な仕組みで動いています。

詳しい情報はAppiumの公式サイトまたはSlideShareで解説されています。

 

Appiumを使ってみる

今回はiOSメインでかんたんな利用方法を紹介します。まずAppiumを動かすために必要なnode.js (0.10以上)をインストールしましよう。Brewが入っている場合は以下のターミナルコマンドでnode.jsはインストール可能です。

$brew install node

Brewを利用していない場合はnode.jsからバイナリをダウンロードし、インストールすることも可能です。

次にGitHubからAppiumのソースコードサンプルをダウンロードし、任意のフォルダに配置しましょう。

そしてサイトからAppiumアプリをダウンロードし、インストールしたら、早速起動してみましょう。

設定の詳細ページはこちらです。

 

簡単なテストケースを作ってみましょう

事前にテストアプリ(WebViewApp)をXcodeでシミュレーター用にビルドをしていただく必要あります。こちらのアプリ(WebViewApp)は先ほどダウンロードしたAppiumのソースコードとサンプルに含まれています。

次に、テストするために本来、人が行うべきUIの操作を楽にコード化する方法を動画にてお見せします。

上記の動画ではAppium Inspectorを使っています。Appium Inspectorは操作したいコマンドを録画し、それを好きな言語(Java, Objective-C, C#, node.js, Ruby, Python)に表示してくれるツールです。

Appium Inspectorからnode.jsのコードを見てみると。。。

use strict";

var wd = require("wd"); // こちらがWebDriverです
var chai = require("chai");
var chaiAsPromised = require("chai-as-promised");

chai.use(chaiAsPromised);
chai.should();
chaiAsPromised.transferPromiseness = wd.transferPromiseness;

var desired = {
	"appium-version": "1.0",
	platformName: "iOS",
	platformVersion: "7.1",
	deviceName: "iPhone",
	app: "/appium/WebViewApp.app",
}; // アプリの各設定ができます

var browser = wd.promiseChainRemote("0.0.0.0", 4723); // AppiumのWebServerへのアクセス

browser.init(desired).then(function() {
	return browser
		.elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIATextField[1]").click()
		.elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIATextField[1]").sendKeys("http://google.com")
		.elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAButton[1]").click()
		.elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]").click()
		.elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]").sendKeys("appium")
		.elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]").click()
		.elementByName("Appium : Mobile App Automation Made Awesome.").click()
		.fin(function() {
			return browser.quit();
		});
}).done();

初期設定と操作部分の記載コードはありますが、テストアプリ(WebViewApp)を対象にテストコードを走らせるとFailします。このコードではロード時間を考慮しておらず、Web遷移又はアプリのロードを待たずに、次のコマンドを実行しようとして失敗します。

これを回避するためには「waitForElementByXPath」または「waitForElementByName」を利用します。

以下のように変更しています

上記のコードだけでは指定した操作のみが実行され、テストケースとしてはまだ不足しています。このためnode.jsでテストケースを作成するためにはMochaというテストフレームワークを利用します。nodeのモジュールであるため、 $ npm install -g mocha で簡単にインストール可能です。


さて、上記のコードをMochaのテストケースに置き換えてみますと

"use strict";

var wd = require("wd"); // こちらがWebDriverです
var chai = require("chai");
var chaiAsPromised = require("chai-as-promised");

chai.use(chaiAsPromised);
chai.should();
chaiAsPromised.transferPromiseness = wd.transferPromiseness;

describe("ios webview simple test", function () {
  this.timeout(300000);
  var driver;
  var allPassed = true;

  before(function () {
    driver = wd.promiseChainRemote("0.0.0.0", 4723); // AppiumのLocal WebServerへのアクセス
    
    var desired = {
    	"appium-version": "1.0",
    	platformName: "iOS",
    	platformVersion: "7.1",
    	deviceName: "iPhone",
    	app: "/appium/WebViewApp.app",
    }; // アプリの各設定ができます
  
    return driver.init(desired);
  }); // Driverの初期化設定

  after(function () {
    return driver
      .quit()
      .finally(function () {
        //必要であればログを表示する console.log("Test Passed? :" + allPassed.toString());
      });
  }); // Driverの停止とテスト結果

  afterEach(function () {
    allPassed = allPassed && this.currentTest.state === 'passed';
  }); //各テスト後の結果確認

  it("should reach appium homepage", function() {
    return driver
      .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIATextField[1]").click()
	    .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIATextField[1]").sendKeys("http://google.com")
		  .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAButton[1]").click()
		  .waitForElementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]").click()
      .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIATextField[1]").sendKeys("appium")
		  .elementByXPath("//UIAApplication[1]/UIAWindow[1]/UIAScrollView[1]/UIAWebView[1]/UIAButton[1]").click()
		  .waitForElementByName("Appium : Mobile App Automation Made Awesome.").click()
		  .context('WEBVIEW')
		  .sleep(2000)
      .title().should.eventually.include('Appium'); // タイトルにappiumが存在する場合はURLへの遷移に成功

  });

});

上記のテストケースのコードを説明します。

before(function () {
    driver = wd.promiseChainRemote("0.0.0.0", 4723); // AppiumのLocal WebServerへのアクセス
    
    var desired = {
        "appium-version": "1.0",
        platformName: "iOS",
        platformVersion: "7.1",
        deviceName: "iPhone",
        app: "/appium/WebViewApp.app",
    }; // アプリの各設定ができます

まずは「before」(テスト開始前)でWebDriverやAppiumの初期化設定を行います。

after(function () {
    return driver
      .quit()
      .finally(function () {
        //必要であればログを表示する console.log("Test Passed? :" + allPassed.toString());
      });
  }); // Driverの停止とテスト結果

「after」(テスト終了後)でWebDriverを終了させます。必要であればログの表示や他のコードの実行などすることも可能です。

afterEach(function () {
    allPassed = allPassed && this.currentTest.state === 'passed';
  }); //各テスト後の結果確認

「afterEach」(各テスト後)ではテストの結果を確認し、失敗の場合には「allPassed」を「false」とします。今回では「allPassed」は特に利用しませんが、他のテストと組み合わせる場合の成果通知として利用することは可能です。

「it」(各テスト)では、先ほどAppium Inspectorで得たコードを利用しています。上記のハイライトの部分が変更点になっており、変更箇所では遷移後のページタイトルに「Appium」などの文字列が含まれている場合にテストケースを成功とします。

 

node.jsでテストケースを走らせる

先ほどのnode.js用のjavascriptコードをローカルに保存し、Appiumを起動させましょう。

以下のターミナルコマンドで起動します。

$appium &

起動を確認できたら、保存したjavascriptコードを以下のコマンドで実行します。

$mocha [javascript名].js

自動的にiOSシミュレータが起動され、先ほど記載したコードが自動的に実行されていきます。テストはアッという間に終わります。

スクリプトを実行したターミナルを確認するとテストケースの結果(もちろん成功「passing」)が表示されています。

image2.png

エラーの場合では結果が「failing」になり、エラーなどがターミナルに吐き出されます。

iOSシミュレータの注意点: デフォルトではPC側のキーレイアウトに合わせるため、日本語だった場合には日本語で入力され、テストが失敗することがあります。これを避けるために、iOSシミュレータの設定で「Hardware」→ 「iOS Uses Same Keyboard Layout As OSX」のチェック外すことで回避できます。

今回は簡単なWebViewの遷移のみしか検証していませんが、この方法で自分のアプリを統合テストなどを自動化することは可能です。

 

Androidでは?

もちろんAppiumはAndroidには対応しており、iOS同様な機能を利用できます。ただ設定はiOSよりも複雑になっていますので詳細は設定ガイドを参照ください。

また既存のAndroidエミュレータでは速度が遅いため、IntelのHardware Accelerated Execution Managerを利用することをおすすめいたします。ガイドはこちら(Intel Hardware Accelerated Execution Manager)です。

(他にもGenymotionなどのエミュレータがあります)

 

簡単な紹介でしたが、アプリの統合テストを自動化するツールとしては非常にポテンシャルはあります、本記事がAppium利用の参考になればと思います。