Kotlin Unit Testing (1)
参考: https://www.slideshare.net/shoma2da/kotlin-63635954
KotlinでUnitTestingをやってみた
余談
Kotlinでは基本的にデフォルトでclassはfinal、メソッドにもfinalがついてる。つまりMockitoなどを使って普通にMockしたりする事が出来ない(Mockitoではfinal classそのものをサポートしていないため)。でまぁそうなるとPowerMockitoとかを使う必要があるのだけど、一応open classにすることで継承可能にしたりすることが可能らしいので今回のスコープはそこにあてて使う
※追記: mockito2を使う事でfinalクラスでも出来る模様
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クラスとして定義してくれる模様
まぁ選択肢の一つとして