DeNA Testing Blog

Make Testing Fun, Smart, and Delighting End-Users

Bluepillを導入してiOSのUIテスト実行を並列化する

はじめまして、SWETグループの細沼(@tobi462)です。

9月から10月にかけて iOSDC 2017 や、それに関連した勉強会(リジェクトコン)などが開催され、iOS開発者にとってはホットな時期だったかと思います。私自身もiOSDC 2017ではライトニングトーク、俺コン Vol.1 / Day. 2 で15分発表をさせていただき、発表者・参加者の両面からこれらのイベントを楽しめたと思っています。

さて今回は、iOSDCでのLT発表の中で触れさせていただいたBluepillについて、実際の使い方などを掘り下げて紹介したいと思います。

サンプルコード

実際に動作するサンプルコードも用意しておきましたので、必要に応じてご利用ください。
https://github.com/YusukeHosonuma/iOS-Bluepill-Sample

Homebrewがインストールされていれば、READMEに書かれた手順に従って、実行するところまで行えるかと思います。

なおBluepillの動作確認サンプルであるため、アプリの内容に特に意味はありません。

Bluepillとは

Bluepill は、iOSアプリのUIテスト実行について、複数シミュレータを同時起動して並列で実行するツールです。

f:id:swet-blog:20171113143332p:plain

並列でテストが実行されるため、UIテストの実行時間を短縮できるというメリットがあります。また、テストは自動的に分割(グルーピング)されるため、開発者が自分でテストの分割を意識する必要がないのも便利な点といえます。

LinkedInが開発したツールで、GitHubリポジトリのREADMEによると巨大なテストスイートを妥当な実行時間で終わらせるために開発した、と書かれています。

LinkedIn created Bluepill to run its large iOS test suite in a reasonable amount of time.

また、以下のツールにインスパイアされて開発したとも書かれています。

  • parallel iOS test(pxctest)
    • 複数のOSバージョン・端末でテストを並列実行するツールで、端末カバレッジの網羅を目的としています。
  • Facebook製ツール

本記事では触れませんが、興味のある方は上記のツールも調べてみても、面白いかもしれません。

動作確認環境

以下の環境で確認を行っています。

  • Xcode 9.0(※)
  • macOS Sierra (10.12.6)
  • Fastlane 2.62.1
  • bluepill 2.0.2

ちなみにXcode 9.0にて公式で複数シミュレータが起動できるようになりましたが、それ以前のXcode 8.xの時点からBluepillは複数シミュレータ起動を実現していました。そのあたりからLinkedInのUIテストの実行時間短縮に対する本気度が伺えます。

なおREADMEにも書かれていますが、Xcode 8.xで利用したい場合は、最新バージョン(2017/11/13時点)の 2.0.2 ではなく、1.1.2 を利用する必要があるので注意しましょう。

以降、アプリやスキームの名称は xxx などに置き換えて記載していきますので、必要に応じて自身の環境に読み替えてください。

※記事執筆時点の最新バージョンは9.1ですが、Bluepillは対応中だったので9.0を利用して確認を行っています

インストール

GitHubリポジトリからダウンロード

GitHubリポジトリのリリースページでバイナリが公開されているので、そこからダウンロードしてPATHの通った適当なディレクトリ(/usr/local/bin/ など)に配置します。

解凍すると bluepillbp という2つのバイナリが格納されていますが、両方とも必須なのでご留意ください。

$ bluepill --version
Bluepill v2.0.2

Homebrewでのインストール

READMEにも書かれていますが、Homebrewでもインストールが可能なので、こちらを利用するとより手軽にインストールできます。

$ brew install bluepill
$ bluepill --version
Bluepill

ただし、(2017/11/13時点では)上記のとおりバージョン番号の出力がされないという事象があります。これはIssueにも報告が上がっていますが、しばらく動きがないようなので、気になる方はGitHubのリリースページからダウンロードした方が確実かもしれません。

設定から実行まで

以下の2段階の手順で実行します。

  1. テスト用のビルド(test-for-building)
  2. 実行(bluepill)

テスト用のビルド(test-for-building)

まずは xcodebuild コマンドの build-for-testing を利用して、テスト実行用のアプリをビルドします。

$ xcodebuild build-for-testing \
    -scheme xxxUITests \
    -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' \
    -derivedDataPath ./build
...

