LoginSignup
32
30

More than 3 years have passed since last update.

Kotlin, LiveData, coroutine なんかを使って初めてのAndroidアプリを作る(1)準備編

Last updated at Posted at 2019-06-05

目的

Kotlinを使ったAndroidアプリを作ってみよう。

以下を使う。

  • AndroidX
  • LiveData(ViewModel)
  • Room
  • Kotlin coroutine
  • Firebase(Analytics, Notification, Cloud FireStore?)

頑張ってテストも書けたら書く。
EspressoRecorderは少しは使えるようになったのだろうか・・・

尚、Javaがある程度読める人を前提にしています。
(Kotlinの公式ドキュメントが、Java経験者向けにしか書かれていない・・・)
従って、オブジェクト指向がどうとかは解説しません。

準備

以下の環境を用意。

  • Android Studio 3.3.4(2019/02/21現在最新)
    https://developer.android.com/studio/?hl=ja  
    ※キャプチャは一部3.3.1の古いバージョンの場合があります

  • Java8 (現環境では1.8.0_172)

    PATHは一応通しておこう

取り敢えず必要なのは上記だけのはず。

※尚、当方のマシンはMac(10.13.6)です。Windowsの方は適宜読み替えて下さい。

1. 最初のプロジェクト

(1) まっさらなプロジェクトを作成

AndroidStudioをインストールしたら、早速起動。

android_kotlin_01.png

  • [+ Start a new Android Studio project]を選びます。
  • [Empty Activity]を選んで、[Next]をタップ。

android_kotlin_02.png

  • [Name]に任意のプロジェクト名、[Package name]にパッケージ名を入力する
    • パッケージ名は、jp.co.xxx.yyyy.zzzzのような形式にすることが推奨されています。
  • [Langage]は[Kotlin]を選ぶ(デフォルト)
  • [Minimum API level]は、エミュレーターならデフォルトで良いでしょう。実機がある場合は、実機のAPI levelに合わせます。

  • [Use AndroidX artifacts]にチェックを入れる

android_kotlin_03.png

  • [Finish]をクリックして、しばし待つ。

あ、インターネットには繋がる状態にして下さいね。(初回に必要なライブラリなどを一斉にダウンロードしてくるため)

こんな感じで、activity_main.xmlとMainActivity.ktのファイルが開くはずです。

android_kotlin_04.png

MainActivity.kt

これがメイン画面のクラスとなります。
Javaの場合、1クラス1ファイルですが、Kotlinではその制限はなく、このMainActivity.ktの中に全然違うクラスを書いていくことも出来ます。

でも、なるべく同じ機能の纏まったファイルにしておきましょう。

