Androidのローカルテストまとめ

概要

Androidテスト全書なども発刊されたり、今回のDroidKaigi2019でもAndroidのテスト周りの発表が多かったように思います。
直近のプロジェクトでもテストのカバレッジ50%以上を目指してテストコードを書いているのでその中でのハマったところやどういった観点でテストコードを書いたかまとめておきます。

テストコードを書いているプロジェクトの構成

アーキテクチャ

AACを使ったMVVMの形を採用しています。
Activity/Fragment <=> ViewModel <=> Repository になっていてAPIを実行した結果をUI側の都合に合わせてViewModelで整形して、Activity/Fragment側にLiveDataを通じて流す形です。
依存解決にはDaggerを使います。

使っているUIライブラリ

  • DataBinding
  • Epoxy

使うテスト用ライブラリ

  • org.robolectric:robolectric:4.1
  • androidx.test:runner :1.1.0
    • テストランナーにAndroidJUnit4を指定しておけば、実行時にInstrumental/Localテストを判別してAndroidJUnit4ClassRunner/RobolectricTestRunnerを切り替えてくれます
  • io.mockk:mockk:1.9.1
    • apiやrepositoryなどを必要に応じmock化する、実行回数のverifyなどに利用
  • androidx.test.espresso:espresso-core:3.1.2-alpha02
    • androidx対応後のライブラリはローカルテストでもEspressoを使ってUIの操作ができるようになった

その他

コードのカバレッジはjacocoを使っています。

各テスト対象についてどのようにテストするか

テストしやすい対象

UtilityClass

  • UtilityクラスはAndroid Platformのクラスが比較的出てこないのでJunitさえわかってれば簡単にかける
  • 基本は境界値だったりで正しさを確認する。
fun max(x: Int, y: Int): Int {
  return if (x > y) x else y
}

@Test
fun testMax() {
  assertThat(max(0, 1)).isEqualsTo(1)
  assertThat(max(0, 0)).isEqualsTo(0)
  assertThat(max(1, 0)).isEqualsTo(1)
}

BindingAdapters

  • robolectricでshadowされるViewを引数にテスト対象のメソッドに渡して、assertionでViewが持つ値を確認することで正しさを確認する。
fun bindText(view: TextView, value: String) {
  view.text = value
}

var mockView = TextView(ApplicationProvider.getApplicationContext<Context>())
@Test
fun test() {  
  bindText(mockView, "hello")  
  assertThat(mockView.text).isEqualTo("hello")
}

EpoxyController

  • buildModel後のmodelsをinterceptするinterfaceが用意されているので、下記の様な拡張関数を用意することで、buildModel後のmodelの配列を取得できる。
    取得できたmodelの配列の順序やclassをassertionすることで、RecyclerViewの表示の正しさを確認する。
// block: requestBuildModelsを実行する前にControllerに値を渡す処理などを実行する関数
inline fun <T : EpoxyController> T.interceptModels(block: T.() -> Unit): List<List<EpoxyModel<*>>> {  
  val models = mutableListOf<List<EpoxyModel<*>>>()  
  val interceptor =  
    mockk<EpoxyController.Interceptor> { every { intercept(capture(models)) } just runs }  
  addInterceptor(interceptor)  
  block()  
  removeInterceptor(interceptor)  
  return models  
}
@Test
fun test() {
  val controller = HogeController()
  val models = controller.interceptModels {
    setValue("hoge")
    requestModelBuild()
  }
  assertThat(models).hasSize(1)
  assertThat(models[0][0]).isInstanceOf(TitleModel::class)
  assertThat(models[0][0].title).isEqualTo("hoge")
}

EpoxyModel

  • テスト対象になるModelのBindingClassやViewHolderのinstanceを作る/mockする。
  • Model自体はAbstractClassなので、テスト用にclass TestModel: TargetModel()みたいな形でinstanceを作れるようにする.
  • bindのメソッドにBindingClassやViewHolderのinstanceを渡して、その後assertionすることでModelの振る舞いの正しさを確認する。
