Kotlin Unit Testing (1)

2017-09-07T00:00:00+00:00 Kotlin

参考: https://www.slideshare.net/shoma2da/kotlin-63635954

KotlinでUnitTestingをやってみた

余談

Kotlinでは基本的にデフォルトでclassはfinal、メソッドにもfinalがついてる。つまりMockitoなどを使って普通にMockしたりする事が出来ない(Mockitoではfinal classそのものをサポートしていないため)。でまぁそうなるとPowerMockitoとかを使う必要があるのだけど、一応open classにすることで継承可能にしたりすることが可能らしいので今回のスコープはそこにあてて使う

build.gradle

buildscript {
    ext.kotlin_version = '1.1.4-3'

    repositories {
        mavenCentral()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: "kotlin"

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
    testCompile "junit:junit:+"
    testCompile "org.assertj:assertj-core:+"
    testCompile "org.mockito:mockito-core:+"
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

※めんどくさいので一部バージョンは適当にしてありますので(ry

まぁAndroidとかでKotlin使う人が多いんでしょうけど(多分)、そこまでする必要性もなかったので普通にgradleプラグインを使って検証します

User.kt

※資料からのコードを利用してます

class User(birthYear: Int) {
    fun getAge() = 2017 - birthYear
}

UserTest.kt

import org.junit.Test
import org.assertj.core.api.Assertions.*
import org.mockito.Mockito.*

class UserTest {

    @Test
    fun 普通にテスト() {
        val user: User = User(1981)
        assertThat(user.getAge()).isEqualTo(36)
    }

    @Test
    fun mockを使ってテスト() {
        val user: User = mock(User::class.java)
        `when`(user.getAge()).thenReturn(1)
        assertThat(user.getAge()).isEqualTo(1)
    }
}

でまぁこれをそのまま実行すると

というようにfinalクラスはmockできねーよって怒られるわけです。 ということでそれを解決する方法(ここからが本題)

方法1: open classにする

open class User(birthYear: Int) {
    open fun getAge() = 2017 - birthYear
}

ちなみにクラスだけではなくメソッドにもnon-publicではない状態になっていると、whenなどでテストがコケてしまうのでメソッドにもopenをつける。この方法ならテストコードは書き換えなくても出切る

ちなみにallOpenっていう公式プラグインも存在するそうです(やりません)

方法2: Userをインターフェースにする

interface User {
    fun getAge(): Int
}

を定義して

class UserImpl(birthYear: Int) : User {
    override fun getAge() = 2017 - birthYear
}

として、テストも通常のやつのインスタンス生成のところだけを書き換える

import org.junit.Test
import org.assertj.core.api.Assertions.*
import org.mockito.Mockito.*

class UserTest {

    @Test
    fun 普通にテスト() {
        val user: User = UserImpl(1981)
        assertThat(user.getAge()).isEqualTo(36)
    }

    @Test
    fun mockを使ってテスト() {
        val user: User = mock(User::class.java)
        `when`(user.getAge()).thenReturn(1)
        assertThat(user.getAge()).isEqualTo(1)
    }
}

こうすればなんとかopenにしなくてもイケる模様

終わりだけど、まぁKotlin UnitTestingネタは続くかもねw

余談: allopenに関して

上記の対策法のopenのやつをやるにあたって自分でopenをがちゃがちゃしなくてもallOpenっていう公式プラグインがあるのでそれを使ってやることもできる

参考: https://kotlinlang.org/docs/reference/compiler-plugins.html#all-open-compiler-plugin

buildscript {
    ext.kotlin_version = '1.1.4-3'

    repositories {
        mavenCentral()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
    }
}

apply plugin: "kotlin"
apply plugin: "kotlin-allopen"

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
    testCompile "junit:junit:+"
    testCompile "org.assertj:assertj-core:+"
    testCompile "org.mockito:mockito-core:+"
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

allOpen {
    annotation("AllOpen")
}

というふうに、allOpenでannotationに指定したアノテーションを持つクラスに限ってはallOpen側で自動でopenクラスとして定義してくれる模様

まぁ選択肢の一つとして

今時のMockitoのfinal classの扱いについて MutationObserver