MainActivity.kt
class MainActivity : AppCompatActivity() {

クラス宣言の部分です。
javaでいうクラスの継承(extends)には、:を使います。
つまり、MainActivityクラスは、AppCompatActivityを継承しています。
(なお、interfaceのimplementsの場合にも、:を使い、クラスの継承と続けて書くことが出来ます。一見してクラスの継承なのか、interfaceなのか分かりづらくはなる気がしますが、Kotlinの流儀なので慣れるしかありません)

AppCompatActivityは、AndroidXというライブラリに入っている、Activityの基本クラスです。下位互換など(マテリアルデザインetc)いろいろ融通を付けてくれる優れものですので、どんどん使っていきましょう。

MaianActivity.kt
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

kotlinでの関数(メソッド)の定義には、funと付けます。
overrideはJavaではアノテーション@Overrideでした。

kotlinでの関数の引数や、変数の定義は、変数名: 型と記述します。更には、型を省略することが出来る場合もあります。静的型言語(C系)の経験が長いと、型が最初にないと、「おまえ何者!?」と気になってしまいますが、徐々に慣れるでしょう・・・(多分)

つまりこのonCreateは、基本クラスのメソッドをオーバーライドしたもので、引数にはBundle型のものを1つ取るメソッド、です。

Bundle?の最後の?が気になりますね。
これがKotlin最大の魅力、Null Safetyの言語仕様の部分です。

?が付いている変数は、nullableであり、nullチェックしていないと厳しくIDE(AndroidStudio)から警告が出ます。逆に何も付いていない変数は、non-nullですので、nullを設定して呼び出すことがそもそも出来ないため、nullチェックをする必要が無い、となります。
JavaにおけるNullPointerExceptionを、最大限に回避出来るようにした言語、と言えるでしょう。ただし、相変わらずnull値のせいでクラッシュすることはありますし、意図的にそのような実装にすることも出来ます。それはまた出てきたときに説明することにして、先に進みましょう。

super.onCreate(savedInstanceState)は、javaとおなじみですね。スーパークラス(基本クラス)のメソッドを呼び出しています。

setContentView(R.layout.activity_main)は、Activityクラスのメソッドです。これはActivityのレイアウト(見た目)を設定する処理です。

Rクラスは、自動で生成されるクラスです。
AndroidStudioの左側に、このような階層が表示されていると思います。

android_kotlin_05.png

resディレクトリの中に、Androidの開発ではさまざまなリソースファイルと呼ばれる物を置いていきます。この中にあるものを、Javaで解釈できるようにGenerate(自動生成)されたものが、Rクラスです。
MainActivityのonCreateで出てきたのが、R.layout.activity_main.xmlですから、layoutフォルダを開いてみて下さい。

android_kotlin_06.png

activity_main.xmlというのがありますね。
中身はこうなっているはずです。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

ConstraintLayoutの中に、TextViewが有るのが分かると思います。
つまり、ConstraintLayoutが親のViewで、その子どもにTextViewが一ついる、という状態です。

コードエディタの左下に、[Design]というタブがあると思いますのでタップしてみて下さい。

android_kotlin_07.png

すると、レイアウトをプレビューすることも出来ます。

android_kotlin_08.png

実際の開発では、こちらのプレビューモードでパーツを配置していく方が多いかと思いますが、今回は紙面ですので、[Text]モードで主に書いていきます。

プレビューで、ConstraintLayoutと、TextViewをそれぞれタップしてみると、青枠が表示されると思いますが、これが「そのViewの配置とサイズ」を表しています。
このレイアウトでは、ConstraintLayoutは画面全体の大きさで、TextViewはその中心にある、というのが見て取れると思います。

[Text]モードに戻ってみましょう。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

android:layout_width="match_parent"
android:layout_height="match_parent"
この二つが、「画面全体の大きさ」であることを決めています。
"match_parent"というのは、親の大きさに揃える、という設定で、ConstraintLayoutは一番親のViewですから、親の親は、つまり、端末の画面サイズそのもの、となります。

xmlns:android=や、xmlns:tools=xmlns:app=は、import文みたいなものなので、気にしなくて良いです。

activity_main.xml
    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

TextViewの方は、

android:layout_width="wrap_content"
android:layout_height="wrap_content"
となっています。"wrap_content"というのは、「コンテンツの幅(高さ)に合わせる」という指定です。今回は文字列なので、文字サイズに応じた高さ、文字列の長さに応じた幅に自動的に調整される、という設定です。

android:text="Hello World!"は、表示される文字列の設定です。勿論任意です。

app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"

この4つは、親のViewに対する設定になります。constraintと付いているので分かるとおり、親がConstraintLayoutであるために、必要な設定要素になります。
constraintをそのまま日本語訳すると、「拘束する」という意味ですが、簡単に言うと、このTextViewの各辺(上下左右)が「どのViewのどこに」くっついているか、という設定になります。
TextViewのすべての辺が、等しい力で親から引っ張られているので、親Viewの真ん中に存在している、ということになります。

ちなみに、このConstraintLayoutは比較的新しいツールなので、私もまだ使い慣れていません(汗)
これを機に使っていきたいと思います。
(それでも仕事で急ぎの時はやっぱりFrameLayoutとか、RelativeLayoutとか使ってしまいますが)

このように、Androidの画面のレイアウトは、xmlで書いていくか、勿論コード上でViewクラスなどを駆使して構成していくことも出来ます。
私はxmlで書くことに特に不便を感じていないので、xmlでやっていきます。

(2) 最初の実行

a. エミュレーターの場合

まずエミュレーターを作成します。