abstract TitleModel: DataBindingModel<TitleDataBinding>() {
  @EpoxyAttribute
  lateinit var title: String

  override fun bind(binding: TitleDataBinding) {
    binding.title.text = title
  }
}


@Test
fun test() {
  val model = TitleModel_()
  val layoutInflater = LayoutInflater.from(ApplicationProvider.getApplicationContext())
  val binding = DataBindingUtil.inflate(layoutInflater, R.layout.title, null, false)

  model.title = "hoge"
  model.bind(binding)

  assertThat(binding.title.text).isEqualTo("hoge")
}

ViewModel

  • 依存するrepositoryをmockする
  • coroutine使っていて非同期処理の問題が出てきたので、Main/IOのスレッドを置き換えられるように、ContextProviderというInterfaceを用意してテスト時にはMain/IOを揃えられるようにした。
  • ViewModelに生やしているメソッドを実行して、repositoryなどから取得したデータがLiveDataに流れてくるかで正しさを確認する。

テストするのに準備ややり方が大変な対象

Daggerを使っていることもあり、それらの依存関係が解消できないとそもそも起動しないのでテストすらできない。
RobolectricはApplicationは指定できるのでTestAppを作ってテスト用に作ったComponent/Moduleを用いて解決するようにする。

Activity

  • SingleActivity/MultiFragmentの構成なのでActivityは書いてない.

Fragment

  • NavigationComponentを使っているとFragmentScenario使うと遷移とかの確認ができなくなる(NavHostFragmentがいなくなるので)。app側が持つlayoutファイルを使うTestActivityを用意して、そのActivityに遷移させるメソッドを用意して、ActivityScenarioでTestActivityを起動、対象のFragmentに遷移としてからテストをするようにした。
  • ktxにlaunchActivityというメソッドがあるがこれを使おうとするとjvmTargetを1.8にあげないと行けないが上げるとhashCodeの実装がAPILevel21あたりから変わっていることでhashCodeのメソッドが見つからずcrashする。
  • SpinnerがEspressoを使うとonDataでデータを指定してリストを選択するようなコードが実機上は行えるがローカルテストではPopupListViewが別レイヤーにて表示されている関係で選択ができない。
    • ViewActionを実装して直接setSelectionするようなコードが必要になります。
  • androidx.test.espresso:espresso-contrib はRecyclerViewやViewPagerの操作のユーティリティーが集まってるのでViewのテストするときは入れたほうが楽になります。

今後挑戦すること

  • DroidKaigiの発表で見たようにFragmentのテストをAndroidTest/test両方から見れるように別に切り出すことで、共通のコードで行えるようにする。
Androidテスト全書

Androidテスト全書

  • 著者: 白山 文彦,外山 純生,平田 敏之,菊池 紘,堀江 亮介,
  • 製本版,電子版
  • PEAKSで購入する

Gradle Pluginを3.2.0にアップデートした際にしたこと

もともとはGradlePluginの3.1.2を使っていたところ3.2.0にアップデートすると下記のエラーがでてbuildができなくなった.

* What went wrong:
A problem occurred configuring project ':app'.
> Failed to notify project evaluation listener.
> com/android/ide/common/res2/ResourceSet

使っている他のgradle pluginのバージョンが足りてなかったのが原因でした.

  • buildTools: 28.0.3
  • kotlin: 1.2.30 -> 1.2.61
  • fabric: 1.22.1 -> 1.25.4

Alexaのスキルでアプリを起動する

AlexaでFireTV上のアプリを起動するまで

はじめに

Echo DotなどのAlexa搭載端末からFireTV Stickなどの端末上にあるアプリを起動させるまでの手順についてまとめる.
現状, USリージョンでのみ対応している.

AlexaのSkillの概要

スキルの種類

AlexaのSkillは4種類.

  • カスタム
  • フラッシュブリーフィング
  • スマートホーム
  • ビデオ

カスタムスキル

ユニークなスキルを作成しましょう。カスタムモデルでスキルの対話をすべて作成できます。

フラッシュブリーフィングスキル

