20190227のKotlinに関する記事は3件です。

Kotlin で JUnit 5

Kotlin で JUnit 5 を使い始めました。両者を組み合わせる事例はあまり見かけませんでしたが、よい感触を得ましたので所感を残しておこうと思います。

背景

フルスクラッチで API サーバを書く案件が始まり、トレンドに乗って Kotlin で実装することになりました。テストフレームワークは使い慣れた JUnit を採用しましたが、せっかくの機会ですので JUnit も最新 5 系に挑戦することにしました。

本記事とは直接関係ありませんが、前提とするプロジェクトは以下の通りです。

  • 全員が Java の API サーバ開発経験あり
  • Kotlin 経験はまちまち
  • 実装・テストともにすべて Kotlin
  • サーバのフレームワークは Spring

良かったこと: モダンなテストが書ける

JUnit 4 から大きく機能拡張されたことと、Kotlin による簡素な記述のおかげで、現代的で保守しやすいテストが書けると感じました。特に、以下が容易に実現できる点はありがたいです。

非 static な内部クラスでネストできる

JUnit 4 でも static な内部クラスでネストすることはできましたが、JUnit 5 では @Nested アノテーションを使うことで、static でない内部クラスをネストすることができるようになりました。この機能は 前提条件を共通化できる という意味で大変気に入っています。たとえば以下のようにクラス名とフィクスチャを対応させることで、BDD のようなテスト記述ができるようになります。

interface Subscriber {
    fun receive(message: String): Boolean
}

class Publisher(private val subscriber: Subscriber) {
    fun send(message: String) = subscriber.receive(message)
}

internal class PublisherTest {

    private lateinit var publisher: Publisher

    @Mock
    private lateinit var subscriber: Subscriber

    @BeforeEach
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        publisher = Publisher(subscriber)
    }

    @Nested
    inner class WhenSubscriberCannotReceive {

        // "Subscriber cannot receive" という状態を記述できる.
        @BeforeEach
        fun setUp() {
            whenever(subscriber.receive(any())).thenReturn(false)
        }

        @Test
        fun thenThenFailsToSend() {
            val actual = publisher.send("bye")
            assertThat(actual, equalTo(false))

            verify(subscriber).receive("bye")
        }
    }

    @Nested
    inner class WhenSubscriberReceives {

        // "Subscriber receives" という状態を記述できる.
        @BeforeEach
        fun setUp() {

            whenever(subscriber.receive(any())).thenReturn(true)
        }

        @Test
        fun thenSucceedsToSend() {
            val actual = publisher.send("hello")
            assertThat(actual, equalTo(true))

            verify(subscriber).receive("hello")
        }
    }
}

初歩的ですが、Kotlin で書く際は、Java と異なり内部クラスがデフォルトで static であるため、inner キーワードを忘れないように注意が必要です。

テストに関連するオブジェクトのスコープが柔軟に変更できるので、ヘルパーメソッドによる共通化がしやすいと感じました。Kotlin の簡潔な記述とも相性がよさそうです。

internal class FooTest {

    private lateinit var publisher: Publisher

    @Mock
    private lateinit var subscriber: Subscriber

    @BeforeEach
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        publisher = Publisher(subscriber)
    }

    @Nested
    inner class WhenSubscriberCannotReceive {

        @BeforeEach
        fun setUp() = stubSubscriber(canReceive = false)

        @Test
        fun thenThenFailsToSend() =
            assertThat(testSending("bye"), equalTo(false))
    }

    @Nested
    inner class WhenSubscriberReceives {

        @BeforeEach
        fun setUp() = stubSubscriber(canReceive = true)

        @Test
        fun thenSucceedsToSend() =
            assertThat(testSending("hello"), equalTo(true))
    }

    // ヘルパーメソッドでテストを共通化する.
    // ここから `publisher` や `subscriber` が見えているところがポイント.
    private fun stubSubscriber(canReceive: Boolean) {
        whenever(subscriber.receive(any())).thenReturn(canReceive)
    }

    private fun testSending(message: String): Boolean {
        val succeeded = publisher.send(message)
        verify(subscriber).receive(message)
        return succeeded
    }
}