  • AVD Managerを起動
    avdManager.png

  • [+ Create Virtual Device...]をクリック
    avdManager2.png

  • [Phone]を選択し、任意の解像度を選び、[Next]をクリック
    avdManager3.png

  • System Imageを選ぶ

    • プロジェクトを作成したときに、Minimum API levelを指定していたと思うので、それ以上のAPI levelを選びます。
    • 初めて選ぶAPI levelは、Downloadが必要なので、[Download]をクリックした後、ダウンロード完了を待って(時間かかります)、[Next]をクリックします。 ※アプリ内課金のあるアプリなどを作成するときは、[Play Store]の欄にストアアプリのアイコンがある物を選ぶと良いです。 avdManager4.png
  • [Finish]をクリック
    基本的に設定はデフォルトのままで大丈夫です。
    avdManager5.png

作成が終わると、次のような画面になります。
avdManager6.png

エミュレーターを起動する

  • AVD ManagerのActionsにある「実行ボタン」をクリック avdManager7.png

時間がかかりますが、ホーム画面が表示されれば起動完了です。
以下はPie(API level 28)のエミュレーターの画面です。
emulator.png

  • 気になる人は、言語を日本語、タイムゾーンを東京に変更する
    • 説明は省きます

※PlayStore入りのイメージにした人は、自動更新をオフにすると良いです。ONのままでも良いですが、たまに起動したときにひたすら更新が走るので、使えるようになるまで時間がかかってしまいます。

  • PlayStoreアプリを起動する
    emulator0.png

  • 右上メニューボタンをクリック
    emulator1.png

  • [Googleアプリを自動更新する]のチェックをオフにする
    emulator2 19.30.30.png

b. 実機を使用する場合

  • [設定]アプリから、[システム]-[端末情報]とクリックし、[ビルド番号]を連続でタップする

    • OS7以前の場合は、[システム]メニューが無く、直接[端末情報]のメニューがある場合があります。設定アプリ内のメニューについては、端末メーカーによっても微妙に異なることがあるので、根気よく探してください。

    devmenu1.png

  • 「これでデベロッパーになりました!」と表示されるまで、連続でタップする

  • 画面を戻り、[開発者向けオプション]をタップ
    devmenu2.png

  • [USBデバッグ]をONにする

    • なんか表示されたらOKしてください

devmenu3.png

  • USBケーブルで開発マシンと接続する

    • 充電しか出来ないケーブルもあります。100円ショップなどで買うとそういう物が多いようです。「データ通信対応」というものを用意してください。
    • 最新の機種はTypeCの物が増えています。形状をよく確認して購入してください。
  • [USB接続の用途]という感じでダイアログが表示された場合は、[キャンセル]か、[ファイを転送する]をタップしてください

    • 他のモードなどに設定してしまうと、デバッグが出来なくなります。
    • Windowsでは、「ファイルを転送する」にしないと、スクリーンキャプチャーが撮れないこともあるようです。

    devmenu4.png

  • 下記が表示されたら、任意でチェックボックスにチェックを入れ、[OK]をタップ

    • OSバージョンが低いと表示されないこともあります

    devmenu5.jpg

    ※上記画面はキャプチャが撮れない状態なので(USBデバッグが有効にならないと画面キャプチャは撮れないため)、別の端末で物理的にカメラ撮影しました(笑)

c. 実行してみよう

  • AndroidStudioの[実行]または[デバッグ]ボタンをクリックする

    • [実行]ボタンはデバッガーが接続しないため、デバッグメニューが利用できません。
    • [デバッグ]ボタンは、ブレークポイントを貼るなどのいわゆるデバッグメニューが利用できます。

    runButton.png

  • ターゲット選択ダイアログで、転送するデバイスを選ぶ

    • [Use same selection for future launches]にチェックを入れる(任意)
      • チェックを入れておくと、次回からターゲット選択ダイアログが表示されず、前回選択したデバイスに対して自動的に転送が始まります。
    • [OK]をクリック

