KotlinのContractsを試す
kotlinのContractsを試してみたメモです。
kotlin 1.3からContractsが利用出来るようになりました。
Contracts DSLで事後条件を定義しておくことで、関数呼び出し後の状態を保証することが出来ます。
下記バージョンで試してみます。
- kotlin 1.4.20
- junit 4.13.1
- assertj 3.18.1
Dependency
gradleを使って試してみます。
今回はテストでContractsを試すので、junitとassertjを追加してます。
dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" testCompile "junit:junit:4.4" testCompile "org.assertj:assertj-core:3.16.1" }
Contractsを使用しない場合
疑似的にDBからPerson
を取得するgetPerson()
を定義します。
本来はPerson
かnull
が返る可能性があります。
このテストを書いてみます。
private fun getPerson(): Person? { // emulate searching from DB return Person( age = 40, name = "Dad" ) } data class Person( val age: Int, val name: String )
まずNullableのPerson
が isNotNull()
でnot nullであることを確認し、
Person
の各propertyの値をテストしています。
@Test fun getPersonTest() { // when not using contract val person = getPerson() assertThat(person).isNotNull assertThat(person!!.age).isEqualTo(40) assertThat(person.name).isEqualTo("Dad") }
テストではNullableのperson
に対してisNotNull
でnot nullであることを確認済みですが、
assertThat(person).isNotNull
次の行ではpersonがnot nullであることを認識してくれません。
!!
で強制的にnot nullとしてage
にアクセスしてます
assertThat(person!!.age).isEqualTo(40)
ここでperson.age
でアクセスすることは出来ません。
// error assertThat(person.age).isEqualTo(40)
次の行からはすでに!!
でnot nullであることが保証されているため、Person.name
でアクセス出来ます。
assertThat(person.name).isEqualTo("Dad")
Contractsを使用した場合
Contractsを使用して、スマートキャストが効くようにしてみます。
Contracts定義
下記のようにassertThatNotNull
という関数を定義し、Contractsを定義してみます。
Contractsを定義するには@ExperimentalContracts
アノテーションを付けます。
contract { }
の中にContracts DSLで記述します。
returns()
でassertThat(actual).isNotNull
の実行が成功した場合に保証する条件をimplies
以下に記載します。
今回の場合は成功した場合、actual != null
でactual
がnot nullであることを保証します。
@ExperimentalContracts fun assertThatNotNull(actual: Any?) { contract { returns() implies (actual != null) } assertThat(actual).isNotNull }
assertThatNotNull
を利用して先程と同じテストをしてみます。
assertThatNotNull(person)
でテストが成功している場合(assertThat(actual).isNotNull
が成功している場合)、
assertThat(person.age).isEqualTo(40)
でnot nullとしてperson.age
でアクセス出来ています。
@Test @ExperimentalContracts fun getPersonTestUsingContract() { val person = getPerson() assertThatNotNull(person) assertThat(person.age).isEqualTo(40) assertThat(person.name).isEqualTo("Dad") }
今度はnullとなる場合を試してみます。
常にnullが返るgetNullPerson()
を定義します。
private fun getNullPerson(): Person? { // return always null return null }
person
がnot nullであることを期待していますが、
getNullPerson()
がnullを返すのでテストとしては失敗しなければなりません。
@Test @ExperimentalContracts fun getNullPersonTestUsingContract() { val person = getNullPerson() // error! // assertThatNotNull(person) assertThat(person).isNull() }
実行するとassertThatNotNull(person)
でAssertionError
が発生し、テストがfailになりました。
Expecting actual not to be null java.lang.AssertionError: Expecting actual not to be null at com.example.kotlin.sandbox.contract.MainTestKt.assertThatNotNull(MainTest.kt:61) at com.example.kotlin.sandbox.contract.MainTest.nullPersonTestUsingContract(MainTest.kt:33) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566)
[参考]
https://speakerdeck.com/ntaro/kotlin-contracts-number-m3kt
サンプルコードは下記にあげました。
おわり。