AndroidXのTestではGoogleのアサーションライブラリのTruth
が使われるみたいです。
Set up project for AndroidX Test
Truth
を必ず使えというよりもAndroidXのテストサポートとしてTruth
の拡張が提供されるから、より書きやすくなるよーということだと思います。多分・・・。
どんなものかなーと思ってTruth
を使ってみました。
使い始めたばかりなのでいろいろ間違いがあると思いますので、参考程度に見ていただければと思います。
JavaでUnit Testでアサーションを書くときはAssertJ
を使っていました。
JUnitのアサーションにAssertJを使う
基本的なアサーションには触れずにオブジェクト、リスト、例外のアサーションについて書いていきたいと思います。
オブジェクトのアサーション
一番よく使うアサーションだと思います。
以下のData Classにアサーションを行っていきます。
data class Task(
val id: Int,
val title: String,
val description: String = ""
)
基本的なアサーション
Truth.assertThat(task.id).isEqualTo(1)
Truth.assertThat(task.title).isEqualTo("title")
Truth.assertThat(task.description).isEqualTo("description")
他のライブラリと比較してもあんまり変わらないと思います。
AssertJ
の場合は基本的なプリミティブ、タイプ以外にも例えばFileクラスの場合はFile専用のアサーションメソッド等が生えてきます。
今の所Truth
は基本的なプリミティブ、タイプとGuava
のタイプをサポートしているみたいです。
Known Types
applyを使ってFluentlyなアサーション
Truth
はAssertJのように複数のアサーションをメソッドチェーンで書くことはできません。
例えばAssertJ
では以下のようにかけますがTruth
ではかけません。
String content = "Hello World.";
assertThat(content).startsWith("Hello")
.endsWith("World.")
.contains(" ");
ただメソッドチェーンでつなげることはできませんがKotlinの場合スコープ関数を使うことで同様に記載することができます。
以下はapplyを使った例です。
Truth.assertThat(content).apply {
startsWith("Hello")
endsWith("World.")
contains(" ")
}
AssertThatはSubject
型(タイプ)を返すのでapply
ブロック内ではレシーバはSubject
となりアサーションが使えます。
Contextがはっきりしていて個人的には違和感はあまり感じませんでした。
上記はSubject
型にスコープ関数を生やしましたが、オブジェクトに対してスコープ関数を生やすこともできます。
task.apply {
Truth.assertThat(id).isEqualTo(1)
Truth.assertThat(title).isEqualTo("title")
Truth.assertThat(description).isEqualTo("description")
}
複数のオブジェクトをアサーションするときにブロックとして表現できるので、良いのではないかと思いました。
自分は使ったことがないのですがAssertJ
だとAssertJ Assertions Generator
でタイプ別のアサーションを作ってくれるプラグインもあるみたいです。
リストのアサーション
続いてリスト(コレクション)のアサーションです。
Truth
にはIterableのSubject
があるので基本的なアサーションは可能です。
val titles = tasks.map { it.title }
Truth.assertThat(titles).hasSize(4)
Truth.assertThat(titles)
.containsExactly("task1", "task2", "task3", "task4")
.inOrder()
さてAssertJ
だといわゆるフィルターしてアサーションみたいなことを書けます。
例えば以下だとAssertJのextractingを使ってリスト内のname
プロパティの値を取り出してアサーションしています。
アサーション対象を一時変数に書かなくても良い利点があります。
List actual = // リストを作る
assertThat(actual).extracting("name")
.hasSize(3)
.containsExactly("user1", "user3", "user5");
これをTruth
でできるか検証しました。
結果、現時点のバージョン0.43
ではAssertJほど簡単にはできないみたいです。
Correspondence
を実装したクラスを作成して抽出条件をかいてIterableSubject
のcomparingElementsUsing
で指定することで実現可能だと思いますがAssertJ
に比べると敷居はかなり高いと思われます。
ですが現在開発中と思われるバージョン0.44
でもう少し簡単に実装する手段としてTransforming
が提供されるみたいです。
GitHubのIssueはこちらになります。
残念ながら0.44
はまだリリースされていないので、GitHubからTransforming
関連のクラス・メソッドだけ抜き出してTruthTransforming
として実装しました。
// toFunction()はメソッド参照を行うための拡張関数
val transforming = TruthTransforming.transforming(Task::title.toFunction(), "has title")
Truth.assertThat(tasks).apply {
hasSize(4)
comparingElementsUsing(transforming)
.containsExactly("task1", "task2", "task3", "task4")
.inOrder()
}
taskのリストからtitleプロパティの値を抜き出してアサーションを行っています。
これが読みやすいかというのは一旦おいておきましょう・・・。
もうちょっとよい書き方があるんだろうと思います。
これでもまだ、敷居と可読性はAssertJ
が優位があると思いますが、こういう方法もあるよということで。
あと、ソースコードのコメントを参考にしているので使い方とか書き方は間違っている可能性が高いので参考程度にお願いします。
例外の検証
例外もThrowableのSubject
があるので基本的なアサーションは可能です。
個人的にAssertJ
で一番好きなのが例外のテストです。
assertThatThrownBy(() -> { Utils.extractByGender(persons, null); })
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("gender must be not null or empty.");
applyを使うと以下のようにかけます。
val exception = Exception("exception", FileNotFoundException("cause"))
Truth.assertThat(exception).apply {
isInstanceOf(Exception::class.java)
hasMessageThat().isEqualTo("exception")
// 個人的にスコープ関数 in スコープ関数はあんまり使わないようにしている・・・
hasCauseThat().isInstanceOf(FileNotFoundException::class.java)
hasCauseThat().hasMessageThat().isEqualTo("cause")
}
全く同じにはかけませんが、それでも同様な表現はできるのではないかと思います。
まとめ
現時点で認知度はAssertJ
の方があると思いますが、今後AndroidXのTest関連の状況次第ではTruth
も認知度が上がってくることが予想されます。
個人的にはもしJavaを使うならAssertJ
を使うかなーと思いますが、Kotlinに関してはスコープ関数を使うことで多少ではありますがFluentlyに書けます。
ただスコープ関数は人によって書き方が違ったりするので乱立するとAssertJ
の方が書き方を統一できるとは思います。
まだTruth
を使い始めたばかりなのでいろいろわかっていないことが多く、特にTruth
はエラー時のメッセージをわかりやすく表示するための仕組みがあるので、今後このあたりも使っていってもう少し習熟度を上げたいと思います。
またこの記事のソースコードはGitHubにプシュしてあります。
beeete2/android-examples