    runButton2.png

無事に転送が完了してアプリが起動すると、このような表示になっているはずです。

Screenshot_1558007198.png

2. 最初のクラス

(1) Kotlinクラスを新規追加

  • 左側のプロジェクトパネルで、app-java-パッケージ名の所をクリック
  • メニューの [File]-[New]-[Kotlin File/Class]を選択

スクリーンショット 2019-05-16 20.52.05.png

  • 任意のクラス名を付け、[OK]をクリック。
    • ここでは、Utilとしました。汎用的な便利処理を集めたUtilityクラスの作成にはいろいろ賛否あるでしょうが、私は作るの大好きです。
    • [Kind]の部分は、[File]のままで良いです。

スクリーンショット 2019-06-05 16.03.02.png

(2) Utilクラスを実装する

Utilクラスを作成し、versionCode, versionNameを返す関数を作ります。
※あまり実用的な意味はないのですが、お試しコードですので大目に見て下さい(汗)

1. Utilクラスを作成し、関数宣言する

Util.kt
class Util {
    fun getVersionCode(): Int {

    }

    fun getVersionName(): String {

    }
}

versionCodeは整数値ですので、Intを返す関数を宣言します。versionNameは文字列なので、Stringを返す関数を宣言します。

Kotlinには、Javaでいうプリミティブ型のint,boolean等はありません。すべてオブジェクト型です。

2. versionCode, versionNameについて

  • プロジェクトパネルの左側で、Gradle Scriptの下にあるbuild.gradle(Module:app)というのを開く。

    • このファイルは、実際には、プロジェクトフォルダ/app/下にあります。
    • プロジェクトフォルダ直下にもbuild.gradleというファイルがありますが、そちらはbuild.gradle(Project:MyKotlinApp)と表示されていますので間違えないように。

    スクリーンショット 2019-06-05 16.12.16.png

build.gradleに下記のようなandroid{}セクションがあるはずです。

app/build.gradle
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "jp.les.kasa.sample.mykotlinapp"
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    (省略)...
}
  • compileSdkVersion: このアプリをコンパイルするのに使うSDKのバージョン(API レベル)
  • applicationId: このアプリのパッケージ名(全世界でユニークである必要がある)
  • minSdkVersion: このアプリが使える一番低いSDKのバージョン(APIレベル)
  • targetSdkVersion: このアプリが動作保証する一番新しいSDKバージョン/APIレベル)
  • versionCode: このアプリのバージョンコード(数値)。ビルド番号のような物。必ずインクリメントしないと、プレイストアにおいてアップデートが出来ない。
  • versionName: バージョン名(文字列)。人間がバージョン管理しやすいように使う。

今回はこのversionCodeとversionNameを取得して返すように、先ほど作った関数を実装します。

3. 関数の実装

実装はこうなります。

Util.kt
class Util {
    companion object {
        fun getVersionCode(): Int {
            return BuildConfig.VERSION_CODE
        }

        fun getVersionName(): String {
            return BuildConfig.VERSION_NAME
        }
    }
}

companion objectは、Javaでいうところのstaticになります。クラスの実体オブジェクトが無くても、クラス名.メソッド名で使えるやつですね。

ところで、BuildConfigって何でしょう?
これは、build.gradleで定義されていた値やらが、Javaコードにジェネレートされたクラスです。(自動生成されるので、こちらが何かする必要はありません)

プロジェクトパネルの方に、generatedJavaという項目があり、開くと、次のようになっているかと思います。
kotlin010.png

build.gradleで定義したっぽい変数が並んでいますね。
今回は、定義済の物を使用していますが、実は、build.gradleに自分で好きな値を追加していき、アプリで参照することができます。
例えば、あるデバッグ用のフラグを切り替えるのを、コード上でやっていくと、ソースコードがばらけたり命名規則がバラバラになったりしがちですが、build.gradleで定義するようにしておくと、どんなフラグがあってどんな名前で、というのがとても見渡しやすくなります。
あと多いのは、開発・ステージング・本番と、APIサーバーの向き先を変えるとき、等でしょうか。
そういう場合にはProduct Flavorという仕組みを使って、ビルドするFlavorを切り替えるだけで向き先を変えられるように作ることが出来ます。
今回はそこまで複雑なアプリにはならないので使わないですが、こういうことも出来るんだなと覚えておくと良いかと思います。

