Kotlin版Power Assertツール「Power-assert」の紹介
はじめに
株式会社ログラスでエンジニアをしております、中村です。
既存のアサーションライブラリを使ったテストの書き方には癖があり、検証対象のパターンごとに適切な関数を使い分ける必要があります。世の中にはアサーションライブラリの使い方だけにフォーカスした記事も数多く存在し、これらの記事にお世話になった方も多いのではないでしょうか。かく言う私もお世話になったエンジニアのうちの一人で、テストとは直接関係のないところにコストをかけていて「不毛だな〜」と感じつつも、せっせとアサーションを書いていました。
そこで、そんな悩みを一気に解決するKotlin向けPower Assertツールを紹介します。
Power-assertとは
Power-assertは、Power Assert機能を提供するKotlinコンパイラープラグインです。デバッグ情報を視覚的に表示させることで、アサーションの問題箇所を直感的に理解できるようになります。
Power-assert(Power Assert系ライブラリ)の特徴
特別な工夫なくアサーションのデバッグ情報をコードレベルで視覚的に表示します。
@Test fun test() {
val five = 5
val fifteen = 10
val twelve = 20
assert(five + fifteen == twelve)
}
実行結果:
Power-assertなし
Assertion failed
java.lang.AssertionError: Assertion failed
at org.example.AppTest...
Power-assertあり
Assertion failed
assert(five + fifteen == twelve)
| | | | |
| | | | 20
| | | false
| | 10
| 15
5
デバッグ情報として、式の評価のプロセスで生成される中間値がコードの各部に視覚的に紐付けられています。five + fifteen
がtwelve
と等価ではないことだけではなく、five
が5、fifteen
が10、five + fifteen
の結果が15であることが一目瞭然で、どこが間違えているのか即座に理解できます。
インストールと設定
Gradleでのインストール方法のみ紹介します。
build.gradle.kts
plugins {
kotlin("jvm") version "2.0.0"
// kotlin("multiplatform") version "2.0.0". // Kotlin Multiplatformでも動作します
kotlin("plugin.power-assert") version "2.0.0" // Power-assertプラグインを追加
}
@OptIn(ExperimentalKotlinGradlePluginApi::class)
powerAssert {
functions = listOf( // デバッグ情報を表示させたい関数・メソッド
"kotlin.assert",
"kotlin.require",
"kotlin.test.assertTrue",
"kotlin.test.assertEquals",
"kotlin.test.assertNull",
)
includedSourceSets = listOf("test") // デバッグ情報を表示させたいソースセット
}
Power-assertプラグインをインストールすると、プロジェクトにpowerAssert
エクステンションが作成されます。
プラグインをプロジェクトに追加するだけでPower Assert機能を使うことができますが、必要であればpowerAssert
の設定を変更してください。
functions
デバッグ情報を表示させたい関数・メソッドのパス(fully-qualified function path)を指定します。デフォルトでkotlin.assert
のみ指定されています。
コンパニオンオブジェクトに属するメソッドはXxx.Companion.yyy
で指定できます。
includedSourceSets
デバッグ情報を表示させたいソースセットを指定します。
includedSourceSets = listOf("main", "test")
とすれば、プロダクションコードでもデバッグ情報を表示させることができます。
使用例
コレクション
コレクションと取り出した要素も表示されます。
@Test fun test () {
val names = listOf("Alice", "Bob", "Carol")
val index = 2
val expeced = "Alice"
assert(names[index] == expeced)
}
assert(names[index] == expeced)
| | | |
| | | Alice
| | false
| Carol
| 2
[Alice, Bob, Carol]
オブジェクト
toString()
の結果が表示されます。
data class Vector3(val x: Float, val y: Float, val z: Float)
@Test fun test() {
val vector1 = Vector3(1.1f, -2.3f, 40.22f)
val vector2 = Vector3(20.9f, -3.0f, -82.1f)
assert(vector1 == vector2)
}
Assertion failed
assert(vector1 == vector2)
| | |
| | Vector3(x=20.9, y=-3.0, z=-82.1)
| false
Vector3(x=1.1, y=-2.3, z=40.22)
メソッドやメンバ変数へのアクセス
メンバやメソッドの戻り値も表示されます。
@Test fun test() {
val id = "半角英数字のみ、32文字未満だよ"
val regex = Regex("""[0-9A-Za-z]""")
assert(id.length < 32 && id.matches(regex))
}
assert(id.length < 32 && id.matches(regex))
| | | | | |
| | | | | [0-9A-Za-z]
| | | | false
| | | 半角英数字のみ、32文字未満だよ
| | true
| 16
半角英数字のみ、32文字未満だよ
改行されたアサーション
アサーションコードが改行されていても表示が崩れることはありません。
fun main() {
val year = 2100
assert(year % 4 == 0
&& (
year % 100 != 0 ||
year % 400 == 0
)
)
}
Assertion failed
assert(year % 4 == 0
| | |
| | true
| 0
2100
&& (
year % 100 != 0 ||
| | |
| | false
| 0
2100
year % 400 == 0
| | |
| | false
| 100
2100
)
)
自作のアサーション関数
powerAssert.functions
にパスを追加することで自作の関数・メソッドにもデバッグ情報を表示させることができます。
ただし、デバッグ情報が表示できる関数には条件があります。
// build.gradle.kts
@OptIn(ExperimentalKotlinGradlePluginApi::class)
powerAssert {
functions = listOf(
"kotlin.assert",
"kotlin.require",
"kotlin.test.assertTrue",
"kotlin.test.assertEquals",
"kotlin.test.assertNull",
"kotlin.test.assertNotNull",
"org.example.myAssert",
)
includedSourceSets = listOf("test", "main")
}
package org.example
fun myAssert(value1: String, value2: Boolean, value3: Float, lazyMessage: () -> Any = { "Assertion failed" }) {
throw IllegalArgumentException(lazyMessage().toString())
}
@Test fun test() {
val name = "Raika"
myAssert(
"Hello, $name!",
4.8 - 4.7 - 0.1 == 0.0,
Math.PI
)
}
myAssert(
"Hello, $name!",
| |
| Raika
Hello, Raika!
4.8 - 4.7 - 0.1 == 0.0,
| | |
| | false
| -3.608224830031759E-16
0.09999999999999964
Math.PI,
|
3.141592653589793
)
メリットと注意点
メリット
デバッグと複雑な条件式の確認
Power-assertは具体的な値や計算結果を視覚的に表示するため、複雑な条件式でも何が間違っているのか一目で理解できます。例えば、次のような複数の要素が絡まる条件のアサーションでも、どの部分が原因で失敗しているのか簡単に特定することが可能です。
fun main() {
val a = 5
val b = 10
val c = 15
val map = mapOf("key1" to "value1", "key2" to "value2")
assert(a + b == c && map["key1"] == "value3")
}
Assertion failed
assert(a + b == c && map["key1"] == "value3")
| | | | | | |
| | | | | | false
| | | | | value1
| | | | | {key1=value1, key2=value2}
| | | | 15
| | | true
| | 10
| 15
5
アサーションライブラリに依存せず、直感的にテストが書ける
JUnitやAssertJのようなアサーションライブラリは、失敗したアサーションのデバッグ情報を提供する強力なツールである一方、以下のデメリットがあります。
- アサーションの書き方はアサーションライブラリに大きく依存する
- アサーションライブラリで対応していないパターンのデバッグ情報が見たい場合、カスタマイズする必要がある
Power-assertであればどんなアサーションでも自動的にデバッグ情報が表示されるため、デバッグ情報を表示するためのアサーションライブラリが必要なくなります。アサーションライブラリの学習コストがなくなる上、純粋なKotlinコードだけで完結するので、プロダクションコードの感覚でテストが書けるようになります。
import org.assertj.core.api.Assertions.assertThat
fun main() {
val a = 5
val b = 10
assertThat(a + b).isEqualTo(20)
}
fun main() {
val a = 5
val b = 10
assert(a + b == 20)
}
注意点
実験版
Power-assertは記事公開時点(2024年6月24日)で実験版であり、機能の安定性や互換性に関しては注意が必要です。
使用する際は最新の情報を確認し、必要に応じてバックアップやバージョン管理を行ってください。
デバック情報が表示される関数の条件
Power-assertによるデバッグ情報はすべての関数・メソッドで表示されるわけではありません。
以下の場合にのみデバッグ情報が表示されます。
- 最後の引数の型
String
-
String
型のスーパータイプ - 上二つのどちらかを戻り値とし、引数は取らないラムダ
-
リファレンスでは
String
または() -> String
と表現されています
-
リファレンスでは
おわりに
Kotlinでのデバッグ体験を大幅に改善する非常に強力なツールであるPower-assertの紹介でした。
多くの方がアサーションライブラリ独特の書き方に苦労したことがあると思うので、サクッとPower-assertを導入して生産性を上げていきましょう!
Discussion