Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to Test Server-side Kotlin #kotlinfest

How to Test Server-side Kotlin #kotlinfest

2018/8/25 Kotlin Fest 2018 での発表資料です

発表者2名が作ったツール:

- https://github.com/maeharin/factlin
- https://github.com/suusan2go/kotlin-fill-class

Hidenori Maehara

August 25, 2018
Tweet

More Decks by Hidenori Maehara

Other Decks in Programming

Transcript

  1. How to Test Server-side Kotlin
    #kotlinfest
    エムスリー株式会社 前原 秀徳 @maeharin
    エムスリー株式会社 鈴木 健太 @suusan2go
    2018/8/25 Kotlin Fest 2018
    1

    View full-size slide

  2. 自己紹介: 前原 秀徳
    ● GitHub / Twitterは @maeharin
    ● 2014年5月にエムスリーに入社
    ● エンジニアチームリーダー、グループ会社の取締役
    を歴任後、2017年5月にサーバーサイドKotlinを用
    いたレガシーシステムのリニューアルプロジェクトを
    立ち上げ
    ● 自慢はブログ記事がはてなブックマーク1200を超え
    たこと
    2

    View full-size slide

  3. 自己紹介: 鈴木 健太
    ● GitHub / Twitterは @suusan2go
    ● 2017年11月に入社して、リニューアルプロジェクトに
    参戦
    ● 前職では主にRails触ってました
    ● リニューアルのプロジェクトでは、KotlinでAPIサーバ
    書きつつ、フロントエンドはNuxt.js使って実装みたい
    なことを担当
    ● 自慢は娘(2歳)が可愛いことです。
    3

    View full-size slide

  4. エムスリーご紹介
    ・医療に関するWebサービスを多数展開
    ・全世界で約400万人の医師会員
    ・日本で約25万人の医師会員
    4

    View full-size slide

  5. Kotlinイン・アクションの翻訳陣が技術顧問・エンジニアフェロー
    エムスリー株式会社
    技術顧問
    藤原 聖
    エムスリー株式会社
    エンジニアフェロー
    長澤 太郎 5

    View full-size slide

  6. お話する内容
    ● 10年前のレガシーシステムをサーバサイドKotlinでリプレイスしました。
    その中で培ったKotlinのテストに関する話をします。
    ● リプレイスしたレガシーシステムは2つ
    ○ 1. 医師のキャリア支援システム
    ○ 2. 薬剤師のキャリア支援システム
    ○ 事業規模感: 年間売上合わせて100億円超
    ○ システムの規模感: それぞれテーブル数150〜200程度
    ○ いずれのシステムも10年前の技術スタック(独自Java FW、ViewはXSLT)
    6

    View full-size slide

  7. 当然ながら...
    7
    テストがない

    View full-size slide

  8. テストのあるシステムへ
    ● APIサーバをKotlin + Spring Boot、フロントをRailsとVue.js(Nuxt.js)で
    リプレイス
    ○ テストのあるシステムへ
    ○ 2017年5月から開始。リリースはほぼ完了
    ○ 約1500ケースのテストをKotlinで書いてきた
    8

    View full-size slide

  9. 医師キャリア支援システム リニューアル後
    REST APIサーバー
    9

    View full-size slide

  10. 薬剤師キャリア支援システム リニューアル後
    REST APIサーバー
    10

    View full-size slide

  11. お話する内容
    ● 1部: Server-Side Kotlin testing libraries(前原)
    ○ サーバーサイドKotlin開発時に有用な主要テストライブラリを紹介
    ○ Kotlinで使う際のTipsやハマりポイントも紹介
    ● 2部: How do we test Server-Side Kotlin(鈴木)
    ○ 実際のプロジェクトでどのようにテストを書いていたかを紹介
    ● 3部: Our test tools for Kotlin(鈴木)
    ○ テストのために発表者が作ったツールを紹介
    11

    View full-size slide

  12. お話する内容
    今回の発表の対象範囲
    サーバーサイドKotlinの単体テス
    ト、DBに接続したインテグレーション
    テストについてお話します。
    E2Eテストの話やAndroidに特化し
    たテストの話は行いません。
    12
    アジャイルテスト -高品質を追求するアジャイルチームにおけるテストの視点
    - 増田 聡 氏 P8より引用
    https://www.slideshare.net/satoshimasuda/ss-3241717
    アジャイルテストの4象限

    View full-size slide

  13. 1部
    Server-Side Kotlin testing libraries
    ● サーバーサイドKotlin開発時に有用な主要テストライブラリを紹介
    ● Kotlinで使う際のTipsやハマりポイントも紹介
    ● ライブラリ選定の際の参考になれば嬉しいです
    13
    ※ 各ライブラリの最新verは2018/8/24時点の情報

    View full-size slide

  14. 主要テスティングライブラリ
    をざっと分類
    ※もちろんこれ以外にも
    様々なライブラリが存在
    赤色背景:
    本資料で解説する分類
    青字:
    私達が使ったライブラリ
    14

    View full-size slide

  15. テスティングフレームワーク
    15

    View full-size slide

  16. JUnit4
    ● https://github.com/junit-team/junit4
    ● 最新ver: 4.12
    ● Javaのテストライブラリのデファクトスタンダード
    ● アサーションライブラリであるhamcrestが組み込まれている
    ● 実績が多く、各種WAFの殆どが対応している
    16

    View full-size slide

  17. JUnit4 - Spring Bootのテスト用スターターキットもJUnit4
    ● spring-boot-starter-test(ver2.0.4.RELEASE)
    17

    View full-size slide

  18. JUnit4 - Kotlinでも問題なく使用することができる
    ● @Testなどのアノテーション含め、Kotlinでも問題なく使用可能
    ● 一部アノテーションはKotlinで使う時少しだけ留意点あり(後述)
    class SampleTest {
    @Test
    fun `1 + 2 は 3`() {
    val actual = sum(1, 2)
    assertEquals(3, actual)
    }
    18

    View full-size slide

  19. JUnit4 - Kotlin tips: JUnit4の@RuleをKotlinのプロパティにつけるとエラーになる
    ● Kotlinのvalからはprivateなフィールドとpublicなgetterが生成される
    ● @Ruleはそのままだと、フィールドに付与される(*)
    ○ * @Ruleの@Targetにより導かれる。詳細はKotlin公式Doc参照
    ○ http://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targets
    ● @Ruleをつけるフィールドはpublicでなければいけないが、上記の通りフィール
    ドがprivateなので、must be publicというエラーになる
    ○ https://junit.org/junit4/javadoc/4.12/org/junit/Rule.html
    class SampleTest {
    @Rule val myRule = MyRule()
    // org.junit.internal.runners.rules.ValidationError: The @Rule
    'myRule' must be public.
    19

    View full-size slide

  20. JUnit4 - 参考: Intellij IDEAでShow Kotlin Bytecodeした結果の抜粋
    // Show Kotlin Bytecodeの結果抜粋
    public final class SampleTest {
    private final LMyRule; myRule
    @Lorg/junit/Rule;()
    public final getMyRule()LMyRule;
    20
    privateなフィールドと
    publicなgetterが生成されている
    @Ruleはprivateなフィールドの方
    に付与されている

    View full-size slide

  21. JUnit4 - 対策1: @JvmFieldをつける
    ● @JvmFieldをつけるとpublicなフィールドが生成されるようになるため、
    正常に動作するようになる
    // Kotlin
    class SampleTest {
    @JvmField @Rule val myRule = MyRule()

    // Show Kotlin Bytecodeの結果抜粋
    public final class SampleTest {
    public final LMyRule; myRule
    @Lorg/junit/Rule;()
    21

    View full-size slide

  22. JUnit4 - 対策2: @get:Ruleとする
    ● @get:Ruleとするとgetterへのアノテーションであることを明示できる。@Rule
    はpublicなgetterにつけても動作するため、これでもOK
    ● https://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targets
    // Kotlin
    class SampleTest {
    @get:Rule val myRule = MyRule()

    // Show Kotlin Bytecodeの結果抜粋
    public final class SampleTest {
    private final LMyRule; myRule
    public final getMyRule()LMyRule;
    @Lorg/junit/Rule;()
    22

    View full-size slide

  23. JUnit4 - Kotlin tips:@BeforeClassなどJavaのstaticメソッドにつけるアノテーション
    ● companion objectのメソッドに@BeforeClassなどのアノテーションをつ
    けるだけでは、何も実行されない
    ● @BeforeClassはJavaのpublicなstaticメソッドにつけるアノテーション
    だが、上記ではpublicなstaticメソッドとしては認識されていないから
    (Companionという参照を介する必要がある)
    class SampleTest {
    companion object {
    @BeforeClass fun foo() { println("foo") } // 実行されない
    }
    @Test fun `test 1`() {}
    @Test fun `test 2`() {}
    23

    View full-size slide

  24. JUnit4 - 対策: @JvmStaticをつける
    ● @JvmStaticをつける。これによりJava側からstaticメソッドとして呼び出
    せるようになる
    ● 上記の例だとSampleTest.foo()として呼び出せるようになるので、JUnit
    が正常に動作する
    class SampleTest {
    companion object {
    @JvmStatic @BeforeClass fun foo() { println("foo") }
    }
    @Test fun `test 1`() {}
    @Test fun `test 2`() {}
    24

    View full-size slide

  25. JUnit4 - Kotlin tips:@DataPointsなどJavaのstaticフィールドにつけるアノテーション
    ● companion objectのプロパティにするだけではエラー
    ● Java側からはprivateなstaticフィールドとして扱われるので、msut be
    publicというエラーになる
    @RunWith(Theories::class)
    class TheoriesTest {
    companion object {
    @DataPoints val values = listOf(
    Data(1, 2, 3),
    Data(1, -2, -1)
    )
    // DataPoint field values must be public
    25

    View full-size slide

  26. @RunWith(Theories::class)
    class TheoriesTest {
    companion object {
    @JvmField @DataPoints val values = listOf(
    Data(1, 2, 3),
    Data(1, -2, -1)
    )
    }
    JUnit4 - 対策: @JvmFieldをつける
    ● @JvmFieldをつける。するとJava側からはpublicなstaticフィールドとし
    て扱えるようになるので、JUnitが正常に動作する
    26

    View full-size slide

  27. JUnit4 - KotlinでもJUnit4は問題なく使える
    ● Javaとの相互運用でハマったらIntellij IDEAでShow Kotlin Bytecodeするとヒ
    ントがあるかも
    ● Tools > Kotlin > Show Kotlin Bytecode 27

    View full-size slide

  28. JUnit5
    ● https://github.com/junit-team/junit5
    ● 最新ver: 5.2.0
    ● JUnit4の後継だが、JUnit4とは全く異なるフレームワーク
    ○ Lambda式対応。JUnit4のRuleやテストランナーの廃止など
    ○ JUnit Vintageを使えばJUnit4のテストケースの実行も可能
    28

    View full-size slide

  29. // Springが提供するJUnit5用拡張
    @ExtendWith(SpringExtension::class)
    @SpringBootTest
    class UserServiceTest {
    @Autowired lateinit var userService: UserService
    @Autowired lateinit var dataSource: DataSource
    JUnit5 - Spring5はJUnit5を公式サポート
    ● https://docs.spring.io/spring/docs/current/spring-framework-refer
    ence/testing.html#integration-testing-annotations-junit-jupiter
    29

    View full-size slide

  30. import org.junit.jupiter.api.assertAll
    class Junit5BuildinKotlinHelperTest {
    @Test
    fun `junit5のassertAllをKotlinで実行`() {
    assertAll("number",
    { assertEquals(1, 1) },
    { assertEquals(2, 2) }
    )
    JUnit5 - JUnit5はKotlin用のヘルパーを提供している
    ● org.junit.jupiter.apiパッケージのトップレベル関数としてKotlinのヘル
    パーが定義されている
    ○ https://junit.org/junit5/docs/current/user-guide/
    30

    View full-size slide

  31. Spek
    ● https://github.com/spekframework/spek
    ● 現在2.x準備中
    ○ 1系 最新ver: 1.2.0
    ○ 2系 最新ver: 2.0.0-alpha.1
    ● Kotlinで書かれたテスティングフレームワーク。BDDスタイル
    ● JetBrainsの中の人が作っていた
    ● アサーションは含まないので、好きなアサーションライブラリと組み合わ
    せて使う
    31

    View full-size slide

  32. class SampleTest: Spek({
    describe("userが0の時") {
    val repo = UserMemoryRepository()
    it("ユーザー数は0") {
    assertEquals(0, repo.count())
    }
    it("存在しないuserを取得しようとするとnull") {
    assertNull(repo.findById(1))
    }
    }
    32
    Spek - コード例

    View full-size slide

  33. Spek - Spek 2.xは...
    ● KotlinConf 2017の発表(Testing Kotlin at Scale: Spek)では、「1.xは
    使わず2.xを待った方がよく、2.xは2017年中に出そう」とのことであった
    が、まだ安定版はリリースされていない(2018/8/24の時点では、
    v2.0.0-alpha.1)
    ○ https://www.youtube.com/watch?v=R425cc6XrvA
    33

    View full-size slide

  34. KotlinTest
    ● https://github.com/kotlintest/kotlintest
    ● 最新ver: 3.1.9
    ● Kotlinで書かれたBDDテスティングフレームワーク
    ● 様々な書き方を選べる
    ● アサーションも含んでいる。JUnitをテスティングフレームワークとして使
    い、アサーションにKotlinTestを使うという手もある
    ● Kotlin標準のkotlin.testシリーズとは別物
    34

    View full-size slide

  35. class SampleTest: StringSpec() {
    init {
    "userが0の時" {
    val repo = UserMemoryRepository()
    "ユーザー数は0" should {
    repo.count() shouldBe 0
    }
    "存在しないuserを取得しようとするとnull" should {
    repo.findById(1) shouldBe null
    }
    35
    KotlinTest - コード例

    View full-size slide

  36. KotlinTest - Spring用の拡張も用意されている
    ● https://github.com/kotlintest/kotlintest/blob/master/doc/reference.md#spring
    36

    View full-size slide

  37. 現在のIntellij IDEAでは
    クラスごとにテスト実行は
    できるが、テストケースごとには実
    行できない模様
    ※ 以下のverで確認
    ・Intellij IDEA ULTIMATE 2018.2
    ・KotlinTest 3.1.9
    37
    KotlinTest - Intellij IDEAからのテスト実行

    View full-size slide

  38. その他のテスティングフレームワーク
    ● TestNG
    ○ https://github.com/cbeust/testng
    ○ JUnit4と同じく歴史のあるテスティングフレームワーク
    ● Spock
    ○ https://github.com/spockframework/spock
    ○ groovy製
    ○ power-assertが一つの特徴
    38

    View full-size slide

  39. 私達はJUnit4を利用
    ● spring-boot-starter-testのデフォルトに従った
    ● 開発当初Spring Boot1.5系が最新だった(JUnit5未サポートだった)
    ● 自分が使用するWebアプリケーションフレームワークとの相性、IDEとの相性を
    調査してから導入することをオススメ
    39

    View full-size slide

  40. アサーションライブラリ
    40

    View full-size slide

  41. Hamcrest
    ● 基本的なアサーションはJUnit4に組み込まれている
    ● 応用的なアサーションはhamcrest-libraryを別途インストール
    ● Kotlinで使う場合はisが予約語なのでエスケープする必要あり
    assertThat(a, `is`(b))
    // もしくは
    import org.hamcrest.CoreMatchers.`is` as Is
    assertThat(a, Is(b))
    41

    View full-size slide

  42. AssertJ
    ● fluent(流れるような)インターフェース
    ● Kotlinで使うには特にハックが必要ない
    assertThat(a).isEqualTo(b)
    42

    View full-size slide

  43. KotlinTestのアサーション
    ● KotlinTestはテスティングフレームワークだが、アサーションも含んでい

    ● 3系からはモジュールが3つに別れており、アサーションである
    kotlintest-assertionsだけimportして使える
    ○ https://github.com/kotlintest/kotlintest/blob/65aabf75d0ef0eac7d67fed1f11c
    1932b41993ce/CHANGELOG.md#version-30x---march-29-2018
    ● テスティングフレームワークとしてJUnit4を使い、アサーションに
    KotlinTestを使うということも可能
    a shouldBe b
    43

    View full-size slide

  44. Kotlin製のアサーションライブラリは沢山ある
    // kotlin.test
    assertEquals(actual = a, expected = b)
    // Kluent
    a shouldEqual b
    // HamKrest
    assert.that(a, equalTo(b))
    // Expekt
    a.should.equal(b)
    // assertk
    assertThat(a).isEqualTo(b)
    44

    View full-size slide

  45. (参考)私達はAssertJを使った
    ● spring-boot-starter-testのデフォルトに従った
    ● fluentインターフェースが好みだったため(主観)
    ● どのライブラリを選ぶのかは好みが分かれる所だと思うが、一つのプロ
    ジェクト内で複数のアサーションライブラリが乱立しないようにはした方
    がよいと思う
    45

    View full-size slide

  46. モックライブラリ
    46

    View full-size slide

  47. Mockito
    ● https://github.com/mockito/mockito
    ● 最新ver: 2.21.3
    ● Javaのモックライブラリのデファクトスタンダード
    ● Kotlinで使う際のハマりポイント2つ
    ○ 1,finalクラス/メソッドをモックできない問題
    ○ 2,Mockito.any()問題
    47

    View full-size slide

  48. Mockito - 問題1. Mockitoはfinalクラス/メソッドをモックできない
    ● Mockitoはfinalクラス/メソッドをモックできない
    ● 一方、Kotlinのクラス/メソッドはデフォルトでfinal
    ● 従って、Kotlinのクラス/メソッドをそのままmockしようとすると「final
    classのためモックできません」というエラーになる
    ● 主な対処法4つ
    class ApiClient {
    fun foo(): String { return "api response" }
    }
    val mock = mock(ApiClient::class.java)
    // Mockito cannot mock/spy because :
    - final class
    48

    View full-size slide

  49. Mockito - 対処法1: 愚直にopen
    open class ApiClient {
    open fun foo(): String { return "api response" }
    }
    val mock = mock(ApiClient::class.java)
    ● モックする対象のクラス、メソッドを手動でopenする
    49

    View full-size slide

  50. Mockito - 対処法2: インターフェースを使う
    interface ApiClient {
    fun foo(): String
    }
    class ApiClientImpl: ApiClient {
    override fun foo(): String { return "api response" }
    }
    val mock = mock(ApiClient::class.java)
    ● テスト対象クラスにインターフェースを実装させ、インターフェースをモッ
    クする
    50

    View full-size slide

  51. Mockito - 対処法3: allopenプラグイン
    ● Kotlinのallopenプラグインを使い、モック対象をopenする
    ● Springの場合、kotlin-springプラグインを使ってSpringのBeanはopen
    しているケースがほとんどだと思うので、あまり意識することは無いかも
    しれない
    ○ https://kotlinlang.org/docs/reference/compiler-plugins.html#spring-support
    51

    View full-size slide

  52. // 独自アノテーションを作る
    package com.maeharin
    annotation class OpenForMock
    // モック対象のクラスに独自アノテーションを付与
    @com.maeharin.OpenForMock
    class ApiClient {
    fun foo(config: ApiConfig): String {
    return "real"
    }
    }
    Mockito - 対処法3: allopenプラグイン
    52

    View full-size slide

  53. Mockito - 対処法3: allopenプラグイン
    // build.gradle
    buildscript {
    dependencies {
    classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
    }
    }
    apply plugin: 'kotlin-allopen'
    allOpen {
    // 独自アノテーションをallOpenの対象に指定
    annotation('com.maeharin.OpenForMock')
    }
    53

    View full-size slide

  54. Mockito - 対処法4: Mockitoのmock-maker-inlineを有効化
    mock-maker-inline
    src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
    に以下のテキストを書く
    ● mockito2.1.0からexperimentalな機能として提供されている
    ○ https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#mock-
    the-unmockable-opt-in-mocking-of-final-classesmethods
    ● experimentalな機能ということは留意が必要
    54

    View full-size slide

  55. Mockito - 問題2: NotNullな引数にMockito.any()を使うとランタイムでエラー
    ● Mockito.any()の戻り値の型はプラットフォーム型のジェネリクス(T!)なのでコ
    ンパイルは通る
    ● しかし、Mockito.any()はランタイムでnullを返す仕様。KotlinはNotNullな引
    数に対してプラットフォーム型を渡すと、ランタイム時にメソッドをコールする
    前にnullチェックするため、エラーになる
    open class ApiClient {
    open fun foo(config: Config /** NotNullな引数 */){ … }
    }
    val mockClient = Mockito.mock(ApiClient::class.java)
    Mockito.`when`(mockClient.foo(Mockito.any())).thenReturn("dummy")
    // java.lang.IllegalStateException: Mockito.any() must not be null
    55

    View full-size slide

  56. Mockito - 参考: Intellij IDEAでShow Kotlin Bytecodeした結果
    mockClient.foo(Mockito.any())

    INVOKESTATIC org/mockito/Mockito.any ()Ljava/lang/Object;
    DUP
    LDC "Mockito.any()"
    INVOKESTATIC
    kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull
    (Ljava/lang/Object;Ljava/lang/String;)V
    CHECKCAST ApiConfig
    INVOKEVIRTUAL ApiClient.foo (LApiConfig;)Ljava/lang/String;
    56

    View full-size slide

  57. Mockito - 対処法1: 独自ヘルパーを作る
    private fun myAny(): T {
    return Mockito.any()
    }
    Mockito.`when`(mock.foo(myAny())).thenReturn("dummy")
    ● 戻り値の型がKotlinのジェネリクスとなるメソッドでMockito.any()をラップ。
    すると、ランタイムのnullチェックがスキップされる
    ○ https://stackoverflow.com/questions/30305217/is-it-possible-to-use-mockito-in-k
    otlin/30308199
    57

    View full-size slide

  58. Mockito - 対処法2: Kotlin用ラッパーmockito-kotlinを使う
    import com.nhaarman.mockitokotlin2.*
    // mockito-kotlinのany()
    whenever(mock.foo(any())).thenReturn("dummy")
    ● Mockito.any()ではなく、mockito-kotlinが提供するany()を使うと前述
    のnullの問題は起きない
    58

    View full-size slide

  59. Mockito-Kotlin
    ● https://github.com/nhaarman/mockito-kotlin
    ● MockitoのKotlin用ラッパー。あくまでもラッパーなので、基本機能は
    Mockitoを利用
    ● 1系と2系でパッケージ名異なる(現最新ver: 1.6.0 / 2.0.0-RC1)
    ● Kotlin用のDSLを提供(`when`と同義のwheneverなど)
    ● mockito-kotlinのany()を使えば、前述のMockito.any()問題は起きない
    ● ※前述のfinal問題を自動でカバーしてくれるわけではないので、そこは
    mockitoのmock-maker-inlinを有効化など前述の対策を講じる必要あ

    59

    View full-size slide

  60. import com.nhaarman.mockitokotlin2.*
    // モック作成
    val mock: ApiClient = mock {
    on { foo(any()) } doReturn "dummy"
    }
    // モックを注入して実行
    SampleService(mock).exec(ApiConfig())
    // モックのメソッドが呼ばれたことを検証
    verify(mock).foo(any())
    Mockito-Kotlin - コード例
    60

    View full-size slide

  61. MockK
    ● https://github.com/mockk/mockk
    ● Kotlin製のモックライブラリ
    ● 最新ver: 1.8.6
    ● デフォルトでfinalクラス/メソッドをモックできる
    ● mockkのany()を使えばMockito.any()問題は起きない
    61

    View full-size slide

  62. import io.mockk.*
    // モック作成
    val mock:ApiClient = mockk()
    every { mock.foo(any()) } returns "dummy"
    // モックを注入して実行
    SampleService(mock).exec(ApiConfig())
    // モックのメソッドが呼ばれたことを検証
    verify(exactly = 1) { mock.foo(any()) }
    MockK - コード例
    62

    View full-size slide

  63. 私達はMockito-Kotlinを利用
    ● spring-boot-starter-testのデフォルトはMockito
    ● Mockito.any()問題の解消やKotlin用のDSLを提供してくれるのが便利
    なので、MockitoのラッパーであるMockito-Kotlinを使うことにした
    63

    View full-size slide

  64. DBセットアップライブラリ
    64

    View full-size slide

  65. DBUnit
    ● http://dbunit.sourceforge.net/
    ● 最新ver: 2.6.0
    ● XML、CSVなどからテストデータをセットアップできる
    ● データベースの状態を検証することもできる
    ● 2002年にver1.0がリリースされた歴史あるJavaライブラリ
    65

    View full-size slide



  66. id="1"
    name="maeharin"
    job="ENGINEER"
    status="ACTIVE"
    age="30"
    score="0"
    is_admin="false"
    birth_day="1990-01-01"
    created_timestamp="2018-01-01 00:00:00"
    />

    66
    DBUnit - コード例

    View full-size slide

  67. @Before
    fun setup() {
    Class.forName(dbDriver)
    conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword)
    val iDBConn = DatabaseConnection(conn)
    val iDataSet =
    FlatXmlDataSetBuilder().build(File("src/test/resources/dbunit-datas/users.xml"))
    DatabaseOperation.CLEAN_INSERT.execute(iDBConn, iDataSet)
    }
    @Test
    fun `test 1`() {
    val stmt = conn.createStatement()
    val rs = stmt.executeQuery("select * from users order by id asc")
    assertTrue(rs.next())
    assertEquals("maeharin", rs.getString("name"))
    } 67
    DBUnit - コード例

    View full-size slide

  68. DBUnit - 課題
    ● 重複記述が多くなる
    ● 雛形を元に一部の値だけ変えたパターンをプログラマブルに作るといっ
    たことが行いづらい
    ● カラムに変更があった時の変更箇所が多い
    68

    View full-size slide

  69. DbSetup
    ● https://github.com/Ninja-Squad/DbSetup
    ● 最新ver: 2.1.0
    ● xmlなどの外部ファイルではなく「コード」でテストデータを生成できる
    Javaライブラリ
    ● Kotlin用のDSLも提供してくれている(DbSetup-kotlin)
    69

    View full-size slide

  70. dbSetup(dest) {
    deleteAllFrom("users")
    insertInto("users") {
    mappedValues(
    "id" to 1,
    "name" to "maeharin",
    "job" to "ENGINEER",
    "status" to "ACTIVE",
    "age" to 30,
    "is_admin" to false,
    "created_timestamp" to LocalDateTime.now()
    )
    }
    }.launch()
    70
    DbSetup - コード例

    View full-size slide

  71. 他の選択肢
    ● WAFの機能を利用する(例: Spring Testの@Sql)
    ● O/Rマッパorデータアクセスライブラリのinsert系メソッドを利用する(例:
    MyBatis、Doma2、Exposed等)
    71

    View full-size slide

  72. 私達はDbSetup-kotlinを少し工夫して利用
    ● (背景)私達はDBアクセスライブラリにはDoma2を使っている
    ○ 私達はDomaのEntityはKotlinではなくJavaで定義した
    ○ DomaのDaoを使ってテストデータをinsertするという手もあるが、Javaなのでフィクス
    チャのプロパティのIDE補完がされない
    ● DbSetupと自作ツールであるFacltinを使うことで、Kotlinのdata classを
    用いてプログラマブルにテストデータを生成する仕組みを作った
    ○ 詳細は第3部で紹介
    72

    View full-size slide

  73. 1部まとめ
    ● テスティングライブラリは、WAFとIDEとの相性も加味して決めることをオ
    ススメ
    ● アサーションはHamcrestやAsserJが鉄板だが、プロジェクトの中で乱
    立しないようにだけ留意しつつ、好みのライブラリを使えばよいと思う
    ● モックはKotlinまわりで少しハマりポイントがあるため、mockito-kotlinを
    使うとよい(もしくはmockk)
    ● DBテストセットアップはDbSetup+Factlin(後述)がオススメ
    73

    View full-size slide

  74. 2部
    How do we test Server-side Kotlin
    ● 実際のプロジェクトでどのようにテストを書いていたか紹介します
    ○ テスト対象のアプリケーションについて
    ○ テスト戦略について
    ○ アプリケーションサービス、JSON APIのテストについてのTips
    74

    View full-size slide

  75. テスト対象のアプリケーション構成についておさらい
    ● SpringBoot + Kotlin で実装されたAPIサーバ
    ● 10年弱運用されてきたアプリケーションのリニューアル!(Java独
    自FW)
    ● 新アプリケーションはオニオンアーキテクチャをベースにした構成
    ○ プレゼンテーション層
    ○ アプリケーション層
    ○ ドメイン層
    ○ インフラストラクチャ層
    75

    View full-size slide

  76. Kotlin + Spring Boot
    API サーバ
    REST APIでやりとり
    もしくは
    バックエンドのAPIとしてSpring Boot + Kotlin
    76

    View full-size slide

  77. オニオンアーキテクチャ
    [DDD]ドメイン駆動 + オニオンアーキテクチャ概略 より引用
    77

    View full-size slide

  78. パッケージ構成
    78
    ● presentation
    ○ WEBのエンドポイント(controllerとか)
    ● domain
    ○ ドメインエンティティ、バリューオブジェクト
    ● application
    ○ アプリケーションサービス(ユースケース)
    ● infrastructure
    ○ DBアクセス、他サービスとのREST API連携など

    View full-size slide

  79. プレゼンテーションの実装イメージ
    @RestController
    class TaskController(val taskService: TaskService) {
    @GetMapping("/tasks/{id}")
    fun findTask(@PathVariable("id") id: Int): TaskResponse {
    val task = taskService.findById(id)
    return TaskResponse(id = task.id, title = task.title)
    }
    }
    // Responseのクラスを必ず作成し、ドメイン層のクラスをそのまま返さない
    data class TaskResponse(val id: Int, val title: Title)
    79

    View full-size slide

  80. アプリケーションサービスの実装イメージ
    @Service
    class TaskService(
    // infrastructure層に定義したクラスをコンストラクタインジェクションで
    DIする
    private val taskRepository: TaskRepository,
    private val userRepository: UserRepository
    ) {
    fun assignTask(taskId: TaskId, assigneeId: UserId): Task {
    val task = taskRepository.findById(taskId) ?: throw NotFoundExcpetiopn()
    val assignee = userRepository.findById(assigneeId) ?: throw NotFoundException()
    task.assignedTo(assignee)
        return taskRepository.save(task)
    }
    }
    80

    View full-size slide

  81. Repositoryの実装イメージ
    @Repository
    class DomaPrefectureRepository(
    private val prefectureEntityDao: PrefectureEntityDao
    ): PrefectureRepository {
    override fun findAll(): List {
    val entities = prefectureEntityDao.selectAll() // Domaから取得
    return entities.map {
    // Domaから取得したものを Domain層で定義したクラスにマッピングしなおす
    Prefecture(
    id = it.id,
    name = it.name,
    status = PrefectureStatus.valueOf(it.status), // レガシーなカラムをそのまま使わずにマッピング
    isPrimary = it.primaryFlag == 2 // レガシーなカラムをそのまま使わずにマッピング
    )
    }
    }
    }
    81

    View full-size slide

  82. このアーキテクチャのpros
    ● 各レイヤの責務が明確になるので変更に強い
    ○ ある箇所を変更したときの影響範囲が明確
    ● 技術的な関心事とビジネスロジックをある程度切り離して考えられる
    ○ 特に歴史的な経緯を含むDBのスキーマとビジネスロジックを切り離
    して考えられるようになった
    82

    View full-size slide

  83. このアーキテクチャのcons
    ● 記述量は比較的に増える傾向にある
    ○ 特に各レイヤー間でのマッピングは結構大変なので、開発当初は
    結構辛い・・・
    ● 単純な処理しかしないAPIでは、単純にデータを各レイヤで受け渡すだ
    けになることがある
    83

    View full-size slide

  84. pros/consを踏まえたテスト方針
    ● テスト対象は基本的にアプリケーションサービスより上の層
    ○ ※ドメイン層はロジックの複雑なものだけテスト
    ● アプリケーションサービスに対するテスト
    ○ アプリケーションとして要求される仕様を満たしているかをテスト
    ○ 基本的にこの層を厚めに書くことで仕様の正しさを担保でき、ドメイ
    ン層のリファクタリングもしやすくする
    ● JSON APIに対するテスト
    ○ レスポンスの正しさ をテスト
    ○ 意図したユーザーのみがアクセスできることをテスト
    84

    View full-size slide

  85. Presentation Application Domain Infrastructure
    各テストがカバーするレイヤーの範囲
    JSON APIのテスト
    アプリケーションサービスのテスト
    PostgreSQL
    外部API
    ElasticSearch
    85

    View full-size slide

  86. アプリケーションサービスの
    テストにおけるTips
    86

    View full-size slide

  87. Presentation Application Domain Infrastructure
    アプリケーションサービスのカバー範囲
    モックしない
    PostgreSQL
    外部API
    ElasticSearch
    開発者毎に
    Docker
    モック
    アプリケーションサービスのテスト
    87

    View full-size slide

  88. アプリケーションサービスのテストコード
    // アプリケーションサービスのテストコードサンプル
    class ConsultantServiceStopConsultantTest : ServiceTestBase() {
    // Testではコンストラクタインジェクションできないので
    lateinitでフィールドインジェクション
    @Autowired private lateinit var consultantService: ConsultantService
    @Autowired private lateinit var consultantRepository: ConsultantRepository
    @Autowired private lateinit var positionRepository: PositionRepository
    @Test
    fun コンサルタントアカウントを
    idで取得できること() {
    val consultantOutput = consultantService.showConsultantSimpleInfo(2)
    assertThat(consultantOutput.id).isEqualTo(2)
    }
    @Test
    fun コンサルタントアカウントを停止できること
    () {
    consultantService.stopConsultant(12) // テスト対象のサービス実行
    // コンサルタントの求人が全て削除されていること
    val positions = positionRepository.findByConsultantId(12)
    assertThat(positions).isEmpty()
    }
    }
    88

    View full-size slide

  89. アプリケーションサービスのテストコード
    @Test
    fun コンサルタントアカウントを
    idで取得できること() {
    val consultantOutput = consultantService.showConsultantSimpleInfo(2)
    assertThat(consultantOutput.id).isEqualTo(2)
    }
    @Test
    fun コンサルタントアカウントを停止できること
    () {
    consultantService.stopConsultant(12) // テスト対象のサービス実行
    // コンサルタントの求人が全て削除されていること
    val positions = positionRepository.findByConsultantId(12)
    assertThat(positions).isEmpty()
    } 89

    View full-size slide

  90. アプリケーションサービスのテスト方針
    ● アプリケーションサービスの結合テスト
    ○ DBアクセス => モックにしない
    ● テストフレームワークはJUnit4。アサーションはAssertJ
    ○ 当初はKotlinTestを使っていたが、Inttelijで実行したときに差
    分をInttelijがいい感じにしてくれないので、AssertJに切り替

    ● 戻り値をテストするのはもちろん、場合によってはDB変更もテスト
    する
    ● テストでDBをどのように扱ったかをお話します。
    90

    View full-size slide

  91. DBアクセスはモックにしなかった
    ● アプリケーションの構造上、各Infrastructureレイヤーからドメインオブジェク
    トへのマッピングが必要になるので、少なくともその部分が例外にならない
    ことはテストしておきたい
    ● 特にKotlinの場合 Null / Not Nullが厳密なので、DB上 Null を取りうるカラ
    ムの値を Not Null のプロパティにマッピングするようなコードがあると簡単
    に実行時例外になってしまう
    ● まだデータアクセスライブラリ(Doma2)によるSQL実行が最低限例外なく実
    行できることも確認したい
    ● マッピングのテストをかいた上でRepositoryをモックにする方向もあったが、
    DbSetupを用いるとDB上のデータの生成が容易だったのでモックにしない
    選択をした
    91

    View full-size slide

  92. データのセットアップをどのようにおこなったか
    ● DBのセットアップにはDbSetup-kotlinを使用
    ● DbSetup-Kotlinのコードは、Factlinというライブラリ(※後で紹介
    します!)によりスキーマから自動生成
    ● Factlinにより、DBのデータセットアップもRailsのFactoryBotのよ
    うに平易に取り扱うことができるようになった
    92

    View full-size slide

  93. // Factlinという独自ライブラリで作成した
    Fixtureクラスを活用して以下のようにセットアップできる
    @Before
    fun setUp() {
    dbSetup(DataSourceDestination(dataSource)) {
    init()
    // suusan2goユーザーを作成
    insertUserFixture(UserFixture(name = "suusan2go", job = "engineer", age = 25))
    // maeharinユーザーを作成
    insertUserFixture(UserFixture(name = "maeharin", job = "engineer", age = 35))
    }.launch()
    }
    Factlinを活用したDBデータのセットアップコード(※後ほど解説します)
    93

    View full-size slide

  94. DBがどのように変更されたかをテストする
    ● DBが意図した状態に変更されたことをチェックしたい
    ○ 特にDBとドメインエンティティのマッピングが単純ではない場
    合や、意図しないデータを更新していないか、意図した件数
    更新しているかなどを厳密にチェックしたい場合
    ● AssertJ-DBを用いてDBの状態をテストした
    ● AsserJ-DBはDBの変更をAssertJのようなシンタックスでテストで
    きるライブラリ
    94

    View full-size slide

  95. @Test
    fun メッセージ送信上限をリセットできること() {
    // 特定テーブルの変更トラッキングを定義
    val changes = Changes(Table(dataSource, "consultant"))
    // 変更のトラッキングを開始
    changes.setStartPointNow()
    // テスト対象のサービスを実行
    consultantService.resetConsultantRemainingMessageCount()
    // 変更のトラッキングを終了
    changes.setEndPointNow()
    assertThat(changes).onTable("consultant")
    .hasNumberOfChanges(1)
    .change()
    .column("id").valueAtEndPoint().isEqualTo(someConsultant.id)
    .column("remaining_message_count").valueAtStartPoint().isEqualTo(0)
    .valueAtEndPoint().isEqualTo(4)
    }
    95

    View full-size slide

  96. AssertJ-DBのcons
    ● 細かなDBの変更を全てAssertJ-DBでテストするのは、シンタックス的に
    少しつらい
    ● 変更を検知するテーブルを絞っても、テストは通常よりも時間がかかる
    ようになる
    ● 全てをAssertJ-DBでテストしているわけではなく、RepositoryでDBから
    引き直してテストしているものが多いです
    96

    View full-size slide

  97. JSON APIのテストにおけるTips
    97

    View full-size slide

  98. JSON APIのテストがカバーする範囲(おさらい)
    Presentation Application Domain Infrastructure
    モックしない
    PostgreSQL
    外部API
    ElasticSearch
    開発者毎に
    Docker
    モック
    JSON APIのテスト
    98

    View full-size slide

  99. JSON APIのテストのTIPS
    ● Kotlinならではのテストリクエストの書き方
    ● JSON APIの効率的なテストの仕方
    99

    View full-size slide

  100. JSON APIのテストをわかりやすく、宣言的に書く
    ● 普通にSpringのMockMVCを使用してコントローラへの
    リクエストをテスト
    ● Kotlinの拡張関数を活用すると認証が必要なAPIや、
    どのようなリクエストなのかを宣言的に記述できます
    100

    View full-size slide

  101. テストコード
    @Test
    fun コンサルタント一覧APIへのアクセステスト() {
    // 管理者はAPIにアクセスできる getはMockMvcRequestBuildersのStaticメソッド
    get("/admin/api/users").asUser(mockMvc, adminEmail, "password").also { request ->
    mockMvc.perform(request)
    .andExpect(status().isOk) // ステータスコードのチェック
    }
    // 普通のユーザーではアクセスできない
    get("/admin/api/users").asUser(mockMvc, email, "password").also { request ->
    mockMvc.perform(request)
    .andExpect(status().isForbidden)
    }
    // ログインしていないとアクセスできない
    get("/admin/api/users").also { request ->
    mockMvc.perform(request)
    .andExpect(status().isForbidden)
    }
    }
    101

    View full-size slide

  102. 拡張関数の開設
    // 事前にログイン用のAPIにアクセスして、ログイン状態のセッションを
    RequestBuilderに渡しておく
    fun MockHttpServletRequestBuilder.asUser(
    mockMvc: MockMvc,
    email: String,
    password: String
    ): MockHttpServletRequestBuilder {
    val loginRequest = LoginRequest(email, password)
    val session = MockHttpSession()
    val request = MockMvcRequestBuilders
    .post("/admin/api/login")
    .withJson(loginRequest)
    .accept(MediaType.APPLICATION_JSON_VALUE)
    .session(session)
    mockMvc.perform(request).andExpect(status().is2xxSuccessful)
    this.session(session)
    return this
    } 102

    View full-size slide

  103. // 渡されたオブジェクトを JSON化してリクエストボディに詰める
    fun MockHttpServletRequestBuilder.withJson(
    object: Object
    ): MockHttpServletRequestBuilder {
    this.contentType(APPLICATION_JSON_UTF_8)
    this.content(ObjectMapper().writeValueAsString(object)
    return this
    }
    103

    View full-size slide

  104. 拡張関数の開設
    val hogeRequestDto = HogeRequestDto(piyo = “hogehoge”, fuga = “hogehoge”)
    // Util的なものを使って書くとこんな感じになる
    val testRequest = post("/api/some_resources")
    RequestUtil.asUser(testRequest, email, password)
    RequestUtil.withJson(testRequest, hogeRequestDto)
    // 拡張関数を組み合わせて宣言的にリクエストを組み立てられる
    post("/api/some_resources")
    .asUser(mockMvc, email, password).withJson(hogeRequestDto)
    104

    View full-size slide

  105. JSONのアサーション (JsonUnit)
    ● spring-boot-starter-testではJSONassertが入ってくるが、JsonUnit
    の方が差分表示が見やすいため、JsonUnitを主に利用した
    ○ https://github.com/skyscreamer/JSONassert
    ○ https://github.com/lukas-krecan/JsonUnit
    105

    View full-size slide

  106. // JsonUnit
    // どのキーの値がどう違うのかが明確
    val a = "{\"id\":1,\"friends\":[1,2]}"
    val b = "{\"id\":1,\"friends\":[3,4]}"
    assertJsonEquals(a, b)
    // java.lang.AssertionError: JSON documents are different:
    //Different value found in node "friends[0]", expected: <1> but was: <3>.
    //Different value found in node "friends[1]", expected: <2> but was: <4>.
    106

    View full-size slide

  107. ● プロパティが数十個あってネストしているAPIの「期待値」を手で書いて
    いくのは骨が折れる
    ● 最初のテスト実行時に正常なAPIレスポンスの期待値をファイルに保存
    し、このファイルを元に期待するJSONフォーマットを記述した
    ● このJSONファイルを期待値としてJsonUnitでアサートするようにする
    と、スピーディーにテストを書くことができた
    ● また、これにより意図せずAPIの形が変わった場合にもテストで気がつ
    けるようになった
    期待するJSONをどのように用意するか
    107

    View full-size slide

  108. @Test
    fun `コンサルタント詳細情報を表示できること
    `() {
    var actual = mockMvc.perform(request)
    .andExpect(status().isOk)
    .andReturn().response.contentAsString
    assertEqualToJsonFileOrSave(actual, "path/to/expected/json")
    }
    // 独自ヘルパーメソッド
    // - jsonファイルがない状態で一度実行すると指定パス配下にファイルができる
    // - jsonファイルがない状態ではテスト自体は
    failする
    // - ファイルが有る場合は、JsonUnitで内容の一致をチェックする
    fun assertEqualToJsonFileOrSave(result: Any, path: String)
    108

    View full-size slide

  109. おまけ:カバレッジ
    ● カバレッジの取得にはJacocoを活用
    ● 少し前までData Classから自動生成されるコードについてのカバレッジ
    が0%になってしまうため、カバレッジがほぼ意味を成さない値になって
    いた
    ● 0.8.2からはKotlinのテストでもカバレッジが正しく取得できる!
    109

    View full-size slide

  110. 例:こんなクラスのカバレッジを考える
    data class GeoCode(
    val latitude: Double,
    val longitude: Double
    ) {
    fun calculateDistances(other: GecCode): Long {}
    }
    @Test
    fun 正しく距離が計算されること(){
    val geoCode = GeoCode(xxx, yyy)
    val otherGeoCode = GeoCode(zzzz, aaaa)
    assertEquals(geoCode.calculateDistance(otherGeoCode), xxxx)
    }
    110

    View full-size slide

  111. 0.8.1以前
    111

    View full-size slide

  112. 0.8.2以降
    112

    View full-size slide

  113. 0.8.2はなんと今週リリース
    113

    View full-size slide

  114. How do we test Servier-Side Kotlinのまとめ
    ● アプリケーションサービスより上の層を中心にテストしてます
    ● アプリケーションサービスの結合テストのTips
    ○ DBSetupでのデータセットアップ
    ○ AssertJ-DBを使ったデータベース状態のテスト
    ● JSON APIでのテストのTips
    ○ 拡張関数でテスト用のリクエストを宣言的に記述できる
    ○ JSONUnit & JSONファイル生成でJSONのテストを容易に
    ● テストのカバレッジはjacocoで。
    0.8.2からはData Classもカバレッジが正しく取得できるように!
    114

    View full-size slide

  115. 3部
    Our test tools for Kotlin
    ● テストのために発表者が作ったツールたちの紹介
    ○ Factlin
    ○ kotlin-fill-class
    115

    View full-size slide

  116. Factlin (by @maeharin)
    ● DbSetupのシンタックスは平易とはいえ、カラムの多いテーブルに対
    するセットアップコードを一から書くのは大変
    ● Factlinを使うことで、既存のデータベーススキーマから、DbSetup用
    のコードを生成できる
    ● テストフィクスチャ用のクラスでNull / Not Nullを区別できる
    ● テストフィクスチャ用のクラスはKotlinのdata classになっているた
    め、copyメソッドを用いて「少しだけパラメータが異なる似たようなフィ
    クスチャ」を簡単に生成できる
    116

    View full-size slide

  117. Factlinの生成コード例
    CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(256) NOT NULL,
    job VARCHAR(256) NOT NULL DEFAULT 'engineer',
    status VARCHAR(256) NOT NULL DEFAULT 'ACTIVE',
    age INTEGER NOT NULL,
    score NUMERIC NOT NULL,
    is_admin BOOLEAN NOT NULL,
    birth_day DATE NOT NULL,
    nick_name VARCHAR(256),
    created_timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
    updated_timestamp TIMESTAMP WITHOUT TIME ZONE
    );
    ● 左記のようなスキーマに対して、
    factlinのgradle タスクを実行する
      ./gradlew factliin
    117

    View full-size slide

  118. // DBのカラムへのコメントも自動で反映されます!
    // Fixture Class
    data class UsersFixture (
    val id: Int = 0, // primary key
    val name: String = "", // user name
    val job: String = "", // job name
    val status: String = "", // activate status
    val age: Int = 0, // user age
    val score: BigDecimal = 0.toBigDecimal(), // game score
    val is_admin: Boolean = false, // user is admin user or not
    val birth_day: LocalDate = LocalDate.now(), // user birth day
    val nick_name: String? = null, // nick name
    val created_timestamp: LocalDateTime = LocalDateTime.now(),
    val updated_timestamp: LocalDateTime? = null
    ) 118

    View full-size slide

  119. // DbSetupの拡張関数を自動生成
    fun DbSetupBuilder.insertUsersFixture(f: UsersFixture) {
    insertInto("users") {
    mappedValues(
    "id" to f.id,
    "name" to f.name,
    "job" to f.job,
    "status" to f.status,
    "age" to f.age,
    "score" to f.score,
    "is_admin" to f.is_admin,
    "birth_day" to f.birth_day,
    "nick_name" to f.nick_name,
    "created_timestamp" to f.created_timestamp,
    "updated_timestamp" to f.updated_timestamp
    )
    }
    } 119

    View full-size slide

  120. // Factlinという独自ライブラリで作成した
    Fixtureクラスを活用して以下のようにセットアップできる
    @Before
    fun setUp() {
    dbSetup(DataSourceDestination(dataSource)) {
    init()
    // suusan2goユーザーを作成
    insertUserFixture(UserFixture(name = "suusan2go", job = "engineer", age = 25))
    // maeharinユーザーを作成
    insertUserFixture(UserFixture(name = "maeharin", job = "engineer", age = 35))
    }.launch()
    }
    Factlinを活用したDBデータのセットアップコード
    120

    View full-size slide

  121. 日本語の解説記事あります
    ● 以下をご参照ください!!!!
    ● DBスキーマからKotlinのテストフィクスチャを自動生成するgradleプラグ
    インを作った
    121

    View full-size slide

  122. Kotlin Fill Class (by @suusan2go)
    ● コンストラクタを書くのを少しだけ楽にしてくれるIntellij Plugin
    ● Named argumentsの形式でコンストラクタの呼び出しを補完してくれる
    ● Kotlin標準のクラスならデフォルト値もついでに与えてくれる
    122

    View full-size slide

  123. モチベーション
    ● 小さなクラスならよいが、歴史的な経緯により大量のプロパティを持
    たざるえないクラスはテストのためにコンストラクタを呼び出して初期
    化するのが大変しんどい
    ● テストのスコープからは少し外れるが、我々のプロジェクトでは各レ
    イヤ間でクラス間で値をマッピングするコードがとても多かった。
    Intellij IDEAでは補完、足りないプロパティの警告はしてくれるもの
    の、一つずつnamed arguments形式で書くのがだるい
    123

    View full-size slide

  124. たとえばこんなクラスをインスタンス化するコード書くの辛くないですか
    data class User(
    val id: Int,
    val name: String,
    val age: Int,
    val address: String,
    val firstName: String,
    val lastName: String,
    val firstNameKana: String,
    val lastNameKana: String,
    val favoriteSongName: String,
    val favoriteFruitsName: String,
    val favoriteSport: String,
    val favoriteProgramingLanguage: String,
    val favoriteBook: String,
    val isMarried: Boolean,
    val hasDependent: Boolean,
    val CommutingTime: Int,
    val nearestStationName: String,
    val isAdmin: Boolean,
    val educationBackground: String,
    val githubAccount: String,
    val twitterAccount: String,
    val facebookAccount: String,
    val linkedinAccount: String,
    val lineAccount: String,
    val m3Account: String,
    val birthday: LocalDate,
    val motherTongue: String,
    val occupation: String,
    val bestSubject: String,
    val deathblow: String,
    val youtubeAccount: String,
    val lovesKotlin: Boolean
    )
    124

    View full-size slide

  125. Kotlin Fill Classを使わない場合
    125

    View full-size slide

  126. Kotlin Fill Classのデモ
    126

    View full-size slide

  127. Kotlin Fill Classの実装について
    ● Inttelijプラグインの作り方自体は今回の発表のスコープから外れるので以下を
    ご参照ください!!!
    ● そしてダウンロードしてください!!!
    ● まだ機能は荒削りなのでpulll-requestお待ちしてます!
    ● Kotlinでコンストラクタをシュッと書くためにIntellijのプラグインを作った
    127

    View full-size slide

  128. 3部 Our test tools for kotlinのまとめ
    ● 以下のようなツールで開発の効率化を行いました
    ○ Factlin DBのスキーマからDBSetupのコードを自動生成
    ○ 大量のプロパティを持つクラスの生成にkotlin-fill-class
    ● KotlinはJavaの資産が使えるとはいえ、まだまだこういったKotlin用のツー
    ルが少ない印象なので今がチャンス!かもしれません。
    128

    View full-size slide

  129. 最後に
    ● サーバーサイドKotlinでもテストはバッチリ書けます!
    ● エムスリーではサーバーサイドKotlinを用いたプロジェクトが複数立ち上
    がっています。We’re Hiring!!!
    129

    View full-size slide