カスタムViewが想定通りに描画されているかテストする

カスタムViewを作って、しかもそれがCanvasを使って描画するようなものだった場合、どうやって動作確認をしていますか?

私はこれまで実機で動かして、目視で確認していました。Viewの見た目なので目視で確認するしかないんですけどね。それを手動でやっていました。

しかしつい先日、手動での確認が難しい案件に出くわしました。それは端末のセンサーの値を読み取って、その値にあわせてカスタムViewの描画が変わるようなものでした。これは手動で確認したくとも難しいです。

例えば心拍数を元に描画が変わるカスタムViewを想像してみてください。心拍数が120を超えたら特殊な表示を行う仕様だと思ってください。実機でそれを確認しようと思ったら、心拍数を上げるべく毎回運動しなきゃいけない、なんてことになるわけです。

そういったViewの描画、見た目の確認がしたい。こういうの、みんなどうやってテストしているのだろう。それが今回の出発点です。

サンプルプロジェクトをGitHubに置いてみたので良かったら見てみてください。というよりコードの解説はこの記事では一切ありませんので、GitHubでみてください。

やり方書かないのもあれなので、追記しました。

サンプルについて

TextViewの周りを線でデコレーションするカスタムViewがテスト対象です。どこを描画するかを指定してinvalidate()すると、TextViewの周りに線が描画されます。onDrawメソッドをオーバーライドして、Canvasを使って線を描いています。

今回はこの描画がちゃんとできるかを確認する、というそんなテストです。

スクリーンショットを撮って確認しよう

Viewの描画を確認したいわけですから、ユニットテストでは確認できません。

そこでまず思いついたのが、スクリーンショットを撮って、その画像で確認できたらいいんじゃないかというものでした。以前にEspresso+Spoonで自動的にスクリーンショットを撮るテストの話を見たのを覚えていたので、これを使えばいけそうと考えました。

問題が2つ

しかしSpoonを使ってスクショを撮るには、WRITE_EXTERNAL_STORAGEパーミッションが必要になります。プロダクト側で必要なら問題ありませんが、そうでない場合はテストのためだけに不要なパーミッションを追加することになります。できればそれは避けたい。

また、スクショはActivityを起動してそれを撮影することになるわけですが、実際に対象のViewを表示するActivityがテストに適した作りになっているとは限りません。

例えばこのサンプルプロジェクトでも、MainActivityを使ってテストできなくもありません。Espressoを使ってボタンを押すようにすれば、カスタムViewの描画は切り替わります。しかしこのMainActivityの仕様だと、カスタムViewの上と下に線を描画した状態をテストできません。

つまり、実際に使うActivityとは別にテストのためだけのActivityが欲しいわけです。

ではそんなActivityをプロダクションに混ぜるのかという話になりますが、それも避けたい。

テスト用のProduct Flavorsを用意する

そこでテスト用のプロダクトフレーバーを作成することでこれを回避しました。これもあまりスマートなやり方ではなく、できれば避けたかったのですが仕方ありません。

debugビルドにだけテスト用のパーミッション、Activityを含めるという方法もなくはないのですが、プロダクトフレーバーで切り分けてしまったほうが潔いかなと思ったのです。

テスト用のAndroidManifestとActivityさえ用意できれば、後は簡単です。

余談、androidTestに専用Activityを作ればいいんじゃないかという考え

ちなみに私は最初、androidTest配下にテスト用のActivityを追加して、それ経由でテストすればいいんじゃないかと考えました。しかしそれはうまくいきません。

なぜなら、androidTestに配置したコードはテスト用のAPKにコンパイルされるからです。

私は今までずっと勘違いしていました。androidTestに書いたテストを実行したら、mainに配置してるテスト対象コードにテストコードを追加したAPKが作成されて、それでテストが実行されてるんだと思ってました。どうもそうではなくて、普通のAPKを単にテスト用APKで外部から操作してただけなんですね。

https://stackoverflow.com/questions/27826935/android-test-only-permissions-with-gradle

作り方

まずproductFlavorを追加します。サンプルでは普段使うやつをDefault、Viewのテスト用のものをUiTestとしました。ここではUiTestを追加するとして書いていますので、適宜読み替えてください。

まずapp/build.gradleにproductFlavorの設定を追加します。applicationIdSuffixはお好みで。

android {
    productFlavors {
        Default {
        }
        UiTest {
            applicationIdSuffix ".uiTest"
        }
    }
    // そのままだとUiTestReleaseもbuildVariantに追加されてしまうので、それに対処
    android.variantFilter { variant ->
        if(variant.buildType.name.equals('release')
                && variant.getFlavors().get(0).name.equals('UiTest')) {
            variant.setIgnore(true);
        }
    }
}

EspressoとSpoonのセットアップ

Espresso

Spoon

プロジェクトルートのbuild.gradleに追記。

        classpath 'com.stanfy.spoon:spoon-gradle-plugin:1.2.2'

app/build.gradleに追記。

apply plugin: 'spoon'


android {
    defaultConfig {
        // 追加しないと多分テストがうまく走ってくれないと思います。
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

dependencies {
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    androidTestCompile('com.android.support.test:runner:0.5', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    androidTestCompile 'com.squareup.spoon:spoon-client:1.6.4'
}

プロダクトフレーバー用のディレクトリを作成

プロジェクトツールウィンドウのスコープをProjectに変更して、手動でディレクトリを作成します。(何か他にいい方法知ってれば教えてください)

<project root>
+-app
  +-src
    +-androidUiTest
    | +-java
    |   +-<your package>
    |      +-Viewの描画確認とスクショを撮るコードをここに配置
    +-UiTest
      +- AndroidManifest.xml
      +-java
      | +-<your package>
      |   +-Viewの描画確認のためのActivityを配置
      +-res // layout.xmlが必要なら作る

AndroidManifest.xmlに書くこと

  • `WRITE_EXTERNAL_STORAGE`の追加
  • 追加したActivityの宣言

サンプルコードを見てもらえば分かりますが、ビルド時にmainにおいてあるAndroidManifest.xmlとマージしてくれるので、UiTestで必要な分だけ書けばOKです。

あとはテストコードを書くだけ

別にアサーションは必要ないし、Espresso使っていると言ってもViewの操作をするわけでもないので(それをしなくていいようにテスト用のActivityを用意している)、テストコード書くのは超簡単なはず。

レポートの生成

  1. buildVariantを`UiTestDebug`に変更
  2. ターミナルで`./gradlew :app:assembleUiTestDebug`
  3. ターミナルで`./gradlew :app:assembleUiTestDebugAndroidTest`
  4. ターミナルで`./gradlew :app:spoonUiTestDebugAndroidTest`
  5. app/build/spoon/UiTestにレポートが生成される
  6. index.htmlをブラウザで開く
ターミナルでコマンドを打つか、もしくはgradleツールウィンドウから該当のタスクをダブルクリックとかでもOKのはず。

Amazonのほしいものリストを公開しています。仕事で欲しいもの、単なる趣味としてほしいもの、リフレッシュのために欲しいものなどを登録しています。 寄贈いただけると泣いて喜びます。大したお礼はできませんが、よりよい情報発信へのモチベーションに繋がりますので、ご検討いただければ幸いです。