
【サーバーサイドKotlin】ことりん × テスト with SpringFramework
はじめに
なぜサーバーサイドKotlinを導入するのか?であるように、現在アプリボットでは、新規開発プロダクトのサーバーサイド言語でKotlin導入を進めています。
そこで、テストコードについてもKotlin化しましたのでご紹介します。
なお、この記事は2018年1月時点の情報なので今はもっと良い方法があるかもしれません。
以前のJavaテストコード
以前はJavaで以下のようなテストコードを書いていました。
テスト時の条件や期待値がわかりやすいように日本語でメソッド名を書いています。
public class HelloWorldJavaTest {
@Autowired // <-------------------------------------- Spring Beanの注入
private HelloWorldService testClass;
@Before // <----------------------------------------- JUnit テスト事前処理
public void setUp() {
// 前提条件を整える(テストデータ準備、日時の固定化等)
}
@Test // <------------------------------------------- JUnit テスト
public void method1_口口の場合_口口になる() {
new MockUp<ChildService>(ChildService.class) {
@Mock // <----------------------------------- JMockit 外部依存メソッドのモック化
public int childMethod1() {
return 2;
}
};
Assertions.assertThat(testClass.method1()) // <-- AsserJ アサーション(検証処理)
.as("処理結果が期待通りであることを確認")
.isEqualTo(2);
}
}
使用しているフレームワークは以下のものです。
分類 | 名前 |
---|---|
テストフレームワーク | JUnit4 |
モックフレームワーク | JMockit |
検証フレームワーク | AssertJ |
Kotlinでのテストコード
KotlinではJavaのテストフレームワークもほぼ使えますが、Kotlinに特化したテストフレームワークがあります。
Kotlinの代表的なテストフレームワーク
分類 | 名前 |
---|---|
テストフレームワーク | Spek |
〃 | KotlinTest |
モックフレームワーク | Mockit-Kotlin |
検証フレームワーク | Kluent |
〃 | Expekt |
〃 | Hamkrest |
これらのうち、今回はテストフレームワークとモックフレームワークを試してみます。
試したこと その1: Spek (テストフレームワーク)
BDD(振る舞い駆動開発)フレームワークでもあるため、振る舞い(仕様)を記載しやすい構造になっています。
以前のJavaテストコードよりこちらの方がメンテナンスもしやすいと考え、検証をすすめました。
object HelloWorldKotlinSpecTest : Spek({
given("HelloWorldServiceクラス") {
val testClass = HelloWorldService()
on("method1メソッドが□□の場合") {
val result = testClass.method1()
it("□□になる") {
Assert.assertEquals(2, result)
}
}
}
})
検証すすめていたところ・・・
テストコードを動かす際にSpringFrameworkで管理しているBeanの注入が出来ませんでした。
SprintFrameworkのテストサポートでは、@Test
アノテーションがついたテストメソッドが必須のようです。
ところが、Spekだとコンストラクタ引数にテストコードを記載するため、テストメソッドが定義できません。
試したこと その2: KotlinTest (テストフレームワーク)
StringSpec, FunSpec, ShouldSpec, WordSpec, FeatureSpec, BehaviorSpec等、様々な書き方がサポートされており多機能なフレームワークです。
ここでは、上記のSpekと同等な記載方法である、BehaviorSpecを試してみました。
// StringSpec, FunSpec, ShouldSpec, WordSpec, FeatureSpec, BehaviorSpec(以下の例で使用), FreeSpec がある。
// PropertyTesting, TableDrivenTesting等の仕組みも用意されている。
object HelloWorldKotlinTest : BehaviorSpec() {
init {
given("HelloWorldServiceクラス") {
val testClass = HelloWorldService()
`when`("□□の場合") {
val result = testClass.method1()
then("□□になる") {
Assert.assertEquals(2, result)
}
}
}
}
}
こちらもSpekと同じく・・・
試したこと その3: Mockit-Kotlin (モックフレームワーク)
このモックフレームワークは、Javaでも有名なMockitを、Kotlinから扱いやすく関数でラップしたフレームワークです。
以下の通りChildService.childMethod1()
の部分モック化に成功しました。
class HelloWorldKotlinTest {
@Autowired
private lateinit var testClass: HelloWorldService
@Test
fun method1_口口の場合_口口になる() {
val mock = mock<ChildService> {
on { childMethod1() } doReturn 2 // <--- mockit-kotlin 外部依存メソッドの部分モック化
}
Assertions.assertThat(testClass.method1())
.`as`("処理結果が期待通りであることを確認")
.isEqualTo(2)
}
}
しかし、テストメソッドを増やしていったら問題が起きました・・・
あるテストメソッド内で部分モック化したクラスChildService
が、後続のテストメソッドで正常に動作しない場合がありました。ChildService
が自分自身のフィールドを参照する処理が実行される際に、フィールドが常にnullに見えてしまうのです。
調査を進めてみると、部分モック化した外部クラスがCGLIB Proxy(SpringAOPによる処理のインジェクション時に利用されるProxyクラス) でラップされている場合に起こる事象のようです。
Proxyが正常に動作せずにChildService
の実態クラスが持つフィールドを見ることが出来ていないような挙動でした。
結果、Kotlinで採用したテストコードの書き方
以下のようなテストコードで書く方針にしました。
以前のJavaテストコードとそっくりですね。
class HelloWorldJavaTest {
@Autowired // <------------------------------------------------- Spring Beanの注入
private lateinit var testClass: HelloWorldService
@Before // <---------------------------------------------------- JUnit テスト事前処理
fun setUp() {
// 前提条件を整える(テストデータ準備、日時の固定化等)
}
@Test // <------------------------------------------------------ JUnit テスト
fun method1_口口の場合_口口になる() {
object : MockUp<ChildService>() {
@Mock // <---------------------------------------------- JMockit 外部依存メソッドのモック化
fun childMethod1(): Int {
return 2
}
}
Assertions.assertThat(testClass.method1()) // <------------- AsserJ アサーション(検証処理)
.`as`("処理結果が期待通りであることを確認")
.isEqualTo(2)
}
}
Kotlin+SpringFrameworkでのテストコードを書く際に問題が発生したこともありますが、
従来のJava技術者の移行しやすさも考え、まずはJavaと似た感じのテストコードとしました。
Kotlinの代表的な検証フレームワーク(Kluent, Expekt, Hamkrest)は今回試しませんでしたが、今後Kotlinでのテストコードでノウハウを蓄積しながら段階的に試してきたいと思います。
まとめ
Kotlinのテストフレームワークを使用する際、SpringFrameworkとの相性問題が出ることがありました。
しかし、KotlinはJavaとの親和性が高いため、Javaのテストフレームワークを使用することでもテストコードを記述することができました。
Kotlinを正式にサポートしているSpring5が出てまだ日が浅いため、上記のような相性問題は残っていると思いますが、良いテストコードが書けるように引き続き検証を進めて行きたいと思います。
この記事へのコメントはありません。