Android ellen cover

Androidのテストをスーパーチャージ ― JUnit & Espresso

テストはたいてい、バグを見つけてより良いコードを書く助けになりますが、適切なフレームワークを正しく使わなければ、テストはとても面倒なものになります。Ellen ShapiroBay Area Android Dev Groupの勉強会でのプレゼンテーションで、JUnit 4とEspresso 2を用いた、UIテストも含んだAndroidでのテストをよりパワフルにする方法について説明しています。プロジェクトの中でテストを簡単に使うための技術とTips、ベストプラクティスについてのデモンストレーションもあります。


イントロダクション (00:00)

この数か月の間で、2つの便利なツールがより良い形になってきました。JUnit4 AndroidランナーとUIテストライブラリEspressoのバージョン2です。これらは相互に絡み合っているのですが、まずはJUnit4ランナーについてお話ししたいと思います。

JUnit4: これは何? (00:19)

JUnit4自体は、巨大なオープンソースのJava向けテストライブラリです。長いこと存在していて、バージョン4の最初のリリースは2006年でしたが、最近になってAndroid用のものがリリースされました。その理由について、今から簡単にお話しします。

JUnit4の一番大きな機能は、サブクラス可能なテストランナーを持っているということで、それによってJavaでできる様々なことに対してテストを書くことが簡単になります。例えば、特にScalaテストを走らせるものや、その他様々な特定のもの(例:Spring)に対してテストを走らせることができます。

テスト: 古のやり方 (01:03)

GoogleはJUnit4ランナーをEspresso 2.0の一部として2014年12月に導入しました。それ以前は、Androidでのテストはこんなふうでした:

ActivityInstrumentationTestCase2<T extends Activity>

このように常にナンセンスなものをサブクラス化してActivityクラスにしなくてはなりませんでした。JUnit3のテストランナーを使っているからです。そしてJUnitの実際のアサートクラスにまでさかのぼって、いろんなものをサブクラス化します。このおかげで全てのアサートメソッドをインポートする必要がないほどです。

この古いバージョンでは、テストの名前はtestという単語からはじめる決まりになっていました。これはコーディングを学んでいる者の一人として、私には非常に間抜けに見えました。「全てのテストがテストという単語ではじまっていたら、それぞれのテストが何をやっているのかどうやって区別すればいいの?そもそもどうしてテストって単語がここにあるんだろう?」これはいらいらして、冗長なものでした。

ある時点でその機能が必要なのはわかっているけれど、全ての作業を常に監視してほしいわけではない(例えば、サーバーが一時的に落ちているとき、使っているサードパーティ製のライブラリにバグが混入したとき、など)という理由で走らせたくないテストがあったとしたら、皆さんはそこをコメントアウトしたり、一時的に消去したりするでしょう。そして、後でバージョン管理からその部分を復元しないといけませんよね。これこそ悩みの種です。また、いくつもの異なるデータセットに対して同じ機能を試すテストが欲しい場合には、プライベートなヘルパーメソッドを作り、個々のテストと共にデータセットを渡す必要があるでしょう。

テスト: JUnit4でのやり方 (02:48)

JUnit4では、命名規則でそのメソッドが何をするかを表す代わりに、アノテーションを用いることができます:

  1. どのメソッドが、毎テストの前に実行されるべきかを宣言する
  2. どのメソッドがテストであるかを宣言する
  3. どのメソッドが、全てのテストの後に実行されるべきかを宣言する

記事の更新情報を受け取る

さらに、テストを無視するようにマークすることができます。コメントアウトするのではなく、無視されるべきテストとしてマークすることができます。これにより、テストが無視されたのは、存在しないからではなくて、チェックされていないだけだということがわかるようになります。例えば、テストの一覧を見れば実行されたかされなかったかがわかるし、実行されたテストは黄色いボール(緑のボールの代わりに)、または失敗した場合には赤いボールとして表示されます。

ランナー自体がサブクラスを持っているので、さまざまな素晴らしいことができるようになります。例えば、テストに特定のサブクラスを使う必要がないので、必要に応じてそれぞれの異なったランナーを使えます。パラメーター化テスト(今まででもっとも便利なもののうちのひとつ)の例をお見せしましょう。もし使いたければ、自分自身で用意したテストランナーのサブクラスを使うこともできます――これについては、そこまで深く追求はしていませんが(かなり深いウサギの巣穴のようなので)、理論的にはできることです。