Parameterized テストがとても書きやすい

Parameterized テストは、テストケースとテストロジックを分離できる手法で、特に多くの組み合わせの検証が必要なケースでは大変強力です。JUnit 4 の parameterized テストは、おまじないがエグい ためかなり苦労させられたのですが、JUnit 5 では極めて直感的に記述できるようになりました。

簡単なテストケースであればアノテーションだけでも記述できるのですが、実用上は @MethodSource@ArgumentsSource の 2 つが、手軽さと柔軟さのバランスがよいと感じています。

@MethodSource は static メソッドに記述するだけで本当にお手軽に利用できます。Java の static メソッドを参照するので、Kotlin で書く際は companion object に関数を定義して @JvmStatic を付与します。

internal class AddTest {

    @ParameterizedTest
    @MethodSource("testCases")
    fun addTest(lhs: Int, rhs: Int, expected: Int) {
        val actual = lhs + rhs
        assertThat(actual, equalTo(expected))
    }

    companion object {

        @Suppress("unused") // used by `addTest`
        @JvmStatic
        fun testCase() = listOf(
                arguments(0, 0, 0),
                arguments(1, 1, 2),
                arguments(1, -1, 0)
        )
    }
}

細かいですが、Kotlin だと Java で書くよりもテストケースを並べている感があって、相性がよさそうだと感じました。

@MethodSource もあまり不満はないのですが、気になる点を挙げるならば以下の通りです。いずれも気にしすぎかもしれませんが。

  • IntelliJ が (執筆時点では) メソッド不使用を警告してくる
    • いちいち抑制するのが面倒
  • 正しく解決されるかどうか実行されるまで分からない
    • IDE の補完が効かない
  • @JvmStatic のメタ情報が気持ち悪い
    • Kotlin なのに!

これらの問題はすべて @ArgumentsSource で解決できます。

internal class AddTest {

    @ParameterizedTest
    @ArgumentsSource(TestCase::class)
    fun addTest(lhs: Int, rhs: Int, expected: Int) {
        val actual = lhs + rhs
        assertThat(actual, equalTo(expected))
    }

    private class TestCase : ArgumentsProvider {
        override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> = Stream.of(
                arguments(0, 0, 0),
                arguments(1, 1, 2),
                arguments(1, -1, 0)
        )
    }
}

強いて気になるのは、コード量が少しだけ増えることと、Kotlin なのに Stream を強要されることでしょうか。これもご愛嬌のレベルだと思います。

例外テストが書きやすい

JUnit 5 から assertThrows が採用され、格段に例外テストが書きやすくなりました。Java でも重宝しますが、なんと Kotlin 向けの assertThrows が提供されており、大変気持ちよく記述できます。

val error = assertThrows<IOException> {
    process()
}
assertThat(error.message, equalTo("expected message"))

Java 用とパッケージを間違えないようにだけ注意が必要です。Kotlin 用は org.junit.jupiter.api.assertThrows です。

Kotlin に助けられたこと

JUnit 5 と Kotlin を組み合わせで困ることはほとんどなかったのですが、現実的には JUnit の関連ライブラリの利用において困ったこともありました。これらは別の記事で取り扱おうと思いますが、一方で Kotlin のおかげで偶然解決した問題もありましたので紹介します。

PowerMock の代わりに MockK が使える

Java では主に static メソッドのモンキーパッチとして活躍していた PowerMock ですが、残念ながら本記事執筆時点では JUnit 5 をサポートしていないようです (案件自体はあるようですが)。本プロジェクトはフルスクラッチの開発であり、かつ DI が強力な Spring を採用しましたので、基本的には PowerMock を使いたくなる状況自体を回避する方針をとっています。

