2
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Organization

Ktorのテストメソッドから少しだけ学ぶKotlin

この記事は ハンズラボ 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を、
自分らのアプリケーションにあわせてセットアップして、
テストをするとのことです。

ApiTest.kt
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アプリケーションを動作させます。

Api.kt
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では、関数の最後のパラメータが関数である場合、
そのパラメータは括弧の外に指定することができるという慣習があります

そのため、

ApiTest.kt
        handleRequest(HttpMethod.Get, "/").run {
            assertEquals(HttpStatusCode.OK, response.status())
            assertEquals("Hello, world!", response.content)
        }

の部分が、
TestApplicationEngine.()に相当します。

ここで、handleRequestの中身を確認します。

TestEngine.kt
/**
 * 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が用意している標準ライブラリで、
letapplyと同様で、
関数オブジェクトを引数にし、
あるクラスインスタンスに対してその関数オブジェクトを適用することができるものです。

といったことで、
Ktorのテストメソッドを記述したとき、
それぞれの関数がどのように処理するか、
確認することができました。

APIを作るところまでたどり着けていませんが、
Kotlinの文法について、
理解を深めることができたと思います。

引き続き、
テストメソッドを作成しつつ、
APIを作成していきたいと思います。

ハンズラボ Advent Calendar 2018
9日目は、@kurapy-n さんです!

参考にさせて頂きました

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
2
Help us understand the problem. What are the problem?