JUnit4では仮定に基づいたテストのスキップが可能です。この「仮定」のアイデアは、とある前提条件に基づいてテストを自動的にスキップ可能にする、というものです。例えば、通常ステージングサーバーを用いて行われるべきである一連のテストを走らせる際に、ベースURLが実際にステージングサーバーのURLになっていることを保証したいとします。そこで、ベースURLはプロダクションサーバーのURLだ、という仮定を立てれば、それらのテストは全て無視することができます。ステージングとプロダクションではデータが異なるため、これは非常に便利です。みなさんもプロダクションサーバーでのデータを(テストで)変えたいとは思わないでしょうし(ひどいアイデアです)、本当に動くか確認したいだけですよね。

これはとてもよいアイデアです、理論的には。問題は、これがEspresso 2.1では現在動かない、ということです。この仕組みは、JUnit4のランナーのサブクラスに依存して、仮定が成り立たなかったときに適切に「スキップする」とマークさせることができます。でも今のところは、そうする代わりに、ただ仮定違反の例外を投げ、テストを失敗させるだけです。なぜかというと、JUnit4ランナーをサブクラス化している全てのランナーがこれを実装しなくてはならないからです。当初、Androidチームはこれをやっていませんでした。私の同僚のBill Best(弊社のAndroidエンジニアリングのディレクター)は、「これはいい!Android JUnit4は仮定が成り立たなかったらテストをスキップするべきだ!これはEspresso 2.1で直るはずだ」と言っていましたが、それから2週間後ぐらいに(Google Codeのandroid-test-kitイシュートラッカーで)「…Espresso 2.1では直ってないようだ」「ごめん、このIssueは再オープンしてチェックするよ」というアップデートがありました。ですので、彼らはまだこの問題に対処しているようです。近いうちに直ることを期待しましょう。

JUnit4の利点 (05:59)

明るい話題として、利点についてお話ししましょう。私にとって最大の利点は、本当に必要でない限り、テストはアクティビティへの参照を持たないということです。古いメソッドに対する大きな利点のひとつは、今まではアクティビティは常に起動せねばならず、時間がかかりましたが、今ではアクティビティテストルールにより、セットアップが非常に簡単になりました。もしアクティビティが必要でなければ、起動する必要もないのです。またアクティビティが必要なときは、セットアップは2秒で終わります。これはどちらの場合においても素晴らしいことです。

もうひとつの利点は、納得のいく方法で命名が行えるということです。命名規則に関する拘束が取り除かれたため、自分にとってわかりやすい方法でテストを命名することができます。単なるpublic static void setUppublic static void tearDownではなく、public static void beforeAllTestsと命名することができます。命名規則に詳しくない誰かがあなたのコードベースにやってきた場合は、@BeforeClassとアノテーションし、beforeAllTetsと命名すれば大丈夫です。ですので、既存の命名規則に従う必要は ない のです。

さらにもうひとつの利点は、繰り返しになりますが、パラメーター化テストで、これは同一のテストを異なったパラメータで何回も走らせることができます。

JUnit4の欠点 (07:29)

実に嫌な対処法があります。願わくば、この動画が投稿されるころには、解決しているといいのですが: FlakyTestはJUnit4のテストでは動作しません。これはとても便利なアノテーションで、テストに加えることができます。「実際に失敗する前に、X回失敗してほしい、例えば、UIテストやサーバーサイドなどの自分がコントロールできない場合があるから」といったことを実現するものです。

私はある特定のSamsung製のデバイスで問題を抱えていました。ボタンをタップするようにデバイスに伝えると、50%くらいの確率で、タップしてもアクセシビリティのラベルだけが現れるのです。そのボタンはタップされたと気づいていません。この問題に対処するためにたくさんのtry-catchコードを書くのは非常に腹立たしいことです。理想的な世界では、FlakyTestでは完全に不要なものになるでしょう。でもここは理想の世界ではないので、非常に頼りになります。しかし、ActivityInstrumentationTestCase2が持っているJUnit3ランナーに直接結びついているため、JUnit4のテストではまだ動作しません。

また別の面倒なことは、いろんなものを追加で静的インポート(static import)しなければいけないことです。もはやJUnitのアサートを継承していないために、自分でインポートする必要があります。すみません、static importではなくimport staticでした。もしstatic importと書いたら、それは怒りだしてあなたを非常に悲しい気持ちにさせることでしょう。テストケースの継承チェーンによって届けられていたもろもろを、自分で静的に持ってこないといけない、ということだけ覚えていてください。