** TEST BUILD SUCCEEDED **

build-for-testing は、Xcode 8から導入された機能で、ビルドとテスト実行を分離できる仕組みです。通常は build-for-testing でテスト用にビルドしたアプリを test-without-building を利用してテスト実行を行います。

実行(Bluepill)

bluepill コマンドを使って実行します。

READMEには多くのオプションが記載 されていますが、必須なのは以下の2つのみです。

  • --xctestrun-path
    • .xctestrun ファイルへのパスを指定
  • --output-dir
    • 実行結果の出力先パスを指定
$ bluepill \
    --xctestrun-path \
    build/Build/Products/xxxUITests_iphonesimulator11.0-x86_64.xctestrun \
    --output-dir \
    bluepill_output

上記コマンドを実行すると、以下のようなログが出力され、デフォルト値である 並列数=4 でUIテスト実行が行われます。

Bluepill runtime version and compile time version are matched: 9.0 (9A235)
{36562} 20171029.150441 [  INFO  ] Using xctestrun configuration
{36562} 20171029.150441 [  INFO  ] This is Bluepill
{36562} 20171029.150441 [  INFO  ] Running with 4 simulators.
{36562} 20171029.150441 [  INFO  ] Packed tests into 5 bundles
{36562} 20171029.150441 [  INFO  ] Started Simulator 1 (PID 36582).
{36582} 20171029.150441 [  INFO  ] (BP-1) Running Tests. Attempt Number 1.
{36582} 20171029.150441 [  INFO  ] (BP-1) [Attempt 1] Create Simulator
{36562} 20171029.150442 [  INFO  ] 1 Simulator still running. [1]
{36562} 20171029.150442 [  INFO  ] Using 357 of 1064 processes.
{36562} 20171029.150442 [  INFO  ] Started Simulator 2 (PID 36585).
{36582} 20171029.150442 [  INFO  ] (BP-1) Booting a simulator without launching Simulator app
{36585} 20171029.150442 [  INFO  ] (BP-2) Running Tests. Attempt Number 1.
...
{36562} 20171029.150813 [  INFO  ] PID 37244 exited 0.
{36562} 20171029.150814 [  INFO  ] All simulators have finished.

またコマンドラインでオプションを指定する他にも、JSONで設定ファイルを用意する方法もあります。なお、JSONで指定するキー名では-oなどの省略系は指定できませんので注意しましょう。

{
  "xctestrun-path": "build/Build/Products/xxxUITests_iphonesimulator11.0-x86_64.xctestrun",
  "output-dir": "bluepill_output"
}

--config オプションで作成したJSONファイルを指定して実行できます。

$ bluepill --config config.json

出力結果

先ほど出力先として指定した bluepill_output フォルダの中身を見ると以下のようになっています。

bluepill_output
├── 1
│   ├── 1-xxx-results.txt
│   ├── 1-xxx-timings.json
│   ├── 1-simulator.log
│   ├── TEST-xxx-results.xml
│   └── xxx-stats.txt
├── 2
│   ├── 1-xxx-results.txt
│   ├── 1-xxx-timings.json
│   ├── 1-simulator.log
│   ├── TEST-xxx-results.xml
│   └── xxx-stats.txt
├── 3
...
└── TEST-FinalReport.xml

TEST-FinalReport.xml がJUnit形式の結果XMLファイルとなっています。Jenkinsのプラグインなど、JUnit形式をサポートしているツールで読み込ませ、見やすい形で表示することも可能です。

1から始まる数字のフォルダには、Bluepillにより自動的に分割された、各グループでのテスト結果やログなどが格納されています。あまり確認するケースは多くないかもしれませんが、覚えておくと何かあった時に調査がしやすいでしょう。

どれだけ実行時間を短縮できるか?

冒頭のスライドからの抜粋になりますが、UIテストケース数が30あるiOSアプリに対して実行したところ、それぞれの実行時間は以下のようになりました(10回施行した平均値です)

実行環境:Mac Pro (Late 2013) 3.5 GHz 6コア / 16GB

方法 実行にかかった時間 通常のテスト実行に対する比率(%)
通常 14:53 100%
Bluepill(並列数3) 9:37 65%
Bluepill(並列数4) 8:27 57%

通常のテスト実行に比べて半分以下とまではいきませんが、かなり実行時間を短縮できることが分かるかと思います。

