目的
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をインストールしたら、早速起動。
- [+ Start a new Android Studio project]を選びます。
- [Empty Activity]を選んで、[Next]をタップ。
-
[Name]に任意のプロジェクト名、[Package name]にパッケージ名を入力する
- パッケージ名は、jp.co.xxx.yyyy.zzzzのような形式にすることが推奨されています。
-
[Langage]は[Kotlin]を選ぶ(デフォルト)
-
[Minimum API level]は、エミュレーターならデフォルトで良いでしょう。実機がある場合は、実機のAPI levelに合わせます。
- API levelが分からない場合は、OSバージョンからAPI levelを以下のサイト等から調べてください。
https://developer.android.com/guide/topics/manifest/uses-sdk-element?hl=ja#ApiLevels
http://smatabinfo.jp/os/android/index.html
※端末のOSバージョンは自分で調べてくださいね。(OSバージョンにより微妙に異なりますが、だいたい、[設定]アプリの[端末情報]-[Androidバージョン]という感じで調べられます)
- API levelが分からない場合は、OSバージョンからAPI levelを以下のサイト等から調べてください。
-
[Use AndroidX artifacts]にチェックを入れる
- [Finish]をクリックして、しばし待つ。
あ、インターネットには繋がる状態にして下さいね。(初回に必要なライブラリなどを一斉にダウンロードしてくるため)
こんな感じで、activity_main.xmlとMainActivity.ktのファイルが開くはずです。
MainActivity.kt
これがメイン画面のクラスとなります。
Javaの場合、1クラス1ファイルですが、Kotlinではその制限はなく、このMainActivity.ktの中に全然違うクラスを書いていくことも出来ます。
でも、なるべく同じ機能の纏まったファイルにしておきましょう。
class MainActivity : AppCompatActivity() {
クラス宣言の部分です。
javaでいうクラスの継承(extends)には、:
を使います。
つまり、MainActivityクラスは、AppCompatActivityを継承しています。
(なお、interfaceのimplementsの場合にも、:
を使い、クラスの継承と続けて書くことが出来ます。一見してクラスの継承なのか、interfaceなのか分かりづらくはなる気がしますが、Kotlinの流儀なので慣れるしかありません)
AppCompatActivityは、AndroidXというライブラリに入っている、Activityの基本クラスです。下位互換など(マテリアルデザインetc)いろいろ融通を付けてくれる優れものですので、どんどん使っていきましょう。
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の左側に、このような階層が表示されていると思います。
resディレクトリの中に、Androidの開発ではさまざまなリソースファイルと呼ばれる物を置いていきます。この中にあるものを、Javaで解釈できるようにGenerate(自動生成)されたものが、Rクラスです。
MainActivityのonCreateで出てきたのが、R.layout.activity_main.xml
ですから、layoutフォルダを開いてみて下さい。
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]というタブがあると思いますのでタップしてみて下さい。
すると、レイアウトをプレビューすることも出来ます。
実際の開発では、こちらのプレビューモードでパーツを配置していく方が多いかと思いますが、今回は紙面ですので、[Text]モードで主に書いていきます。
プレビューで、ConstraintLayout
と、TextView
をそれぞれタップしてみると、青枠が表示されると思いますが、これが「そのViewの配置とサイズ」を表しています。
このレイアウトでは、ConstraintLayoutは画面全体の大きさで、TextViewはその中心にある、というのが見て取れると思います。
[Text]モードに戻ってみましょう。
<?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文みたいなものなので、気にしなくて良いです。
<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を起動
- [+ Create Virtual Device...]をクリック
- [Phone]を選択し、任意の解像度を選び、[Next]をクリック
- System Imageを選ぶ
- プロジェクトを作成したときに、Minimum API levelを指定していたと思うので、それ以上のAPI levelを選びます。
- 初めて選ぶAPI levelは、Downloadが必要なので、[Download]をクリックした後、ダウンロード完了を待って(時間かかります)、[Next]をクリックします。
※アプリ内課金のあるアプリなどを作成するときは、[Play Store]の欄にストアアプリのアイコンがある物を選ぶと良いです。
- [Finish]をクリック
基本的に設定はデフォルトのままで大丈夫です。
エミュレーターを起動する
- AVD ManagerのActionsにある「実行ボタン」をクリック
時間がかかりますが、ホーム画面が表示されれば起動完了です。
以下はPie(API level 28)のエミュレーターの画面です。
- 気になる人は、言語を日本語、タイムゾーンを東京に変更する
- 説明は省きます
※PlayStore入りのイメージにした人は、自動更新をオフにすると良いです。ONのままでも良いですが、たまに起動したときにひたすら更新が走るので、使えるようになるまで時間がかかってしまいます。
- PlayStoreアプリを起動する
- 右上メニューボタンをクリック
- [Googleアプリを自動更新する]のチェックをオフにする
b. 実機を使用する場合
-
[設定]アプリから、[システム]-[端末情報]とクリックし、[ビルド番号]を連続でタップする
- OS7以前の場合は、[システム]メニューが無く、直接[端末情報]のメニューがある場合があります。設定アプリ内のメニューについては、端末メーカーによっても微妙に異なることがあるので、根気よく探してください。
-
「これでデベロッパーになりました!」と表示されるまで、連続でタップする
-
[USBデバッグ]をONにする
- なんか表示されたらOKしてください
-
USBケーブルで開発マシンと接続する
- 充電しか出来ないケーブルもあります。100円ショップなどで買うとそういう物が多いようです。「データ通信対応」というものを用意してください。
- 最新の機種はTypeCの物が増えています。形状をよく確認して購入してください。
-
[USB接続の用途]という感じでダイアログが表示された場合は、[キャンセル]か、[ファイを転送する]をタップしてください
- 他のモードなどに設定してしまうと、デバッグが出来なくなります。
- Windowsでは、「ファイルを転送する」にしないと、スクリーンキャプチャーが撮れないこともあるようです。
-
下記が表示されたら、任意でチェックボックスにチェックを入れ、[OK]をタップ
- OSバージョンが低いと表示されないこともあります
※上記画面はキャプチャが撮れない状態なので(USBデバッグが有効にならないと画面キャプチャは撮れないため)、別の端末で物理的にカメラ撮影しました(笑)
c. 実行してみよう
-
AndroidStudioの[実行]または[デバッグ]ボタンをクリックする
- [実行]ボタンはデバッガーが接続しないため、デバッグメニューが利用できません。
- [デバッグ]ボタンは、ブレークポイントを貼るなどのいわゆるデバッグメニューが利用できます。
-
ターゲット選択ダイアログで、転送するデバイスを選ぶ
- [Use same selection for future launches]にチェックを入れる(任意)
- チェックを入れておくと、次回からターゲット選択ダイアログが表示されず、前回選択したデバイスに対して自動的に転送が始まります。
- [OK]をクリック
- [Use same selection for future launches]にチェックを入れる(任意)
無事に転送が完了してアプリが起動すると、このような表示になっているはずです。
2. 最初のクラス
(1) Kotlinクラスを新規追加
- 左側のプロジェクトパネルで、
app
-java
-パッケージ名
の所をクリック - メニューの [File]-[New]-[Kotlin File/Class]を選択
- 任意のクラス名を付け、[OK]をクリック。
- ここでは、
Util
としました。汎用的な便利処理を集めたUtilityクラスの作成にはいろいろ賛否あるでしょうが、私は作るの大好きです。 - [Kind]の部分は、[File]のままで良いです。
- ここでは、
(2) Utilクラスを実装する
Utilクラスを作成し、versionCode, versionNameを返す関数を作ります。
※あまり実用的な意味はないのですが、お試しコードですので大目に見て下さい(汗)
1. Utilクラスを作成し、関数宣言する
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)
と表示されていますので間違えないように。
- このファイルは、実際には、
build.gradleに下記のようなandroid{}
セクションがあるはずです。
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. 関数の実装
実装はこうなります。
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
という項目があり、開くと、次のようになっているかと思います。
build.gradleで定義したっぽい変数が並んでいますね。
今回は、定義済の物を使用していますが、実は、build.gradleに自分で好きな値を追加していき、アプリで参照することができます。
例えば、あるデバッグ用のフラグを切り替えるのを、コード上でやっていくと、ソースコードがばらけたり命名規則がバラバラになったりしがちですが、build.gradleで定義するようにしておくと、どんなフラグがあってどんな名前で、というのがとても見渡しやすくなります。
あと多いのは、開発・ステージング・本番と、APIサーバーの向き先を変えるとき、等でしょうか。
そういう場合にはProduct Flavorという仕組みを使って、ビルドするFlavorを切り替えるだけで向き先を変えられるように作ることが出来ます。
今回はそこまで複雑なアプリにはならないので使わないですが、こういうことも出来るんだなと覚えておくと良いかと思います。
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クラスに作った二つの関数ですが、値を返すだけですね。そんな場合はこんな風に書くことが出来ます。
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]を選ぶ -
getVersionCode
、getVersionName
のチェックを入れて[OK] -
../app/src/test/...
を選んで[OK]-
../app/src/androidTest/...
になっている方を選ばないこと
-
こんなファイルが開くかと思います。
class UtilTest {
@Test
fun getVersionCode() {
}
@Test
fun getVersionName() {
}
}
versionCodeは1, versionNameは"1.0"でしたから、それと比較するテストを書きます。
その前に、値の比較がassertjのやつが好きなのでそれを使います。
build.gralde(app)のdependencies
に下記を追加して下さい。
testImplementation 'org.assertj:assertj-core:3.2.0'
Gradle syncをするように言われるので、必ず実行して下さい。
※以降、build.gradleを書き換えた場合には、毎回Gradle syncが必要です。
テストの実装はこうなります。
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
を積極的に使った方がいいそうなので、多分、val
とvar
ではメモリの使い方が違うんだと思います(適当)。
ここでも型宣言がされていないのがお分かりでしょうか?
Util.getVersionCode()の戻り値がInt型なので、その結果を受け取る変数もIntだろうと、「型推論」されていることになります。分かりきったものを書く必要は無いよねということで、省略できることになっています。
繰り返しですが、C系の静的型言語の経験が長い身には、「あんた何者(型は何?)」というのが直ぐ気になってしまうのですが、、、そのうち慣れるんでしょう(多分)。なんていうんでしょうか、肩書きが真っ先に気になる典型的な日本人の悪習でしょうか?(汗)
assertThat(versionCode).isEqualTo(1)
は、versionCode
が1
と等しいか、というチェックを行い、等しければ通過し、異なればエラーを起こします。
assertThat(versionName).isEqualTo("1.0")
も同様で、versionNameが"1.0"と等しければ通過し、異なればエラーとなります。
2. テストクラスを実行
テストを実行してみます。
UtilTestクラスの宣言の左側に、緑色の再生アイコンみたいなのが表示されているのが分かりますでしょうか?これをクリックし、[Run 'UtilTest']してみます。
TestPassしてますか?
してなかったら何かおかしいです。直しましょう。
無事にPassしてると、下部のツールウィンドウ[Run]がこんな表示になるはずです。
3. テストメソッドを単独実行
各メソッドの左にも、再生アイコンのようなものが表示されているので、それをそれぞれクリックして[Run ...]してみましょう。
試しに、isEqualTo()
の値を変えてみると、テストが失敗するのが分かります。
Expected :2
Actual :1
で、期待値と実際の値が違うので失敗したことが分かりますね。
何度も書きますが、このUtilクラス、及びそのテストはほとんど無意味です。versionCode/Nameを上げる度に、テストの期待値の方を変えていかなければなりませんので、あまりよろしくないです。
今回は、「テストの体験」ということで大目に見て下さい(汗)
※サンプルで作られるExampleInstrumentedTest
も似たようなもんだし、許して下さい?(笑)
なお、テストの実行をデバッグしたいとき(ブレークポイントを貼ってstep実行とか)は、[Debug xxxTest]を選べば出来ます。
(2) InstrumentationTest
通称(?) androidTestです。
こちらは、Android端末やエミュレーターが必要となるテストです。
UIのテストだけでなく、Context
と呼ばれるものが必要になるテストは、すべてこちらに書く必要があります。
必要とならなくするためのサービス、ライブラリがいろいろありますが、後述するRobolectric以外、今回は省きます。(というかもうRobolectricあれば必要十分になりました)
Espressoというライブラリを使っていきます。
まず準備として、build.gradle(app)のdependenciesに下記のように追加しておいて下さい。
// 追加
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が開くので、下記のように編集
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が重いと、テストが場合によっては失敗することがあります)。
テストを実行すると・・・あれ、失敗しますねオカシイデスネ
失敗したテストメソッドを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'
を追加
テストに関する依存関係の部分は全部で下記のようになるはずです。
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{}
セクション内に、次のように追記します。
testOptions {
unitTests {
includeAndroidResources = true
}
}
最後に、gradle.propertiesに下記を追記します。
android.enableUnitTestBinaryResources=true
※AndroidStudio3.3以上では不要というような情報があるのですが、書かないと、私の環境ではテスト実行できませんでした。3.4以上だと不要かもしれないです。
ここまでで、Gradle Syncを必ず行って下さい。
2. MainActivityTest.ktをtestフォルダに移動する
MainActivityTest.kt
は先ほど、androidTest
下に作成しましたが、これを、Util.kt
と同じtest
フォルダに移動します。
ファイルを掴んで、パッケージ名(test)
という所にDrag&Dropで移動できます。
※androidTest
下にテストクラスファイルがあっても大丈夫なような記述がネット上には散見されますが、AndroidStudioから実行する際に、androidTest
下にあると実行するデバイスを要求されるので、Robolectricが使われていないようです。
確認ダイアログでは、[Refactor]をクリックします。他の設定はそのままで良いはずです。
ファイルを移動したら、一度[Build]メニューから、[Clean Project]と[Rebuild Project]をしておきましょう。
3. 実行してみる
MainActivityTest
クラスの左側にあるテストを実行・・・の前に、一度androidTestで作られてしまった設定を削除します。
-
ツールバーの所に、ドロイド君アイコンの付いた
MainActivityTest
と書かれた枠があるので、そこをクリックして、[Edit Configurations...]をクリック -
[Android Instrumented Tests]下にある[MainActivityTest]を選択してから、[-]ボタンをクリックして、設定を削除する
-
その後[OK]をクリック
これで実行できる準備が出来ました。
MainActivityTestクラス宣言の左にある実行ボタンを押しましょう。
最初は何か色々ダウンロードするようなので時間がかかります。
Passしましたか?
もしClassNotFound
等のエラーが出る場合は、Android Studioの再起動を試してみて下さい。(完全に終了させて下さい)
それでもまだエラーが出る場合は、[File]メニューから、[Invalidate Caches/Restart...]-[Invalidate and Restart]と実行してみて下さい。
(4) コマンドラインからテスト
さて、無事デバイスが不要なテストになったところで、テストを一括で全部実行したいとき、どうしましょう?
AndroidStudioからなら、パッケージ名(test)
を右クリックして、[Run 'Tests in xxx']で実行できます。
しかしJenkinsやテストシェル(バッチ)なんか作って一気に流したいときはどうしましょう?
勿論、コマンドラインから実行できます。
ターミナル(Macの場合)を開き、プロジェクトルートに移動します。
$> ./gradlew app:testDebug
これでパッケージ名(test)
下にあるテストはすべて実行されます。
あ、気になる人は、デフォルトで作成されていた以下のサンプルテストのファイルは、削除してしまって大丈夫です。
ExampleInstrumentedTest
ExampleUnitTest
テストは、実機が動くのを見たいという人もいるでしょうし、実機が無くても実行したいという人もいると思います。今後のテストは、androidTest
かtest
のお好きな方に追加していって貰えればいいかと思います。
テストコードは全く同じで行けるはず・・・なので・・・
まとめ
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