JUnit 4 - デモ (09:57)

[Ellen氏がsample projectでどのようにテストを書いて、動かすのかデモをします。]

Espresso: これは何? (23:50)

Espressoは、Googleが作った、Androidのための素敵なオープンソースのUIテストフレームワークです。あなたのアプリケーションで、テストを統合するためのとても優れた方法です。伝統的なユニットテストは、「これは独立して動くの?」という問題に答えることが全てでした。そしてUIテストは、全てが統合されていることを確かめるものです。あなたのコードは独立した状態では完璧に動いていても、UIテスト的なものなしで全てを統合しようとしたら、わけのわからない面倒なバグをもっているかもしれないのです。

独立したような状態でUIをテストするのも、大きな価値があると思いますが、不可避的に、アプリの中には、協調して動くかをテストして確認しなくてはいけないようなピースもあります。例えば、フラグメントやアクティビティ、ビューなどです。

Espresso: どうやって動く? (24:51)

もしあなたがHamcrestマッチャーをMockitoともに使ったことがあるなら、見たことのある感じがするでしょう。ビュー階層を掘り進み、直接ビューを参照せずとも、特定のビューにマッチするものがほしい、と指定することができます。FindViewWithIDに似ていますが、「ビュー階層を掘り進んでいって、そのIDにマッチするビューを見つけ出し、それを持つマッチャーを返す」という観点だけは違っています。

これは即座にユーザーに見えるビューの一部分のみを考慮します。アプリを使おうとしている体験のことを考えれば納得がいくはずです: 「もしEspressoフレームワークから見えないのならば、ユーザーからもおそらく見えない。」

Espressoを使えばすぐに、テキストを入力し、タップ、スクロール、スワイプすることができます。Google製のこのフレームワークには、UIの中で動かしたいであろうほとんどのよくあるアクションは用意されています。もし欲しいアクションが提供されていない場合は、自分で作ることもできます。

事実上制限なく複雑なものを作ることができますが、これは良いことでもあり、悪いことでもありますね。またカスタムアクションを組み合わせて新しくアクションを作ることもできます。例えば、Hamcrestマッチャーの大きく長いチェーンとビューアクションを作る代わりに、テキストとビューをプレースホルダーで置き換える、など。これで読みやすく、わかりやすく、何をテストしているかについて説明することができるというわけです。

ではここで、UIテストを書く際の一般的なtipsを2、3個ほどお伝えしたいと思いますが、実際にデモを見てからのほうがわかりやすいかと思います。

Espresso: UIテストデモ (26:35)

[Ellen氏がサンプルアプリWinoを使って、EspressoでUIテストを書いて動かすデモをします。]

UIテストベストプラクティス (35:27)

UIテストのベストプラクティスは、長い長い時間をアプリケーションのテストに費やすのを避けるために重要な事です。わざわざ毎回UI全体をナビゲートすることはありません。私がEspressoでテストを書き始めた1年ほど前、テストにはかなり長い時間がかかっていました。実行するたびに、フレームワークにログインし、実際にテストしたい部分に遷移し、そこからようやくテストが走っていました。アプリのフローが複雑だと、テストのたびに膨大な時間がかかってしまいます。

より良い方法としては、クラスとインスタンス、BeforeAfterメソッドを使うやり方があります。beforeクラスとアノテーションされた静的メソッドのうちのひとつを使って、指定されたファイル内の全てのテストが実行される前にコードを実行することができます(裏でユーザーをログインさせる、など)。Afterクラスでは、セットアップされた状態を全て破棄することができます。インスタンスメソッドを使っても同じことができ、テストごとにその前後に実行されます。UIテストで生まれた状態は巻き戻して、い���も同じ状態から開始されるように気をつけてください。

UIの各ピースごとにテストクラスを作りましょう。指定されたクラス内の特定のUIピースに対する全てのテストを分離することで、まずはじめに、テストのうちの特定のクラスだけを実行することができるようになります。これは、テストをしているものを分離している、ということです。全てのテストを分離することで、特定のフラグメントの中にあるものだけをテストするテストアクティビティを作ることも可能になります。