productFlavorsのサンプル
    flavorDimensions 'api'
    productFlavors {
        develop {
            dimension = 'api'
            buildConfigField("String", "WEBAPI_URL", "\"http://dev.example.com/api/\"")
        }
        product {
            dimension = 'api'
            buildConfigField("String", "WEBAPI_URL", "\"http://example.com/api/\"")
        }
        staging {
            dimension = 'api'
            buildConfigField("String", "WEBAPI_URL", "\"http://stg.example.com/api/\"")
        }
    }

上記のサンプルだと、developフレーバーでビルドしたときには、BuildConfig.WEBAPI_URLには"http://dev.example.com/api/"が入ってビルドされ、productフレーバーでビルドしたときには、"http://example.com/api/"が入ってビルドされます。

ちょっと脱線しました。戻ります。

さて、今回作成した2つのメソッドははっきり言って実用性はありません。だってversionCodeが欲しければ、そのままそこにBuildConfig.VERSION_CODEを書けばいいだけですから。
一応これは、次のテストを書くための苦肉の策なので、大目に見て下さい(汗)

※Eclipse時代のAndroid開発をご存じの方は、versionCode/Nameを取得するには、わざわざPackageManagerを介していたのをご存じかと思います。それにはContextが必要で、コードももう少し行数が必要だったので、このように汎用クラスの便利メソッドとして出していた実装者も多いのでは無いかと思いますが、もはやその必要はないということですね。

(3) Kotlinらしく実装する

Utilクラスに作った二つの関数ですが、値を返すだけですね。そんな場合はこんな風に書くことが出来ます。

Util.kt
fun getVersionCode() = BuildConfig.VERSION_CODE
fun getVersionName() = BuildConfig.VERSION_NAME

スッキリ書けて良い感じ。
あ、関数の戻り値の型宣言が無くなりました。これは、返しているのが明らかにInt/Stringなのだから、わざわざ書かなくても型推論してくれるため、省略できるから書いてない、ということになります。

3. 最初のテスト

(1) JUnitTest

Android端末(エミュレーターでもいいですが)を必要としない、ピュアなJavaのテストです。
ロジック系の確認に使います。
今回は、あまり意味の無さそうなversionCode/Nameを取得する関数を作りましたが、実はこのテストを書くためでした。(他に簡単にテストを書けるネタが思いつかなかった)

1. JUnitTestを書く

テストメソッド名には、日本語が使えるのですが、AndroidStudioでテストするにはとても大きな不具合があって、 メソッド名が日本語だと、該当テストメソッドを「単独実行」できない 、というものがあります。これ、テストを一括実行した後で、エラーが出たものだけ再テストとかするときにとっても不便なんですよね・・・早く直してくれないかなあ。

従って、長くなりますが英語で書いていくことにします。

  • Utilクラスの宣言部分でoption+Enterキー押下(Mac版)で表示されるポップアップで、[Create test]を選ぶ

    kotlin_test001.png

  • getVersionCodegetVersionNameのチェックを入れて[OK]

    kotlin_test002.png

  • ../app/src/test/...を選んで[OK]

    • ../app/src/androidTest/...になっている方を選ばないこと

    kotlin_test003.png

こんなファイルが開くかと思います。

UtilTest.kt
class UtilTest {

    @Test
    fun getVersionCode() {
    }

    @Test
    fun getVersionName() {
    }
}

versionCodeは1, versionNameは"1.0"でしたから、それと比較するテストを書きます。

その前に、値の比較がassertjのやつが好きなのでそれを使います。
build.gralde(app)のdependenciesに下記を追加して下さい。

app/build.gradle
 testImplementation 'org.assertj:assertj-core:3.2.0'

Gradle syncをするように言われるので、必ず実行して下さい。
※以降、build.gradleを書き換えた場合には、毎回Gradle syncが必要です。

