Android

アサーションライブラリのGoogle Truthを使ってみる

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を実装したクラスを作成して抽出条件をかいてIterableSubjectcomparingElementsUsingで指定することで実現可能だと思いますが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

-Android

© 2023 ビー鉄のブログ Powered by AFFINGER5