UIなしにテストができるものがあれば、UIなしにテストしましょう。UIを表示するのは時間がかかります。またいろんなUIにまたがって使われる共通コード、特にバリデーション関係(Eメールや電話番号のバリデーションなど)があれば、ユーザーが入力したデータを検証してきちんとビジネスに使えるようにバリデーションを走らせる必要があります。誰かが無効な電話番号を入力した時に適切なエラーが表示されるかを確認するのにUIテストを使いましょう。でも、UIテストで400個の異なった不正な電話番号をテストする、なんてことはやりたくないですよね。そのかわりにUIを使わないパラメータ化テストを使ってく���さい。

UIテストのさらなるTips (38:56)

モックフレーバー を作ってテストをすることで、(ネットワークのスタックのような)オブジェクトをモックするのがとても簡単になり、またそれが完成版のアプリには含まれないことを保証してくれます。またUIが矛盾なく振舞っていること、よくわからない状態のサーバではなく 既知のデータ に対してテストしていることも確認できます。実際にテストするのは、「サーバー側は何も変わっていないか?」ではなく、「それがユーザーに表示されたときに実際にどう見えるべきか?」です。

ここでちょっと宣伝させてくださいね。Vokalのチームが内製で開発しオープンソースにしたとても素晴らしいものがあるんです。Bill Bestが始めた、Mocktrofitというオープンソースのライブラリです。似たようなモックテストライブラリでiOS用のVOKMockUrlProtocolもあります。これが何をするのかというと、モックデータファイルを作り、どのAPIをコールするかをそのファイル名で決定するものです。ネットワークスタックのモック化に興味があって、特にすでにretrofitを使っているなら、ぜひチェックしてみてください。

文字列リソース(R.string)を、スクリーンに表示されているものと比較しましょう。UIテストを書くときに、チェックしようとしている関数に生の文字列をそのまま渡すのが好きな人がいます。例えば、文字列を作成する(例:PMを4:30 (PM)のようにカッコの中に入れる)には生の文字列を渡さないと難しいですよね。ですが、指定したエラーメッセージが正しい場所に表示されるか、あるいは指定したものがユーザーに表示されるかを確かめたい場合は、文字列リソースが便利だし、時間の節約にもなります。また全ての文字列を同じ場所に置いておけるので、たとえばあなたやコピーライターがそれを変更しても、テストを書き直す必要はありません。

繰り返しになりますが、スクロールが必要なことを覚えていてください。Espressoは現在スクリーン上で見えているものを通して検索します。もしあなたが小さなシミュレータやデバイスを使っていた場合、その表示したいもののところまでスクロールできるようにする必要があります。さもなくばテストは失敗します。

アプリケーションというコンテキストでのUIテストにおいて私が好きなことは、実際に辿っていかずに、埋もれているUIのピースを手に入れることができることです。例えば登録ページをいじっているときに、必要な情報を全て毎回タイプして入力したくはないですよね。要するに、「今から百万秒の間、このアプリケーションの今作っている部分をいじっていられるように、テストが失敗しないでほしい」とか「テストの中に作り上げたこれらの状態は全部、私のやりたいことをやってくれている?」と言うことができます。情報を全て入力する時間を節約するだけでなく、それが動くかどうかのテストをすでに書いている状態なのです。これができるってかなりクールですよね。

Q&A (44:01)

Q: Espressoのスクリプトを異なったデバイスのタイプ、メーカーからスクリーンサイズの差異までに適用する際の苦労について個人的にいろいろ聞いていますが、あなたのEspressoの経験から、この断片化問題についてと、どうやって対処しているかについて聞かせてください

Ellen: 一番大きなことは、FlakyTestの必要性です。これが必要なものの大部分です。それと、スクロールができることです。グレイスフルデグラデーションすべき機能があるかどうかを知るのにも役に立ちます。またその機能が望んでいる方法で達成されたかどうかを知るのにもとても便利です。

クライアントのために私が作ったアプリは、比較的シンプルなもので、「この機能は使えないし、そのためのサポートライブラリもない」というようなものではありませんでした。私が使った機能のほとんど全てはサポートライブラリがあったので、そのような必ずしもあってもなくても良い機能との経験はあまりありません。ですが、断片化の問題は間違いなく頭痛の種です。

Q: もしEspressoでアクティビティのユニットテストをしていて、そのアクティビティが、インテントから来るような何らかの形の入力を期待している場合、どうやっていますか?

Ellen: テストしたいものによります。Espresso 2.1では、アクティビティルールに加え、インテントのデータをモックするインテントテストルールが導入されました。例えば、だれかがあなたのアプリを他の、テキストを共有するインテントを送るアプリから開いたとします。UIを使えるだけ使うために全てをセットアップする代わりに、そのインテントを使ってアプリを起動できるようにモックすることができます。まだ少ししかいじっていませんが、今まで見た感じではとてもクールだと思います。