テストの実装はこうなります。

UtilTest.kt
import org.junit.Test
import org.assertj.core.api.Assertions.assertThat


class UtilTest {

    @Test
    fun getVersionCode() {
        val versionCode = Util.getVersionCode()
        assertThat(versionCode).isEqualTo(1)
    }

    @Test
    fun getVersionName() {
        val versionName = Util.getVersionName()
        assertThat(versionName).isEqualTo("1.0")
    }
}

valは変数宣言するときに付けます。この変数は、値を上書きできません。javaでいうfinalですかね。
後で値を上書きしたい場合には、varを使います。
javaの時はあまり意識して「この変数後で上書きするっけ」等考えず、finalを付けることもほとんど私は無かったのですが(汗)、Kotlinでは考えながら使っていく事になります。
valを積極的に使った方がいいそうなので、多分、valvarではメモリの使い方が違うんだと思います(適当)。

ここでも型宣言がされていないのがお分かりでしょうか?
Util.getVersionCode()の戻り値がInt型なので、その結果を受け取る変数もIntだろうと、「型推論」されていることになります。分かりきったものを書く必要は無いよねということで、省略できることになっています。
繰り返しですが、C系の静的型言語の経験が長い身には、「あんた何者(型は何?)」というのが直ぐ気になってしまうのですが、、、そのうち慣れるんでしょう(多分)。なんていうんでしょうか、肩書きが真っ先に気になる典型的な日本人の悪習でしょうか?(汗)

assertThat(versionCode).isEqualTo(1)は、versionCode1と等しいか、というチェックを行い、等しければ通過し、異なればエラーを起こします。
assertThat(versionName).isEqualTo("1.0")も同様で、versionNameが"1.0"と等しければ通過し、異なればエラーとなります。

2. テストクラスを実行

テストを実行してみます。
UtilTestクラスの宣言の左側に、緑色の再生アイコンみたいなのが表示されているのが分かりますでしょうか?これをクリックし、[Run 'UtilTest']してみます。

kotlin_test004.png

TestPassしてますか?
してなかったら何かおかしいです。直しましょう。

無事にPassしてると、下部のツールウィンドウ[Run]がこんな表示になるはずです。
kotlin_test_006.png

3. テストメソッドを単独実行

各メソッドの左にも、再生アイコンのようなものが表示されているので、それをそれぞれクリックして[Run ...]してみましょう。

kotlin_test005.png

試しに、isEqualTo()の値を変えてみると、テストが失敗するのが分かります。
kotlin_test_007.png

Expected :2
Actual :1

で、期待値と実際の値が違うので失敗したことが分かりますね。

何度も書きますが、このUtilクラス、及びそのテストはほとんど無意味です。versionCode/Nameを上げる度に、テストの期待値の方を変えていかなければなりませんので、あまりよろしくないです。
今回は、「テストの体験」ということで大目に見て下さい(汗)

※サンプルで作られるExampleInstrumentedTestも似たようなもんだし、許して下さい?(笑)

なお、テストの実行をデバッグしたいとき(ブレークポイントを貼ってstep実行とか)は、[Debug xxxTest]を選べば出来ます。

(2) InstrumentationTest

通称(?) androidTestです。
こちらは、Android端末やエミュレーターが必要となるテストです。
UIのテストだけでなく、Contextと呼ばれるものが必要になるテストは、すべてこちらに書く必要があります。
必要とならなくするためのサービス、ライブラリがいろいろありますが、後述するRobolectric以外、今回は省きます。(というかもうRobolectricあれば必要十分になりました)

Espressoというライブラリを使っていきます。

まず準備として、build.gradle(app)のdependenciesに下記のように追加しておいて下さい。

app/build.gradle
   // 追加
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test:rules:1.2.0'

※androidxのテストライブラリが頻繁にバージョンが上がっているため、ここは随時変わる可能性があります。

1. MainActivityのテストを作る