ユーザーが自分のニュースフィードを管理できます。このプリビルドモデルを使用すると、聞きたいコンテンツを自分で管理できるほか、ジャンル別にトピックを検索できます。

スマートホームスキル

ユーザーが自分のスマートホームデバイスを管理できます。このプリビルドモデルを使用すると、照明やその他のデバイスの電源を座ったままオフにできます。

ビデオスキル

接続しているビデオデバイスで指定したコンテンツを再生させたり, 音量などの操作を行える。

  • コンテンツの検索, 再生
  • 再生/一時停止などの制御
  • 音量の変更
  • ビデオデバイスのOn/Off
  • アプリ, GUIの起動
  • 録画 など

スキルの処理概要


動画を再生するスキルの作成

動画のコンテンツを再生するスキルはビデオスキルを作成してユーザーからの応答を受け取る.
その後, ビデオスキルを介してユーザーからのリクエストをLambdaで適切な処理を行いAlexaSkillに応答する.

  • 適切な処理: コンテンツの検索を行うことや, コンテンツの再生/停止などを行う応答を作ること

ビデオスキルの実装

AlexaのスキルはAWSとは別に管理されており, 以下のコンソールで作成する.
https://developer.amazon.com/alexa/console/ask

コンソールでやること

  • default languageでUSを選択する. (日本語ではビデオスキルの作成がまだ行えない)
  • Create Skill
  • Input Skill Name
  • Select Video Skill
  • copy skill ID

Lambdaの実装

とりあえず指定のアプリを起動するところまでを実装する.
LambdaはAWSのサービスなので, 以下のAWSのコンソールから作成する.
https://console.aws.amazon.com/console/home

  • Lambdaの管理画面を開く
  • create function
    • Name, Runtime(得意な言語), Roleを選択する
  • Triggerとして, Alexa Smart Home を追加する
    • Application IDは, ビデオスキルの実装で作成したスキルのスキルIDを入力する
    • Enable Triggerのチェックボックスは, チェックするとスキルから呼ばれるようになり料金が発生するようになるので十分なテストを行ってから入れること
  • Skillとしての振る舞いを実装する
    起動以外については, https://developer.amazon.com/ja/docs/device-apis/alexa-launcher.htmlVideo Skill API Referenceを参照すること.

Alexa SkillへLambdaのARNを登録

  • 再度作っていたビデオスキルの画面に戻り, Default Endpointに上記で作成したLambdaのARNを入力する.
    • skillを使っているユーザーの地域に合わせてlambdaのEndpointも変えることができるので, 北米ユーザー向けはUSリージョンで作成したLambda, ヨーロッパユーザー向けにはEUリージョンで作成したLambdaと使い分けることができる

    Alexa Skillのテスト

  • 手順: https://developer.amazon.com/ja/docs/devconsole/test-your-skill.html
  • 今回はビデオスキルなのでアカウントリンクとアクセス権限の設定が必須なので行う.
    • (抜粋) 一部のスキルでは、エンドユーザーのIDを別のシステムのユーザーに接続できるようにする必要があります。これを実現するための仕組みを、アカウントリンクと呼び、Alexaに設定されているユーザーと、スキルのシステム(開発者のシステム)のユーザーアカウントを結び付けることを実現します
  • https://developer.amazon.com/ja/docs/custom-skills/link-an-alexa-user-with-a-user-in-your-system.html
  • アカウントリンクはOAuthに則った認証方式を用いる必要がある

ここまでのまとめ

OAuthの認証が必要になったので, Alexaからアプリを起動するところまでをやってみたかったが一旦ここで終わりにする.
ここまでの設定を行えば起動するはず.

参考

Instrumental Testを実行する環境を整える

markdownでかけるようにしてみた

RaspberryPI 3の立ち上げ

概要

RaspberryPI 3でやっている初期設定の手順まとめ.

やること

  • IPアドレスの固定化
  • SSHの設定
  • 必要なものをインストール

各手順

IPアドレスの固定化