Q:Espresso 2でスクリーンショットを撮ることは可能ですか?あるいは、何かテストが失敗した時にスクリーンショットを撮るために、何かアドオンが必要ですか?

Ellen: これはやったことありません。私の記憶によると可能だったと思いますが、鵜呑みにしないでください。UIテストでは可能だと思います――AppleがXcode7でリリースした新しいテスト用ツールの中で、テストが失敗した時に自動でスクリーンショットを撮るツールを導入したのは知っています。そうするために比較ツールを使うこともできます。これについてはあとで調べてツイートしますが、今のところはちょっとわかりません。

Q: これはADPだ、あるいはビューが適切に整列している、というような感じでビューの位置をチェックすることはできますか?

Ellen: ビューマッチャーは実際にはビューを返しません。ビューマッチャーからビューを取得する方法があったはずですが、少し複雑です。これは位置を得るためのものであり、正確にピクセルパーフェクトなデザインのために作られたものではありません。ユーザーがあなたのアプリをなんとかして使って確実に目的を達成できるように作られたものです。小さいデバイスで、スクロールするかは別として、表示される必要があるものがあるなら、FindViewWithを使えば、それが見えるかどうかは確認することはできます。ですが、ピクセルパーフェクトに配置されるかどうかをチェックする、ということについて言えば、いろんな魔術を使わない限り、それをやる方法はわかりません。

Q: いつスクロールするかを覚えている、ということですが、ベストプラクティスは何でしょうか?要素を見つけるためにスクロールが必要かもしれない、ということですが、どのようにこの部分を処理していますか?

Ellen: 通常私がやっているのは、キーボードが前面に表示された時に隠れるくらいの長さの要素だったとしたら、単純にスクロールします。特に、小さいデバイスでは、そうすることが必要になりますから。もし、変更する必要があるエディットテキストがひとつあるだけなら、スクロールの心配はしません。経験則的には、スクロールビュー内につっこみたければ、単にスクロールすればよいのです。もしあなたがユーザーがスクロールの必要があるだろうと感じていて、スクロールビューに入れる十分な理由があるならば、あなたのアプリをテストするロボットも、同様のデバイスで動作している場合スクロールする必要があるでしょう。

基本的には、そのビューが少なくとも90%見えるまでスクロールし続けて、さらにしばらく「ビューがどこに行ったかわからない!」となる前までスクロールします。様々なメーカーがどれだけAndroidのUIをいじっているかによって、影響を受けてしまいますが、それでも本当に望むなら、時間と労力をかけて、「よし、特にこのデバイスではテストをスキップしよう、あるいは基本的にこのような場合には対処しないようにしよう」とすることができます。JUnit4の仮定が動くようになれば、もっと簡単にできるようになります。

Q: Appleからリリース予定のXcode 7のUIオートメーションテストと比較して、どうですか?

Ellen: はい、ほんの少しだけXcode 7で遊んでみました。いつもはSquareが提供しているライブラリのKIFを使っています。UIオートメーションでひとつ、少しいらいらすることは、アプリケーションの中で何が起こっているかを取得できないことです。なぜなら、アプリケーションは基本的には完全に分割してテストすることが可能だからです。その中のものにはアクセスできません。一方でKIFはたくさんのプライベートAPIを使っていて、「裏でやる必要のものをちゃんとやっているか?」を確認することができます。これはEspresso流アプローチの大きな利点だと思うのですが、表示に関することをちゃんとやっているかのみならず、保存されるべきあらゆるデータが正しく保存されているか、ということまで確認できるのです。あるいは、私がやったように、テストを書いて、失敗した場合にはそれが怒るようにすることもできます。

翻訳:岩谷 明 Akira Iwaya 校正・校閲:Yuko Honda Morita

About the content

This content has been published here with the express permission of the author.

Ellen Shapiro

Ellenは、イリノイ州シカゴにあるVokalのシニアiOSエンジニアです。シカゴで[AndroidListener](http://www.meetup.com/AndroidListener-Chicago/)を主催しています。また、暇な時間に、作詞作曲のアプリケーションであるHumを開発したり、raywenderlich.comでiOSのチュートリアルを書いたりしています。

4 design patterns for a RESTless mobile integration »

close