MainActivityには、現在、"Hello World!"と表示されるはずなので、それをテストしましょう。

  • MainActivityのクラス宣言のところで、option+Enterキーを押下(Mac版)し、[Create test]をクリック
  • チェックは何も入れずに[OK]をクリック
  • 今回は、../app/src/androidTest/...を選んで[OK]をクリック
  • MainActivityTest.ktが開くので、下記のように編集
MainActivityTest.kt
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.ActivityTestRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MainActivityTest {
    @get:Rule
    val activityRule = ActivityTestRule(MainActivity::class.java)

    @Test
    fun helloWorld() {
        onView(withText("Hello world!")).check(matches(isDisplayed()))
    }
}

※import文まで載せていますので、間違いが無いかよく確認して下さい。

Activityのテストクラスには、@RunWith(AndroidJUnit4::class)というアノテーションを付けていきます。
そして、テストメソッドには、一つ一つ、@Testというアノテーションを付けていきます。

@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java)

の部分は、Activityクラスをテストするときの、テストするActivityクラスなどを指定しているコードです。

onView(withText("Hello world!")).check(matches(isDisplayed()))

ここのコードが、Espressoというライブラリを用いたテストコード部分です。
"Hello world!"という文字列が表示されたViewが、isDisplayed(表示されている)かどうかチェックしています。

2. UIテストを実行する

JUnitテストの時と同じく、MainActivityTestクラス宣言の左横にある三角形の再生ボタンのような実行ボタンを押すと、テストが走ります。実機かエミュレーターが必要です。実機ならば接続済みであること、エミュレーターなら起動してアプリのアップデートなどが終わっていることを確認しましょう(そういうプロセスでOSが重いと、テストが場合によっては失敗することがあります)。

テストを実行すると・・・あれ、失敗しますねオカシイデスネ

kotlin_test009.png

失敗したテストメソッドをRunウィンドウでクリックすると、エラーメッセージが出ています。

No views in hierarchy found matching: with text: is "Hello world!"

"Hello world!"って書かれたViewが無いよ、と言ってます。

レイアウトxmlを確認すると・・・

android:text="Hello World!"

アウチ! typoですね。wが大文字でした。(迫真の演技)
こういうときはコピペすべきですね。

ということで、withText("Hello World!")に書き直して、実行します。

無事、通過しました。

(3) 実機を使わないでUIのテスト

先ほどのテストでは、テストを走らせると実機かエミュレーターに、一瞬画面が起動したのが分かったと思います。
これが何を意味するかというと、テストする際に必ずデバイス(実機またはエミュレーター)を必要とする、ということになります。

UIのテストをするんだから当然でしょ、とも思いますが・・・
これが実に面倒なんです。
特に、画面は出さないけどContextを必要とするテスト(SQLiteとか)なんかにも、ロジックテストしか書いてないのに、デバイスが必要になってしまう。JenkinsなんかのCIでTestを回したいときに非常に不便です。
また、いちいち1つのテストの度に画面を起動するので、テストの実行そのものに時間がかかるという問題点もあります。(私が業務で作った200以上のGUIテストのあるアプリは、全テストの実行に2-3時間かかります)

ということで、編み出されたのが、Robolectricというものです。これはデバイスが無くとも、なんか色々JVMでエミュレートしてくれる物で便利そうではあったのですが、サードパーティー製と言うことで敷居が高く感じていた人もいるでしょう。
が、なんとこのRobolectricが4.0以上から、androidxのテストライブラリに組み込まれることになりました。というかRobolectricの方がandroidxをサポートしたと言うべきか。

ということで、早速使ってみたいと思います。

1. Robolectricを使えるようにする

まず、build.gradle(app)のdependenciesを書き換えます。

  • androidTestImplementationの部分を、testImplementationにすべて置き換える
  • testImplementation 'org.robolectric:robolectric:4.3'を追加

テストに関する依存関係の部分は全部で下記のようになるはずです。

app/build.gradle
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.assertj:assertj-core:3.2.0'
    testImplementation 'androidx.test.ext:junit:1.1.1'
    testImplementation 'androidx.test:runner:1.2.0'
    testImplementation 'androidx.test:rules:1.2.0'
    testImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    testImplementation 'org.robolectric:robolectric:4.3'