.xctestrunの中身

ところで build-for-testing により生成され、Bluepillの実行時に指定していた .xctestrun とは何者なのでしょうか?

ファイルの実体は plist 形式になっており、XML形式で出力されています。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>xxxx</key>
  <dict>
    <key>UITargetAppPath</key>
    <string>__TESTROOT__/Debug-iphonesimulator/xxx.app</string>
    <key>TestHostPath</key>
    <string>__TESTROOT__/Debug-iphonesimulator/xxxUITests-Runner.app</string>
    <key>TestBundlePath</key>
    <string>__TESTHOST__/PlugIns/xxxUITests.xctest</string>

上記は重要と思われる部分のみを抜粋したものですが、箇条書きに直すと以下のようになります。

  • UITargetAppPath
    • __TESTROOT__/Debug-iphonesimulator/xxx.app
  • TestHostPath
    • __TESTROOT__/Debug-iphonesimulator/xxxUITests-Runner.app
  • TestBundlePath
    • __TESTHOST__/PlugIns/xxxUITests.xctest

上から順に、それぞれ「テストターゲット」「テストランナー(ホストApp)」「テストバンドル」のパスとなっています。つまり .xctestrun には、テスト実行に必要な各ファイルへのパスが記録されているということになります。

この手のツールは実際に試してみると、思わぬところでエラーが発生して利用する気を失ってしまうということがありがちですが、こういった部分を理解しておくとそうした状況で早く解決できるかもしれません。

実行オプションについて

前述したとおり、Bluepillには 多数の実行オプション が用意されています。

ここでは比較的、重要と思われるオプションを紹介します。

必須オプション

  • xctestrun-path
    • .xctestrunのパス
  • output-dir
    • ログの出力先