しかし、以下のような例外的状況も発生しました。

  • Kotlin なので、純粋関数や拡張関数を使いたいケースももちろんある
  • サードパーティーのライブラリで static メソッドを利用せざるを得ないケースはどうしようもない

これらを解決してくれるライブラリとして、MockK を採用しました。使用感は Python の patch に似ていて、すぐ慣れることができました。以下の例は拡張関数にモックを適用していますが、純粋関数や Java の static メソッドにも同じようにモックを適用できます。

// Function.kt
fun String.extension() = "orange"

// FunctionTest.kt
internal class FunctionTest {

    @Test
    fun mockedSuccessfully() {
        assertThat("apple".extension(), equalTo("banana"))
    }

    companion object {
        private const val MOCKED_PACKAGE = "mocked.FunctionKt"

        @Suppress("unused") // IntelliJ IDEA warns...
        @JvmStatic
        @BeforeAll
        fun initialize() {
            mockkStatic(MOCKED_PACKAGE)
            every { "apple".extension() } returns "banana"
        }

        @Suppress("unused") // IntelliJ IDEA warns...
        @JvmStatic
        @AfterAll
        fun finalize() {
            unmockkStatic(MOCKED_PACKAGE)
        }
    }
}

MockK はとても多機能なため全容を研究しきれていませんが、モックライブラリの機能に頼りすぎて設計が疎かになっては元も子もありませんので、どうしようもないときだけ MockK で実現できないか調査する、という方針をとっています。

まとめ

JUnit 5 はモダンなテストが書けて大変使いやすく、Kotlin との相性もよさそうでした。MockK という思わぬメリットを享受できた点も幸運でした。両者の組み合わせについて大きなデメリットも感じませんでしたので、今後も活用していきたいと思います。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kotlin で JUnit 5 を使ってみた

Kotlin で JUnit 5 を使い始めました。両者を組み合わせる事例はあまり見かけませんでしたが、よい感触を得ましたので所感を残しておこうと思います。

背景

フルスクラッチで API サーバを書く案件が始まり、トレンドに乗って Kotlin で実装することになりました。テストフレームワークは使い慣れた JUnit を採用しましたが、せっかくの機会ですので JUnit も最新 5 系に挑戦することにしました。

本記事とは直接関係ありませんが、前提とするプロジェクトは以下の通りです。

  • 全員が Java の API サーバ開発経験あり
  • Kotlin 経験はまちまち
  • 実装・テストともにすべて Kotlin
  • サーバのフレームワークは Spring

良かったこと: モダンなテストが書ける

JUnit 4 から大きく機能拡張されたことと、Kotlin による簡素な記述のおかげで、現代的で保守しやすいテストが書けると感じました。特に、以下が容易に実現できる点はありがたいです。

非 static な内部クラスでネストできる

JUnit 4 でも static な内部クラスでネストすることはできましたが、JUnit 5 では @Nested アノテーションを使うことで、static でない内部クラスをネストすることができるようになりました。この機能は 前提条件を共通化できる という意味で大変気に入っています。たとえば以下のようにクラス名とフィクスチャを対応させることで、BDD のようなテスト記述ができるようになります。

interface Subscriber {
    fun receive(message: String): Boolean
}

class Publisher(private val subscriber: Subscriber) {
    fun send(message: String) = subscriber.receive(message)
}

internal class PublisherTest {

    private lateinit var publisher: Publisher

    @Mock
    private lateinit var subscriber: Subscriber