stretchからは, 「/etc/network/interfaces」 を使った固定化ではなく, 下記の方法でが望ましい. interfacesのファイルにもそう記載がある.
クライアント側で払い出されるIPアドレスを要求し固定する. /etc/dhcpcd.conf を編集し, 下記を追記する. ルーター側でMACアドレスに対してIPを割り振るようにし.固定する. ifconfigで出力される内容の「ether」の項目がMACアドレスなので,それをルーターに設定する.

SSHの設定

サービスの自動起動

Deviceの再起動時にSSH serviceが自動で起動するように. `sudo systemctl enable ssh` を実行して自動でサービスが立ち上がるようにしておく.

鍵認証方式に切り替え

参照

Firebase Test Labのススメ

概要

無料枠でもFirebaseTestLab使って15台/day(3台*5回)をランダムテストとespresso使ったinstumentalTestを実行するようにしておくと便利かも.

Firebase Test Labとは

Firebaseが提供している一つのサービスで, firebaseが管理してる実際の端末, エミュレータ上でテストを実行し, 動画/スクリーンショット/logcatとして残してくれるサービス.

1度に3台まで1日あたり15台までを無料枠で利用することができる.

定期的に実行できる様に環境を整える

CircleCIでの定期実行の仕方についてのメモ.
.circleci/config.yml に以下のWorkFlowを追加する.
時間の指定はUTCになるので日本時間では9時間足しておく.

triggers:
  - schedule:
      cron: "0 1,4,7,10,13 * * *" # UTC. 10, 13, 16, 19, 22
jobs:
  - hoge # 定期実行したいjobをつなげていく

TestLabをremote実行できる状態を作る

login用のAuthenticationを作成する

コンソール画面から認証用のjsonを作成しておく.(下記の画像を参照)
「サービスアカウント」 => 「サービスアカウントを作成」 から, Test Labの管理者のアカウント作成を行い, formatをjsonとしてダウンロードする.

gcloud CLIの環境を作成する

CircleCIが用意しているDocker Image(circleci/android:api-26-alpha)はubuntuベースなので下記URLの手順にそってgcloud SDKをインストールできる.
https://cloud.google.com/sdk/downloads#apt-get

#!/bin/sh

export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)"
echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
sudo apt-get update && sudo apt-get install google-cloud-sdk
sudo apt-get install google-cloud-sdk-app-engine-java
echo $TEST_LAB_AUTH | base64 --decode > test_lab_auth.json
gcloud auth activate-service-account --key-file test_lab_auth.json

TestLabを実行するshellを組む

Test Labではパラメータコマンド実行時のパラメータでdeviceの指定やroboテスト, instrumentalTestのどちらかをするなどの指定指定をできるがyamlに定義しておくことができる.
https://firebase.google.com/docs/test-lab/command-line?hl=ja

# 無料枠を最大限使うには、virtual:2, pyhsical:1だと、5 test/day できる。
# 1test => all test codes

quick-robo-test:
  app: mobile/build/outputs/apk/mobile-develop-debug.apk
  type: robo
  max-steps: 100
  timeout: 90s
  async: true
  device:
    - {model: Nexus5X, version: 26, locale: ja}
    - {model: Nexus6P, version: 23, locale: ja}
    - {model: D6603, version: 21, locale: ja}
quick-inst-test:
  app: mobile/build/outputs/apk/mobile-develop-debug.apk
  test: mobile/build/outputs/apk/mobile-develop-debug-androidTest.apk
  type: instrumentation
  async: true
  device:
    - {model: Nexus5X, version: 26, locale: ja}
    - {model: Nexus6P, version: 23, locale: ja}
    - {model: D6603, version: 21, locale: ja}

上記のyamlをtest_lab.ymlと定義した上で下記のコマンドでtestを実行できる.

gcloud firebase test android run test_lab.yml:quick-inst-test

Androidのローカルテストまとめ

概要 Androidテスト全書なども発刊されたり、今回のDroidKaigi2019でもAndroidのテスト周りの発表が多かったように思います。 直近のプロジェクトでもテストのカバレッジ50%以上を目指してテストコードを書いているのでその中でのハマったところやどういった観点で...