次に、同じくbuild.gradleのandroid{}セクション内に、次のように追記します。

app/build.gradle
    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }

最後に、gradle.propertiesに下記を追記します。

gradle.properties
android.enableUnitTestBinaryResources=true

※AndroidStudio3.3以上では不要というような情報があるのですが、書かないと、私の環境ではテスト実行できませんでした。3.4以上だと不要かもしれないです。

ここまでで、Gradle Syncを必ず行って下さい。

2. MainActivityTest.ktをtestフォルダに移動する

MainActivityTest.ktは先ほど、androidTest下に作成しましたが、これを、Util.ktと同じtestフォルダに移動します。
ファイルを掴んで、パッケージ名(test)という所にDrag&Dropで移動できます。
kotlin_test010.png

androidTest下にテストクラスファイルがあっても大丈夫なような記述がネット上には散見されますが、AndroidStudioから実行する際に、androidTest下にあると実行するデバイスを要求されるので、Robolectricが使われていないようです。

確認ダイアログでは、[Refactor]をクリックします。他の設定はそのままで良いはずです。
kotlin_011.png

ファイルを移動したら、一度[Build]メニューから、[Clean Project]と[Rebuild Project]をしておきましょう。
kotlin_012.png

3. 実行してみる

MainActivityTestクラスの左側にあるテストを実行・・・の前に、一度androidTestで作られてしまった設定を削除します。

  • ツールバーの所に、ドロイド君アイコンの付いたMainActivityTestと書かれた枠があるので、そこをクリックして、[Edit Configurations...]をクリック

    kotlin_test013.png

  • [Android Instrumented Tests]下にある[MainActivityTest]を選択してから、[-]ボタンをクリックして、設定を削除する

    kotlin_test014.png

  • その後[OK]をクリック

これで実行できる準備が出来ました。
MainActivityTestクラス宣言の左にある実行ボタンを押しましょう。

最初は何か色々ダウンロードするようなので時間がかかります。

Passしましたか?
もしClassNotFound等のエラーが出る場合は、Android Studioの再起動を試してみて下さい。(完全に終了させて下さい)
それでもまだエラーが出る場合は、[File]メニューから、[Invalidate Caches/Restart...]-[Invalidate and Restart]と実行してみて下さい。

kotlin_015.png

(4) コマンドラインからテスト

さて、無事デバイスが不要なテストになったところで、テストを一括で全部実行したいとき、どうしましょう?
AndroidStudioからなら、パッケージ名(test)を右クリックして、[Run 'Tests in xxx']で実行できます。

しかしJenkinsやテストシェル(バッチ)なんか作って一気に流したいときはどうしましょう?

勿論、コマンドラインから実行できます。
ターミナル(Macの場合)を開き、プロジェクトルートに移動します。

$> ./gradlew app:testDebug

これでパッケージ名(test)下にあるテストはすべて実行されます。

あ、気になる人は、デフォルトで作成されていた以下のサンプルテストのファイルは、削除してしまって大丈夫です。

ExampleInstrumentedTest
ExampleUnitTest

テストは、実機が動くのを見たいという人もいるでしょうし、実機が無くても実行したいという人もいると思います。今後のテストは、androidTesttestのお好きな方に追加していって貰えればいいかと思います。
テストコードは全く同じで行けるはず・・・なので・・・

まとめ

AndroidStudioでプロジェクトを新規作成し、簡単なテストコードを実装することを学びました。

ここまでの状態のプロジェクトをGithubにpushしてあります。
https://github.com/le-kamba/qiita_pedometer/tree/feature/qiita_01

予告

これで準備編は終わりです。
次回から、いよいよアプリらしい機能を作っていきましょう。

予定では、「歩数計記録アプリ」にしていこうと思います。
まずは、Androidの標準的なUIを使って画面を作り、データベースに保存し、表示する、というところまで予定しています。

参考ページなど

Robolectricの一番簡単なサンプルはここを参考にしました。
https://proandroiddev.com/robolectric-testing-with-androidjunitrunner-86292bceef25

32
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
30