    @BeforeEach
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        publisher = Publisher(subscriber)
    }

    @Nested
    inner class WhenSubscriberCannotReceive {

        // "Subscriber cannot receive" という状態を記述できる.
        @BeforeEach
        fun setUp() {
            whenever(subscriber.receive(any())).thenReturn(false)
        }

        @Test
        fun thenThenFailsToSend() {
            val actual = publisher.send("bye")
            assertThat(actual, equalTo(false))

            verify(subscriber).receive("bye")
        }
    }

    @Nested
    inner class WhenSubscriberReceives {

        // "Subscriber receives" という状態を記述できる.
        @BeforeEach
        fun setUp() {

            whenever(subscriber.receive(any())).thenReturn(true)
        }

        @Test
        fun thenSucceedsToSend() {
            val actual = publisher.send("hello")
            assertThat(actual, equalTo(true))

            verify(subscriber).receive("hello")
        }
    }
}

初歩的ですが、Kotlin で書く際は、Java と異なり内部クラスがデフォルトで static であるため、inner キーワードを忘れないように注意が必要です。

テストに関連するオブジェクトのスコープが柔軟に変更できるので、ヘルパーメソッドによる共通化がしやすいと感じました。Kotlin の簡潔な記述とも相性がよさそうです。

internal class FooTest {

    private lateinit var publisher: Publisher

    @Mock
    private lateinit var subscriber: Subscriber

    @BeforeEach
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        publisher = Publisher(subscriber)
    }

    @Nested
    inner class WhenSubscriberCannotReceive {

        @BeforeEach
        fun setUp() = stubSubscriber(canReceive = false)

        @Test
        fun thenThenFailsToSend() =
            assertThat(testSending("bye"), equalTo(false))
    }

    @Nested
    inner class WhenSubscriberReceives {

        @BeforeEach
        fun setUp() = stubSubscriber(canReceive = true)

        @Test
        fun thenSucceedsToSend() =
            assertThat(testSending("hello"), equalTo(true))
    }

    // ヘルパーメソッドでテストを共通化する.
    // ここから `publisher` や `subscriber` が見えているところがポイント.
    private fun stubSubscriber(canReceive: Boolean) {
        whenever(subscriber.receive(any())).thenReturn(canReceive)
    }

    private fun testSending(message: String): Boolean {
        val succeeded = publisher.send(message)
        verify(subscriber).receive(message)
        return succeeded
    }
}

Parameterized テストがとても書きやすい

Parameterized テストは、テストケースとテストロジックを分離できる手法で、特に多くの組み合わせの検証が必要なケースでは大変強力です。JUnit 4 の parameterized テストは、おまじないがエグい ためかなり苦労させられたのですが、JUnit 5 では極めて直感的に記述できるようになりました。

簡単なテストケースであればアノテーションだけでも記述できるのですが、実用上は @MethodSource@ArgumentsSource の 2 つが、手軽さと柔軟さのバランスがよいと感じています。

@MethodSource は static メソッドに記述するだけで本当にお手軽に利用できます。Java の static メソッドを参照するので、Kotlin で書く際は companion object に関数を定義して @JvmStatic を付与します。

internal class AddTest {

    @ParameterizedTest
    @MethodSource("testCases")
    fun addTest(lhs: Int, rhs: Int, expected: Int) {
        val actual = lhs + rhs
        assertThat(actual, equalTo(expected))
    }

    companion object {

        @Suppress("unused") // used by `addTest`
        @JvmStatic
        fun testCase() = listOf(
                arguments(0, 0, 0),
                arguments(1, 1, 2),
                arguments(1, -1, 0)
        )
    }
}

細かいですが、Kotlin だと Java で書くよりもテストケースを並べている感があって、相性がよさそうだと感じました。

@MethodSource もあまり不満はないのですが、気になる点を挙げるならば以下の通りです。いずれも気にしすぎかもしれませんが。

  • IntelliJ が (執筆時点では) メソッド不使用を警告してくる
    • いちいち抑制するのが面倒
  • 正しく解決されるかどうか実行されるまで分からない
    • IDE の補完が効かない
  • @JvmStatic のメタ情報が気持ち悪い
    • Kotlin なのに!

これらの問題はすべて @ArgumentsSource で解決できます。

