【kotlin】期待される例外の検査と進入禁止【楽しくプログラミング】

# オリックスバファローズ25年振りV！
　プログラム楽しんでますか？
　kotlinはプログラムそのものが楽しいですね。オリックスが優勝したんで色々上がってます。能率以外。

# 使っているのはSpek2とgoogle.Truth
　一応バージョンとか書いときますか。

- kotlin 1.5.21
- spek 2.0.17
- truth 1.1.3

```build.gradle
// （抜粋）
buildscript {
    ext {
        kotlin_version = '1.5.21'
        spek_version = '2.0.17'
        truth_version = '1.1.3'
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

    testImplementation "org.spekframework.spek2:spek-dsl-jvm:$spek_version"
    testRuntimeOnly "org.spekframework.spek2:spek-runner-junit5:$spek_version"

    testImplementation "com.google.truth:truth:$truth_version"
    testImplementation "com.google.truth.extensions:truth-java8-extension:$truth_version"
}
```

# 進入禁止を作ってみた
　ここは通ってはいけないという標識を作ってみました。

```TestUtils.kt
val `⛔` = assertThat("⛔進入禁止").isNull()
```

　こんなふざけた事が出来るんですね。kotlin楽しいです。
　プログラムコードに絵文字を使うとか**お前の頭には脳味噌のかわりにタラの白子でも詰まってるのか**と怒られそうです。オリックスが優勝したからなんだっていいじゃん。
　テストコードだからへーきへーき。うっとこのジェンキンスおじも怒らずデリバリーしてくれてるし。
　**CI/CD環境によっては分からんので真似していいかは知りません。**
　真似する人はいないと思うけど。

　ウチの若いのがこんなん書いてたら怒るけどね。お前の頭には脳味噌のかわりにタラの白子でも詰まってるのかって言って。

　で、これfunにしちゃうと、呼び出す時に```()```がいるのが嫌で、valにしてます。
　これで標識を置いたらちゃんとエラーになってくれちゃう、と。

　今回はJUnitを入れてなくてfailが使えないんでisNullとか雑に惹起させてます。優勝したから何やってもいいってことです。

```HogeTest.kt
internal class HogeTest: Spek({
    describe("ふざけてません") {
        context("入ったらダメ") {
            `⛔`
        }
    }
})
```

# 例外の種類を検査したい
　最初は以下で良しとしていたコードも、**MyException::class.javaがなんかヤダな**という謎のこだわり変遷しています。

```最初のテストコード.kt
runCatching { throw MyException() /* ← 例外が期待されるコード */}
  .onSuccess { `⛔` }
  .onFailure { assertThat(it).isInstanceOf(MyException::class.java) }

```

```謎のこだわり.kt
runCatching { throw MyException() /* ← 例外が期待されるコード */}
  .onSuccess { `⛔` }
  .onFailure { assertThat(it is MyException).isTrue() }
```

　今度はここで**MyExceptionを汎用化したい**という願望がムキムキと湧いてきました。

# T型を検査するには具象化型パラメータ(reified)を使う
　見てもらう方が早いですね。具体例です。

```具体例.kt
inline fun <reified T : Throwable> Throwable.inspection() {
    assertThat(this is T).isTrue()
}

// 呼び出し抜粋
.onFailure { it.inspection<MyException>() }
```

　reifiedを使う場合はinlineが必須になります。```this is T is true```が素敵です。チャーリーゴードンが書いた文章みたいでいい感じです。

# ここまでやるならもうちょっとやってみようかな
```もうちょっとやってみた.kt
inline fun <reified T : Throwable> inspection(closure: () -> Unit) {
    runCatching(closure)
      .onSuccess { `⛔` }
      .onFailure { assertThat(it is T).isTrue() }
}

// 呼び出し
inspection<MyException> {
    throw MyException() // ← 例外が期待されるコード
}
```

　こんな感じで😊してたわけですが、今度はMyException("ここの文言")もチェックしたくなっちゃったわけです。

# というわけで最終系
```最終系.kt
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import com.google.common.truth.Truth.assertThat

internal class 最終系: Spek({
    describe("楽しく単体テスト") {
        context("期待される例外の検査と進入禁止") {

            class MyException(message: String): Throwable(message)
            fun ここにテスト対象のコード() { throw MyException("オリックス2021リーグ優勝おめでとう！") }

            it("こんな事していいのかしら") {
                /**
                 * 例外MyExceptionの発生を期待するブロック
                 */
                例外検査.inspection<MyException> {
                    ここにテスト対象のコード()
                }
                // この検査ほにゃららを増やせばもっと細かい検証も。checkMessageContainsとか
                .checkMessage("オリックス2021リーグ優勝おめでとう！")
            }
        }
    }
})

internal class 例外検査 constructor(private val e: Throwable) {
    companion object {
        inline fun <reified T : Throwable> inspection(closure: () -> Unit): 例外検査 {
        runCatching(closure)
            .onSuccess { `⛔` }
            .onFailure {
                assertThat(it is T).isTrue()
                return 例外検査(it)
            }
            throw RuntimeException() // ← 戻り値なくて怒られるんで発砲
        }
    }

    fun checkMessage(expected: String) {
        assertThat(e.message).isEqualTo(expected)
    }
}

// ↓ 因みにこの人が評価された瞬間こけます。この例では最終系kt.classがロードされた時点
// ↓ funにしろって話だけど `⛔`() はなんか煽ってるみたいだし
// ↓ 別ソースファイルでこれだけを書いたら多分大体OKです
val `⛔` = assertThat("⛔進入禁止").isNull()

```

　クルマとプログラミングは多少の遊びゴコロが必要なんです。ほんとかよ。
　いいよ優勝したから。

　2021年レギュラーシーズンのパシフィック・リーグ優勝はオリックスバファローズでした！　おめでとう！

# 追記
　一応コピペで使えるように。自分で使おうとして面倒だったので💦

```ExpectedException.kt
import com.google.common.truth.Truth.assertThat

internal class ExpectedException constructor(private val e: Throwable) {
    companion object {
        inline fun <reified T : Throwable> inspection(closure: () -> Unit): ExpectedException {
            runCatching(closure)
                .onSuccess { fail() }
                .onFailure {
                    assertThat(it is T).isTrue()
                    return ExpectedException(it)
                }
            throw RuntimeException()
        }
    }

    fun checkMessage(expected: String) {
        assertThat(e.message).isEqualTo(expected)
    }
}

```