任意オプション

  • config
    • JSONで記述された設定ファイルへのパス
  • device
    • 実行するデバイス(デフォルト:iPhone 6
  • headless
    • ヘッドレスモードを有効にするか(デフォルト:off
    • true(※)を指定することで、シミュレータが表示されないので省メモリに繋がります
    • CI環境などでは、他Jobとの競合を避ける意味でも有効にしておくと良いでしょう
  • num-sims
    • シミュレータの並列実行数(デフォルト:4
    • Mac Pro(Late 2013)やMacBook Pro(Mid 2015)で試した限りでは、デフォルト値の 4 が安定して良い結果が得られました
    • 必要であれば、マシンスペックに応じて変更すると良いでしょう
  • screenshots-directory
    • 失敗したテストのスクリーンショットを出力するディレクトリ(デフォルト:指定なし)
    • 原因の調査がしやすくなるので、指定しておいたほうが良いでしょう
    • 相対パスで指定する場合は ../ を使用すると正しく解釈されないので注意が必要です

※README上では、デフォルト値が off と記載されているオプションがありますが true/false で指定します。

Fastlaneへの組み込み

ここまで見てきたように、Bluepillはかなり手軽に使い始めることができます。一歩進んで、Fastlaneから利用できるようにすると、さらに便利になります。

Fastlaneについての説明やインストール方法についてはここでは触れませんので、必要に応じて 公式ドキュメント などをご参照ください。

テスト用アプリのビルド(build_for_testing)

scan アクションを利用し、build_for_testingtrue を指定することで行います。

desc 'テスト実行用にビルド'
lane :build_for_testing do
  scan(
    build_for_testing: true,
    scheme: 'xxxUITests',
    destination: 'platform=iOS Simulator,name=iPhone 6,OS=latest',
    derived_data_path: './build'      
  )
end

Bluepillを用いたテスト実行(test_with_bluepill)

sh アクションを利用して、shellコマンドを前述のコマンドをそのまま実行します。

desc 'Bluepill を用いたUIテスト実行'
lane :test_with_bluepill do
  sh('bluepill -c ../config.json')
end

設定ファイルの内容は基本的に同じなのですが、パスの頭に ../ を追加している点に注意しましょう。

{
  "xctestrun-path": "../build/Build/Products/xxxUITests_iphonesimulator11.0-x86_64.xctestrun",
  "output-dir": "../bluepill_output",
  "headless": true,
  "screenshots-directory": "bluepill_screenshots"
}

これはFastlaneの実行が fastlane ディレクトリで行われるため、1つ上のディレクトリを参照しなければならないためです。ただし、前述したように screenshots-directory オプションだけは ../ を使用すると正しく解釈されないためそのままにしています。

Fastlaneプラグイン

今回は、JSONの設定ファイルを用いるアプローチを取ったのでshell実行で済ませてしまいましたが、GitHub上にいくつかBluepill用のFastlaneプラグインも公開されています。

もし要件に合うものがあれば、そちらを利用するのも手かと思います。

ただし、READMEに書かれた全てのオプションがサポートされているプラグインは無さそうでした。プラグインを利用するときには、そうした制限があることを理解した上で利用しましょう(個人的には設定ファイルに集約したほうが、メンテナンス性を上げられるかと思います)

レーンの実行

それでは用意した2つのレーンを利用して、ビルド・テストを実行してみましょう。

$ fastlane build_for_testing
...
[xx:xx:xx]: fastlane.tools finished successfully 🎉

$ fastlane test_with_bluepill
...
[xx:xx:xx]: fastlane.tools finished successfully 🎉

両方のコマンドで、Fastlaneから successfully のメッセージが出力されれば成功です。

Jenkins上で実行する

ここまで整備できていれば、Jenkins上で実行することも簡単です。

ここでは実行とテスト結果の集計方法のジョブ設定だけ紹介します(Jenkinsの基本的な使い方や設定については触れませんので、必要に応じて書籍やWeb記事をご参照ください)

ビルド・テスト実行

ビルド > ビルド手順の追加から「シェルの実行」を追加し、fastlaneコマンドを利用して先ほど用意したlaneを呼び出すようにします。

fastlane build_for_testing
fastlane test_with_bluepill

f:id:swet-blog:20171101134312p:plain

テスト結果の集計

ビルド後の処理 > ビルド後の処理の追加から「成果物を保存」と「JUnitテスト結果の集計」を追加し、bluepill_output/*.xml といったように指定します。

f:id:swet-blog:20171101134353p:plain

テスト結果の確認

正常にビルドが成功すると、ビルドの結果画面などに「最新のテスト結果」がリンク表示され、その遷移先から詳細なテスト結果を確認することが出来ます。

f:id:swet-blog:20171101134405p:plain

それぞれのテストにかかった時間も確認できるので、設定しておくと便利かと思います。

まとめ

今回は「Bluepill」についての紹介と、実際に利用するにあたってのインストール手順からFastlaneへの組み込み、Jenkinsへの設定まで紹介しました。

この手のツールは実際に利用し始めるまでに予期せぬトラブルに遭遇して、面倒になってしまって結局使わないというケースもあると思い、分かりやすい記事を心がけてみたのですがいかがだったでしょうか。

また、次回のブログ記事もお楽しみください。

UIテストの最前線: SeleniumConf Berlin 2017参加レポート

SWETグループの薦田です(@toshiya_komoda)。10月9、10日にドイツ・ベルリンで開催されたSelenium Conferenceに参加してきました。私もLightning Talksで、機械学習とUIテストに関する発表をさせていただきました(スライド)。

こちらの内容については、別の記事で書かせていただくこととし、この記事ではカンファレンスで聴講し、特に気になったトークについてレポートさせていただこうと思います。

ウェブ・アプリケーションにおけるUIテストの最前線の状況が少しでも伝われば幸いです。

f:id:swet-blog:20171018220430j:plain

Seleniumについておさらい

Selenium WebDriverは、ウェブ・ブラウザをプログラムから操作するためのソフトウェアツールです。OSSとして開発されており、主な用途としてウェブ・アプリケーションのテスト自動化に用いられています。現バージョンは3.6.0(2017年10月24日現在)、プロジェクト開始から10年以上の歴史を持ちます。

Selenium内部ではブラウザを操作するためにWebDriverと呼ばれるAPIが用いられています。主要なブラウザベンダが、自社ブラウザ向けのWebDriverの実装を提供しています(Official Selenium Blog: Selenium 3.0: Out Now)。 Seleniumはブラウザベンダが公式に認めているブラウザ・テストツールと言って言い過ぎではないでしょう。また、W3Cにて仕様標準化の作業が進められています(WebDriverのCR)。

Selenium Conference

Selenium Conferenceはプロジェクト公式のミートアップイベントで、プロジェクトのリーダーであるステアリング・コミッティも参加するイベントです。7年の歴史があり、2011年から2015年は年に1度、2016年からは年に2度ずつ開催されています。 前回のカンファレンスは、2017年4月にアメリカ オースティンで開催されており、SWETでも参加レポートを公開しています。 また、過去のトークがYouTube上で視聴できるようになっているので、興味のある方は眺めてみると雰囲気が伝わるかと思います。 今回のトークもすでにアップロードされています。

それでは、特に気になったトークを3つ紹介させていただきます。

Selenium State of the Union, To Infinity and Beyond Selenium

ステアリング・コミッティの一人であるSimon Stewart氏による基調講演です。 基調講演の中で、Seleniumが普及しプロダクトが成熟していく中で現在のSeleniumに求められているものは、 より分かりやすいドキュメンテーションと分かりやすいAPIだ、と語りました。 これらを実現するために、現役でテストを書いているエンジニアにこそ、OSSであるSeleniumにコントリビュートしてほしいとのことでした。

また気になる話題として、Firefoxのバージョンアップに伴い2017年8月にサポートが切れてしまった Selenium IDEの動向についても言及がありました。 現在、applitoolsの開発者が中心となって、Selenium IDEを公式に作り直しているとのことで期待して待っていて欲しいとのことでした(開発中のSelenium IDEのレポジトリ)。

Keep Your Test Lean

GithubのQAエンジニアによるUIテストにおけるベストプラクティスの話です。 UIテスト自動化が効果的に機能するための要件として、

  • Valuable: 重要で実行頻度の高いテストケースが自動化されている
  • Reliable: UIテストの実行結果を安定させるための仕組みが備わっている
  • Fast: テスト実行時間が短い時間に収まっている

という3点を挙げ、UIテストスイートがこれらを満たすために必要な戦略と工夫について説明されています。

UIテストをやみくもに作成してしまった結果、メンテナンスコストばかり大きくなりメリットが感じられなくなった、という話は日本でもよく聞く話です。 SWETでもこれまでにUIテストを運用していく中で、全く同じような運用上の課題を経験しています(資料)。

特にUIテストをテスト担当者に属人化させず、システム開発者から目に見えるものとするための工夫が興味深いものでした。具体的には、

  • UIテストはシステムの開発言語と同じ言語で記述する
  • テスト対象システムと同じコードベースで管理する
  • テストコードベースの運用ルールをテスト対象システムと同じにする
  • テスト対象UIパーツにカスタムデータ属性を埋め込んでおく

といった工夫が紹介されていました。最後のテスト対象のUIパーツにカスタムデータ属性を埋め込むとは、例えば以下のHTMLにおけるdata-qaがこれに相当します。 このタグがついたUIパーツは、テストで利用されているということが開発者にひと目で分かります。これにより、意図せずテストを壊す変更が入ってしまうことを防止できるというわけです。

# コードは動画中のスライドより引用
<l-popup as="popup" class="has-menu tall-option has-icon cap-option">
  <ul class="popup-menu narrow-menu" data-qa='add-candidate-menu'>
    <li class="option">
      <a on-click="addManually()">
        <view is="icon-edit"></view>
        Add manually
      </a>
    </li>

また、非同期処理が原因でテストが落ちた場合の再実行の仕組みを整える、並列化やヘッドレスブラウザを利用して高速化を行う、といったテスト実行環境の足回りの改善の重要性にも触れています。

UIテスト導入を検討している方に、ぜひ一度視聴してほしい内容でした。

Readable. Stable. Maintainable. E2E Testing @ Facebook

Facebookでテストフレームワーク・テストインフラを担当しているエンジニアの発表です。

Seleniumをそのまま用いたUIテストコードの可読性が低いことは、よく指摘されるSeleniumの問題点ですが、Facebookの中で開発しているテストフレームワークがこの問題をいかに解決しているかについて、具体例を用いて説明しています。

f:id:swet-blog:20171020201722p:plain (写真は動画中より抜粋)

例えば、上記のようなフィード欄のUIテストを考えてみましょう。以下の例では、 Seleniumをそのまま使って書かれたコードが、可読性の低いテストコードになる様子を分かりやすく説明しています。

# コードは動画中のスライドより引用

var feed
// feedがロードされるのが非同期なので、これに対応するためのwhileループ
while (feed == nil) {
  feed = try? driver.findElement(by: id("feed"))
}

// id を直接指定したWebElementの取得
let story = element.findElementBy(by: id("permalink"))[2]

// たまに一回目のクリックがうまくいかないのでretryしている
story.click()
story.click()

// 理由は分からないが、sleepを追加するとテストが動作するので入れられたsleep
sleep(5000)

// id を直接したUIパーツのセレクト
let postText = driver.findElement(by: id("post_text"))

コメントに記載している通り、UIテストにまつわるバッドノウハウがそこかしこに登場しており、本質的にテストしたい内容が、表現されている実装とは言えないものとなってしまっています。 FacebookにおけるUIテストフレームワークの開発の動機の一つは、この問題を解決し、可読性が高くメンテナンスが容易なUIテストコードを実現することだったそうです。

FacebookのUIテストフレームワークにおける問題解決のポイントは、テスト対象画面上のUIパーツの構造とそれに紐づくアクションをクラスとして定義する仕組みを提供していることです。

例えば、前述のフィードのUIは以下のようなクラスとして定義されると説明されています。

# コードは動画中のスライドより引用

// フィードに対するUI定義クラス。
// 子要素と、それに対するアクションを定義していく
class FeedStory: Component {
  let definition = 
           root(storyLocator, "story")
           .withDescendant(
             profilePhotoLocator,
             "profile photo"
           ) // 画像アイコン
           .withDescendant(
             permalinkLocator,
             "permalink"
           ) // その下にフィード詳細へのパーマリンク
           .withDescendant(
             contentLocator,
             "post content"
           ) // その下に投稿内容
           .withDescendant(
              StoryFooter
           ) // その下にフッタ
             // 下部で定義されたStoryFooterクラスを使いまわすことができる

  // UIパーツ内の要素を取得する関数
  getPostText() -> String {
    return getTextOf("post content")
  }

  // UIパーツに対するアクションを定義した関数。
  // 返り値はPageObject Patternの考え方に従い、UIオブジェクトが返される
  openProfile() -> UserProfile {
    return click("profile photo", andGoTo: UserProfile)
  }
  ...
}

// Like, Commentを含むフッターの定義。StoryFeedクラスと同様にUIを定義していく
class StoryFooter {
  // Like, Commentを含むFooterコンポーネントのUI定義クラス
}

このようなUIパーツのクラスを利用することで、 例えば「フィードを投稿して、この投稿したフィードの詳細ページを開き、これにコメントを付ける」といった複雑なシナリオテストも、以下のように簡略に書けるようになるとのことです。

waitFor(LoginScreen)
  .loginAs(user)
  .selectComposer()
  .open()
  .enterText("Test Post")
  .post()
  .selectStoryWithText("Test Post")
  .openPermalink()
  .postComment("Test Comment")

冒頭のバッドノウハウだらけのテストコードと比較して、テストコードの可読性が大きく向上していることが伺えます。

はじめにテスト対象のUIの仕様をクラスとして定義してしまうことで、テストロジックのメンテナンスコストを下げるというアイディアは、UIテストのデザインパターンとしてよく知られたPageObjectパターンの考え方をフレームワークとして一歩推し進めたかたちとなっており、興味深いアプローチだと感じました。

また、トークの中ではテストコードの可読性に関する話題だけでなく、One Worldと呼ばれるモバイルテスト基盤の話など、UIテストに関する多くの技術トピックに触れています。

UIテストの技術的な側面に興味のある方には是非視聴していただきたい内容でした。

終わりに

講演以外で印象に残っていることは、ZaleniumというOSSソフトウェアの開発者と直接会って話ができたことです。 Zaleniumは、Seleniumを用いた複数のUIテストを並列実行することができるテスト基盤を提供するソフトウェアです。 SWETでもZaleniumを利用しており、機能拡張のPRを送っていました。 普段は会えないOSS開発者と直接会って話ができることは海外カンファレンス参加の一つの魅力だと感じました。

Selenium Conferenceは年2回、来年はインドとアメリカでの開催です。 来年4月には第1回目のAppium Conferenceも開催されます。 DeNA Testing Blogでは今後もテスト自動化の最前線の動向について、継続して情報発信していく予定です。

参考資料

Android Test Night #1 開催

はじめまして、SWETの金子(theoden9014)です。

今回のエントリーは2017-09-21に第一回目としてSWET主催で開催したAndroid Test Night #1と、そこで「Android SDK with Docker」という題で私が発表した内容についてご紹介したいと思います。

Test Night とは?

Test Nightとは、SWETが主催する主にテストに関係することについて語り合うことを目的とした勉強会です。

昨年11月からiOS Test Nightと呼ばれるiOSに特化したTest Nightを2ヶ月ごとに開催しています。 これまで合計39件の発表がおこなわれ、多数の方に参加していただき、活発な議論がおこなわれる勉強会になっています。

Android Test Night とは?

iOS Test Nightと同様にTest Nightで管理しているイベントです。 今回はAndroidについても同様にテストを対象としたイベントの需要があることがわかり、開催する運びとなりました。

Android Test Night #1

今回はその第1回目です。
15分枠のセッションでは

  • 「コードレビューをより良くする Danger x Android」
  • 「Androidのテストを効率的にするために考えたこと」
  • 「Android E2E Testing at Mercari」

といった、開発プロセス改善についてのセッションや、実際にテストに取り組んでいる事例紹介のセッション。
5分枠のセッションでは

  • 「AndroidSDK with Docker」
  • 「JUnit5とAndroidのテスト」
  • 「Kotlinで書かれたAndroidアプリをBazelでビルドする」
  • 「Android CIをBitriseに移行して開発者・QAが幸せになったこと」
  • 「Clean Architecture & TDD」

といった、CI、テストツールやテスト手法についてのセッション。 といった、様々な内容のセッションがありました。

当日の資料に関してはこちらにアップロードされているので詳細が気になる方はご覧ください。

f:id:swet-blog:20170926194045j:plain Android Test Night #1 登壇時の様子

Application Build with Docker

ここからは私が発表した「Android SDK with Docker」に話を移したいと思います。

あるアプリケーションをCIに載せようとした際、CIでアプリケーションのビルドを可能にする必要があります。

CI毎にビルドに関する設定方法や環境構築はまちまちで、複数のCIを利用する際はそれぞれのCIに対してビルド環境やビルドフローをメンテナンスする必要が出てきます。
近年のサーバーサイドアプリケーションはビルド環境や動作環境をDockerで統一化を図ることが主流となってきていますが、クライアントサイドアプリケーションではそういったアプローチに関する情報が少ない状態だと思います。

そこで今回はクライアントサイドでもDockerによるビルド環境の統一化ができないか検証してみました。

Android SDK with Docker

今回はAndroidアプリケーションに必要なビルド環境をコンテナイメージ化したAndroid SDK imageと、 そのAndroid SDK imageを元にアプリケーションコードを含めてビルドするApplication imageに分けたいと思います。 Android SDK imageを元にアプリケーションコードをmountしてビルドする手段もあります。 しかし、今回はgradleのキャッシュをイメージに含めたいので、Application imageを作る方向にしました。

Dockerのイメージレイヤーは以下の通りです。

alpine image
|
|-- Android SDK image
    |
    |-- Application image

Android SDK image

まずはAndroid SDKを利用できるDockerイメージが必要です。

ここで注意が必要なのはGoogleのAndroid SDK利用規約 (3.4)により、バックアップ目的以外でのコピー及び再配布が禁止とされている点です。 (gfxさんのこちらのブログ記事に詳細が言及されています)

つまり、Androidをビルドする為のDocker imageは自身でビルドする必要があるということです。
しかしながら、Dockerfile自体にAndroidSDKのライセンスは含まれません。 よって、Github上に公開されているものを利用するか自身で記述することでDockerfileを用意し、自身でDocker imageのビルドを行なってください。

Application image

こちらはAndroidアプリケーションと同じディレクトリ内にDockerfileを作成します。 ADD . /projectでアプリケーションコードをイメージに追加する前にgradleのキャッシュを生成しています。 これはDocker imageをビルドする際、命令によって変更が加わるまではDocker imageキャッシュが利用されるという仕様を利用し、最大限にDockerイメージのキャッシュを生かしています。
仮にADD . /projectの後にgradleのキャッシュを生成する処理をしてしまうと、 ADDで追加するアプリケーションコードに変更が加わると、それ以降の処理はDockerイメージのキャッシュが利用されなくなります。

FROM ${ANDROID_SDK_IMAGE}

RUN mkdir -p /tmp/gradle/app
COPY gradlew /tmp/gradle/gradlew
COPY gradle /tmp/gradle/gradle
COPY build.gradle /tmp/gradle/build.gradle
COPY gradle.properties /tmp/gradle/gradle.properties
COPY settings.gradle /tmp/gradle/settings.gradle
COPY app/build.gradle /tmp/gradle/app/build.gradle
COPY app/mastiff.gradle /tmp/gradle/app/mastiff.gradle

WORKDIR /tmp/gradle
RUN ./gradlew

ADD . /project
WORKDIR /project

RUN echo "sdk.dir=$ANDROID_HOME" > local.properties

CMD ["./gradlew", "tasks"]

この状態で、

docker build -t application:$(git rev-parse HEAD) .
docker run --rm -it \
           -v $(pwd)/app/build:/project/app/build \
           application:$(git rev-parse HEAD) \
           ./gradlew build

と実行することで、アプリケーションをビルドすることが可能です。

登壇したセッションの資料にはAndroidエミュレータをDockerで起動しテストを行う方法も記載しております。しかし、エミュレータでのテストはパフォーマンスがよくないので、デバイスファーム等を利用すると良いと思います。

最後に

今回はクライアントサイドアプリケーションのDockerizeに関してご紹介しました。機会があればサーバーサイドアプリケーションのDockerizeについてもご紹介できればと思っております。

Android Test Night #2 の開催時期は未定ですが、2, 3ヶ月に1回程度開催を予定しておりますので、ご興味がある方は是非お待ちしております。

本ブログでは定期的にこういったテストにまつわる情報をアウトプットをしていきますので、是非本ブログをウォッチして頂ければと思います。

参考文献

iOSDC2017にSWETから3名登壇してきました

はじめまして、1エントリ目を書くことになったSWETの平田(@tarappo)です。

DeNAのSWETグループでブログを始めることになりました。 今後共よろしくお願いします。

SWETってなに?って方もおられるかと思いますが、それについては以下の資料をチェックしてもらえると嬉しいです。

DeNAの取り組むテストエンジニアリング

1発目のエントリーですが、SWETのメンバーが最近登壇した内容について紹介しようと思います。 つい先日行われたiOS開発者のお祭りであるiOSDC 2017(9/15-9/17開催)にSWETから3名が登壇してきました。

今回は、SWETが得意とする領域である「自動テスト」と「CI」、そしてそれに密に関係する「デバイスファーム」といった3つの領域について発表しました。

その3つの発表について紹介したいと思います。

5分枠での発表ですが、その内容はボリュームがずっしりとありますので、是非発表資料と併せて見て頂ければと思います。

f:id:swet-blog:20171024140928j:plain

iOSで利用できるデバイスファームのメリット・デメリットの紹介

モバイルデバイスをクラウドやイントラネットワーク内で一元管理できるデバイスファームは、iOSにおいても、AWS Device Farmをはじめ簡単に利用できるものが増えてきています。しかし、実際に利用したという話は多くないのが現状です。

この発表ではAWS Device Farm、Xamarin Test Cloud、 Testdroid、TestObjectについて 実際に利用した感想を紹介しています。

UIテストの実行時間の短縮に挑戦する

XCTestが進化するにつれ、iOSアプリ開発においても徐々にUIテストが利用されるようになってきました。しかし、UIテスト(UI testing)はユニットテストに比べて実行に時間がかかるという課題があります。

本発表では「複数シミュレータを同時起動してテストを並列実行するOSSであるBluepill」と「物理的に複数台のMacを利用したテストの並列実行」の2つを試した結果、通常の並列化しないテスト実行に比べて、どれだけ実行時間を短縮することが出来たかを発表しました。

2017年のiOSアプリ開発におけるCI事情

iOSアプリ開発においてもCIを利用するのが一般的になりつつあります。 本発表では、そもそもCIとはなにか?、そして世の中にあるiOSアプリ開発向けのCIサービスの比較を発表しました。

おわりに

SWETではiOSDCでの登壇に限らず、積極的に対外的にアウトプットをおこなっています。 また、SWET主催の勉強会(iOS / Android Test Night)も随時開催中ですので、興味がある方は来て頂ければと思います。

本ブログでは定期的に情報をアウトプットをしていきますので、是非本ブログをウォッチして頂ければと思います。