internal class AddTest {

    @ParameterizedTest
    @ArgumentsSource(TestCase::class)
    fun addTest(lhs: Int, rhs: Int, expected: Int) {
        val actual = lhs + rhs
        assertThat(actual, equalTo(expected))
    }

    private class TestCase : ArgumentsProvider {
        override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> = Stream.of(
                arguments(0, 0, 0),
                arguments(1, 1, 2),
                arguments(1, -1, 0)
        )
    }
}

強いて気になるのは、コード量が少しだけ増えることと、Kotlin なのに Stream を強要されることでしょうか。これもご愛嬌のレベルだと思います。

例外テストが書きやすい

JUnit 5 から assertThrows が採用され、格段に例外テストが書きやすくなりました。Java でも重宝しますが、なんと Kotlin 向けの assertThrows が提供されており、大変気持ちよく記述できます。

val error = assertThrows<IOException> {
    process()
}
assertThat(error.message, equalTo("expected message"))

Java 用とパッケージを間違えないようにだけ注意が必要です。Kotlin 用は org.junit.jupiter.api.assertThrows です。

Kotlin に助けられたこと

JUnit 5 と Kotlin を組み合わせで困ることはほとんどなかったのですが、現実的には JUnit の関連ライブラリの利用において困ったこともありました。これらは別の記事で取り扱おうと思いますが、一方で Kotlin のおかげで偶然解決した問題もありましたので紹介します。

PowerMock の代わりに MockK が使える

Java では主に static メソッドのモンキーパッチとして活躍していた PowerMock ですが、残念ながら本記事執筆時点では JUnit 5 をサポートしていないようです (案件自体はあるようですが)。本プロジェクトはフルスクラッチの開発であり、かつ DI が強力な Spring を採用しましたので、基本的には PowerMock を使いたくなる状況自体を回避する方針をとっています。

しかし、以下のような例外的状況も発生しました。

  • Kotlin なので、純粋関数や拡張関数を使いたいケースももちろんある
  • サードパーティーのライブラリで static メソッドを利用せざるを得ないケースはどうしようもない

これらを解決してくれるライブラリとして、MockK を採用しました。使用感は Python の patch に似ていて、すぐ慣れることができました。以下の例は拡張関数にモックを適用していますが、純粋関数や Java の static メソッドにも同じようにモックを適用できます。

// Function.kt
fun String.extension() = "orange"

// FunctionTest.kt
internal class FunctionTest {

    @Test
    fun mockedSuccessfully() {
        assertThat("apple".extension(), equalTo("banana"))
    }

    companion object {
        private const val MOCKED_PACKAGE = "mocked.FunctionKt"

        @Suppress("unused") // IntelliJ IDEA warns...
        @JvmStatic
        @BeforeAll
        fun initialize() {
            mockkStatic(MOCKED_PACKAGE)
            every { "apple".extension() } returns "banana"
        }

        @Suppress("unused") // IntelliJ IDEA warns...
        @JvmStatic
        @AfterAll
        fun finalize() {
            unmockkStatic(MOCKED_PACKAGE)
        }
    }
}

MockK はとても多機能なため全容を研究しきれていませんが、モックライブラリの機能に頼りすぎて設計が疎かになっては元も子もありませんので、どうしようもないときだけ MockK で実現できないか調査する、という方針をとっています。

まとめ

JUnit 5 はモダンなテストが書けて大変使いやすく、Kotlin との相性もよさそうでした。MockK という思わぬメリットを享受できた点も幸運でした。両者の組み合わせについて大きなデメリットも感じませんでしたので、今後も活用していきたいと思います。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ことりんと一緒 Springもね - 8. リポジトリ層 - 補足:H2 Database

概要 / 説明

ことりんと一緒 Springもね - 8. リポジトリ層 では、リポジトリ層を設けて、データの永続化処理を抽象化して外部の永続化領域への操作を透過的に実施できるようにしました。

