この記事は ハンズラボ Advent Calendar 2018 8日目の記事です。
おはようございます。@naokiurです。
今年も担当させて頂きます。
業務ではPythonとJavaScriptを使用して開発を行っておりますが、
完全に趣味で、Kotlinに興味があり、
KotlinConf 2018 報告会に参加させて頂いたところ、
Ktorという、
JetBrains謹製のWebフレームワークがあると(ようやく)知ったので、
Ktorを試しました。
前職でJavaをやっておりましたが、
Kotlinはぜんぜん初学者なので、
合わせて言語自体の習得もしていきたいです。
環境
- MacBook Pro (Retina 13-inch、Early 2015)
- macOS High Sierra 10.13.6
- Java 1.8.0_152
- IntelliJ IDEA CE 2018.2.5
実施したこと
- APIのテストクラスを作成する
わかったこと
- 拡張関数
- レシーバ付きの関数リテラル
- 標準ライブラリ
run
使用するもの
ライブラリ
クラス
- Application
- TestApplicationEngine
関数
- withTestApplication
- run
自作
関数
- testHello
- Application.api
- TestApplicationEngine.handleRequest
結果
テストクラスを作成する
APIをテストするテストクラスを作成します。
Ktorのテストは、
withTestApplication
を、
自分らのアプリケーションにあわせてセットアップして、
テストをするとのことです。
package jp.ne.naokiur.api
import io.ktor.application.Application
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.handleRequest
import io.ktor.server.testing.withTestApplication
import kotlin.test.Test
import kotlin.test.assertEquals
class ApiTest {
@Test
fun testHello() = withTestApplication(Application::api) {
handleRequest(HttpMethod.Get, "/").run {
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("Hello, world!", response.content)
}
}
}
Application::api
の部分には、あとで作成する、
自分のアプリケーションを設定します。
しかしちょっとまって!
Kotlin初学者の自分、
これがなにをやっているかわかりません…!
いわゆる おまじない
みたいな形になってしまうのは良くない!
ということで、これが何をやっているか、
ちゃんと調べました。
未熟な点があるかと思いますので、
誤っている点がありましたら、
ご指摘いただけると幸いです。
fun testHello() = withTestApplication(Application::api)
既存の関数を、定義する関数に代入できるので、
io.ktor.server.testing.withTestApplication
という KtorのTest用関数を代入します。
代入時に、自分のアプリケーション用にSetupしています。
withTestApplication
の引数は、以下の2つ
moduleFunction: Application.() -> Unit
test: TestApplicationEngine.() -> R
両方とも、関数が渡されることを期待しています。
Javaも、Java8から関数を引数に渡すことができるようになったかと思いますが、
Kotlinもそれをすることができます。
moduleFunction: Application.() -> Unit
Application.()
は、
io.ktor.application.Application
クラスの関数を期待していることを表します。
このクラスは、Ktorにおいて非常に重要な意味を持つクラスで、
Webアプリケーションを動作させ、リクエストをハンドリングするために必要なクラスです。
雑な解釈ですが、
Springの SpringApplication
と似たようなものだと思っております。
Ktorでは、 Application
クラスの 拡張関数
を作成し、
Webアプリケーションを動作させます。
package jp.ne.naokiur.api
import io.ktor.application.Application
fun Application.api() {
// some setting, routing.
}
拡張関数
というのは、
Application.api() {}
の部分です。
元の Application
クラスに、 api
という関数はありませんが、
自分で任意の関数を、そのクラスに追加することができます。
こうすることで、自分のアプリケーションを定義しているのですね。
上述の通り、withTestApplication
の第一引数は、
io.ktor.application.Application
クラスの関数を期待しているため、
メソッド参照(ClassName::MethodName
)で、
自分のアプリケーションを渡してあげます。
test: TestApplicationEngine.() -> R
ここでも関数を期待していて、
io.ktor.server.testing.TestApplicationEngine
クラスの関数を期待していることを表します。
そのため、
withTestApplication(Application::api, TestApplicationEngine::hoge)
というような記述になるかと思えば、
そうはなっておりません。
なぜならば、
Kotlinの慣習で、以下があるためです。
Kotlinでは、関数の最後のパラメータが関数である場合、
そのパラメータは括弧の外に指定することができるという慣習があります
そのため、
handleRequest(HttpMethod.Get, "/").run {
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("Hello, world!", response.content)
}
の部分が、
TestApplicationEngine.()
に相当します。
ここで、handleRequest
の中身を確認します。
/**
* Make a test request
*/
fun TestApplicationEngine.handleRequest(
method: HttpMethod,
uri: String,
setup: TestApplicationRequest.() -> Unit = {}
): TestApplicationCall = handleRequest {
this.uri = uri
this.method = method
setup()
}
handleRequest
は、TestApplicationEngine
に属する関数であるため、
withTestApplication
が期待する、
TestApplicationEngine.()
に相当することがわかります。
そして、fun TestApplicationEngine.handleRequest()
というこの記述方法、
これはレシーバ付きの関数リテラル
と呼ばれるものです。
TestApplicationEngine
をレシーバとし、
引数は method
, uri
, setup
、
戻り値は TestApplicationCall
型のレシーバ付き無名関数
です。
今回の例では、 setup
は指定していないのですが、
setup
はデフォルトの引数が定められているため、
指定しない場合、 デフォルトの引数が用いられます。
ここでは、 {}
ですね。
最後の引数が関数であるとき、
括弧の外に指定すると、
期待している型を推論してくれます。
そのため、 TestApplicationEngine.handleRequest
を、
handleRequest
と呼び出すことが可能です。
handleRequest
を実行すると、
関数リテラルである TestApplicationCall = handleRequest
を実行します。
この handleRequest
で、リクエストを作成し、
その結果のクラスTestApplicationCall
に対して、
run
を実行し、response等を検証します。
run
はKotlinが用意している標準ライブラリで、
let
や apply
と同様で、
関数オブジェクトを引数にし、
あるクラスインスタンスに対してその関数オブジェクトを適用することができるものです。
といったことで、
Ktorのテストメソッドを記述したとき、
それぞれの関数がどのように処理するか、
確認することができました。
APIを作るところまでたどり着けていませんが、
Kotlinの文法について、
理解を深めることができたと思います。
引き続き、
テストメソッドを作成しつつ、
APIを作成していきたいと思います。
ハンズラボ Advent Calendar 2018 、
9日目は、@kurapy-n さんです!