読者です 読者をやめる 読者になる 読者になる

Android/iOSアプリのテストの区分戦略

技術部の松尾(@Kazu_cocoa)です。

クックパッドのモバイルアプリ開発では、どのようなテストを書き、どのようなタイミングで、どのようなテストを実施するか?に関してエンジニア各位が意識を合わせるためにテストサイズを定義し運用してきました。ここでは、そんなテストサイズに関して簡単ですがまとめておこうと思います。

テストサイズとは

ソフトウェアテストに関わったことがある方なら テストレベル という言葉には出会ったことがあるかと思います。JSTQBでは、このテストレベルは"管理していくテストの活動のグループ"と定義しています*1。 そうでない方も、俗に言う単体テスト/統合テストなど聞いたことがあるかと思いますが、その区分がここで示しているテストレベルとなります。

一方、このテストレベルはV字型と言われる開発工程と合わせて世の中で広く使われているため、社内における共通認識を構築するにあたり個々人の認識を合わせる上で手間がありました。そこで、 私たちの開発スタイルに合うようにいくつかの指標を定義し、そこに沿ってテストを区分する方法をとりました。その結果、その区分に対して妥当な表現としてテストサイズ、という言葉をとりました。この区分はGoogleのTest Sizeの影響も受けています。

なお、テストレベルに関しては安定したリリースを継続するためのテストとテストレベルの話として記事を書いたことがあります。当時はまだこのサイズに対する定義も試行錯誤の段階だったのでサイズという表現を用いてませんでした。

以下では、今回の話の対象と区分、その実例に軽く触れながらどのような定義を用いているのか載せていきます。

対象

  • モバイルクライアントアプリ
    • サーバ側は対象外

テストサイズと区分け表

テストサイズをsmall/medium/large/Enormousに区分します。その中で、いくつかの依存する要素に対してどのような関係を持っているのかをまとめます。

feature Small Medium Large Enormous
network no localhost localhost/yes yes
system access no partial/yes yes yes
OS(APIs) no yes yes yes
View no partial yes yes
external system no no no yes
time per a test < 100ms < 2s < 120s < 500s
  • yes: 基本、mock/stubしない
  • no: mock/stubする
  • partial: テストしたい事を安定させるためのmock/stubはOKだが、基本はしない
  • localhost: localhostに向けた通信

基準

テストサイズを区分する上で、以下の要素を考慮に入れています。

  • 依存性 : OSへの依存、ライブラリへの依存など。依存性が高いほど、複雑な系を対象にする。
  • 実行時間 : テストの実行時間
  • 実施環境: ネットワークアクセスなどなどのテスト時に依存する周辺環境

これらは私たちの開発スタイルであったり、テスト容易性を考える上で特に重要な性質を持ってきています。

区分け表で使っている要素

  • network connection
    • モバイルアプリの通信環境
    • API Clientを置き換えるか、localhostを起動するか、など
  • system access
    • DB/Shared preferenceなど、データの書き込みがあるかどうかなど
  • OS APIs
    • OS固有のシステムへのアクセスがあるかどうか
  • View
    • Viewが実際に描画されるかどうか、など
  • External system
    • アプリ間連携とか含む
    • Pushとその模倣など

各種内容

実際にS/M/L/Eの4つのサイズに区分した例を出しました。その中で、それらを実際の業務でどのように使い分けているのかを例も交えながら取り上げていきます。

Small Test

内容

  • RUN FAST!!

実施頻度

  • PR毎

検出したいこと

  • 小さなロジック単位で、意図した通りにメソッドが動作さすること

検出しないこと

  • Viewが正しく表示されるか
  • ネットワークなどの周辺環境の変化に対する確認はできない

  • RoboletricやJVM上で実施されるテストコード
    • 社内では、基本的に何もアノテーションをつけない場合このテストサイズとしてテストが実行されます
  • XCTest/OCMock系を使ったテストコード