今回のケースでは、外部の永続化領域はインメモリでアプリケーションに組み込みで動作する H2 Database Engine を利用していました。

この H2 Database Engine には、データベース情報を確認するためのコンソールアプリケーションが組み込まれています。そちらを利用してデータベースの状態確認を行ってみます。

前提 / 環境

ランタイムバージョン

  • Kotlin : 1.3.21
  • SpringBoot : 2.1.1.RELEASE

Spring Dependencies

  • Web
  • JDBC
  • JPA
  • H2
  • Actuator

開発環境

  • OS : Mac
  • IDE : IntelliJ IDEA
  • Build : Gradle

手順 / 解説

application.yml の設定

SpringBoot の application.yml に以下のようにH2データベースが使用できるように構成情報を定義します。

データソース

Springアプリケーションからデータベースに接続するためのデータソース定義を以下のように実施します。

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:app;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE
    username: guest
    password: guest
構成要素 内容
driver-class-name H2DBのドライバーライブラリ
org.h2.Driver
url データベース接続URL(※フォーマットについて後述)
jdbc:h2:mem:app;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE
username データベース接続ユーザ名
password データベース接続パスワード
H2データベース接続URLフォーマット

H2データベースは、インメモリで動作するアプリケーション組み込み型の使用や、リモートサーバとして稼働するサーバモード、またファイルシステムにデータを書き出す動作など様々な動作モードがとれます。それぞれの動作モードに合わせた接続URLフォーマットとなります。

動作モード URLフォーマット サンプル
組み込みモード:インメモリ(プライベート) jdbc:h2:mem: jdbc:h2:mem:
組み込みモード:インメモリ(Named) jdbc:h2:mem:<DB名> jdbc:h2:mem:app
組み込みモード:ローカルファイル jdbc:h2:[file:][<ファイルパス>]<DB名> jdbc:h2:file:/data/sample
インメモリDBのドロップ防止 DB_CLOSE_DELAY=-1 jdbc:h2:mem:app;DB_CLOSE_DELAY=-1
サーバモード:TCP接続 jdbc:h2:tcp://<サーバアドレス>[:<ポート>]/[<ファイルパス>] jdbc:h2:tcp://localhost:8084/data/sample
サーバモード:TLS接続 jdbc:h2:ssl://<サーバアドレス>[:<ポート>]/[<ファイルパス>] jdbc:h2:ssl://localhost/mem:test
VM終了時のDB接続切断 DB_CLOSE_ON_EXIT=TRUE jdbc:h2:mem:app;DB_CLOSE_ON_EXIT=TRUE

H2データベース

H2 データベースのコンソール画面を表示させるには以下の設定を行います。

spring:
  h2:
    console:
      enabled: true

H2 データベースのコンソール画面の表示

アプリケーションを起動した後、H2 データベースコンソール画面にアクセスしてみます。

$ ./gradlew bootRun

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.1.RELEASE)
:
:
:
2019-02-26 16:05:13.358  INFO 7073 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 15 endpoint(s) beneath base path '/admin'
2019-02-26 16:05:13.457  INFO 7073 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-02-26 16:05:13.460  INFO 7073 --- [           main] i.p.s.simple.SimpleApplicationKt         : Started SimpleApplicationKt in 5.577 seconds (JVM running for 5.998)
<==========---> 83% EXECUTING [2m 46s]
> :bootRun
  • http://localhost:[設定したポート番号]/h2-console へアクセスし、H2 データベースの接続ユーザ/パスワードを入力して接続を行います。

h2-console-login.png

H2-console.png

まとめ / 振り返り

SpringBoot と組み合わせて手軽に利用できるH2データベースを操作してみました。
データベースの機能自体に依存しないアプリケーション開発の場面では、インストール作業が不要で、簡単に使えるH2データベースは利用できる場面がいろいろとあるのではないでしょうか。

今回のソース

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む