Medium Test

内容

  • OS提供のAPIや機能、描画へ依存するが一定のシナリオ実施までは行わないテスト
    • Intentの受け渡しによりViewが正しく起動するとか
    • ActivityやCustom Viewの単純な表示確認とか
    • 通信は基本mock/stubサーバ、localhostへの接続

実施頻度

  • PR毎、もしくはmasterにマージされる毎

検出したいこと

  • 幾つかの要素に対して依存性を持った要素の、主にロジック面で不備がないか
  • Androidに依存する箇所の動作を確認できる
    • intentの受け渡しが正しくできているか
    • 特定のviewの表示要素がクラッシュなく表示されているか
    • sharedpreferenceなどへの書き込みが正しくできているか
    • RoboletricでShadowにされているところ全般
  • iOSへの依存に関しても同様

検出しないこと

  • 画面遷移含んだ複数Viewの遷移が正しくできていること
  • 通信のタイムアウトエラーなどの、OSの外部環境に依存する振る舞いの変化など

  • AndroidTest、Espressoを使ったテストコード
    • @MediumTestアノテーションを付与する
  • XCTestを使ったテストコード

Large Test

内容

  • 一定のシナリオ(画面遷移)が期待通り実施されるか確認
  • その時の通信などの処理も確認

実行頻度

  • 任意、もしくはリリース毎

検出したいこと

  • Viewからの、一連の操作に対する内部処理が正しく動作しているかを確認する

検出しないこと

  • テスト対象となる単アプリ以外を含めた連携の確認
  • 実際の通信を行った上での、タイムアウトエラーなどによる表示の変化

  • EspressoやUIAutomator v2を使い、ある一定の操作を実際の描画含めて行うテストコード
    • @LargeTestアノテーションを付与する
    • 別途、社内では、Android TVに対する画面遷移含むテストの独自アノテーションを定義し実施しています
  • XCUITest / UIAutomation / EarlGrey
    • EarlGrey用のスキーマを用意
  • Appium(一部)

Enormous Test

内容

  • リリースする成果物や、アプリ関連携が正しく動作するか確認する
  • ある一定のシナリオを、リリースと同等の状態で実施する
  • OS側の設定を変化させた時のアプリの動作を確認する

実行頻度

  • 任意、もしくはリリース毎

検出したいこと

  • 複数のアプリを跨いでアプリが期待した通りに動作するか
  • システム設定に依存した箇所の表示の変化
  • リリース物そのものに対する画面網羅、画像取得、その差分検出して問題ないかどうか
  • 性能劣化がないか
  • 多機種試験

不可能

  • 表示の網羅

  • Appium
  • 手動テスト

Tips

テストサイズが小さいうちはDIなどの技術を用いてテスト容易性を上げる、依存性を下げるなどするかと思います。一方で、テストサイズが大きくなるとテストが不安定になる要素や私たちアプリ固有の依存を持つ箇所が目立つようになります。また、動きを観測することで数値計測が必要になることも多々あります。その場合に対して、私たちは必要な補助ツールを用意し、適宜対応しています。その一例を載せています。

直接テスト実施には関係ありませんが、テストをするために必要な情報を取得したり、忘れがちなところを補助するツールも作り実行頻度を調整して適用しています。

  • wda_client(Ruby)
    • WebDriverAgentを使う最小限のラッパー
  • license-tools-plugin(Java)
    • Androidアプリのライセンス作成を楽にするOSS

最後に

安定した開発サイクルを保ちサービス開発に集中できるようにするために、安定した・必要な時間内に終えることができるテストの実現は現在まだ必要とされる環境です。そのためには、開発のボトルネックとなっている箇所を様々な技術(skill, technology, knowledge)を用いて対応していくことが必要です。

弊社ではこのような取り組みを共に実施し、より良いサービスを提供し続ける為にエンジニアを募集しています。ご興味のある方は、是非とも覗いてみてください。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/