20190203のKotlinに関する記事は5件です。

Ktorでテスト入門 〜とりあえずgetとpostをやってみる〜

概要

サーバーサイドKotlinフレームワークのKtorのスモークテストやるぞ!というわけで今回は、以下のドキュメントからgetとpostのテスティングを抽出して記事化しました!
Testing Server Applications

環境

  • macOS High Sierra 10.13.6
  • IntelliJ IDEA 2018.3.1
  • Java 1.8.0_191
  • Kotlin 1.3.11
  • Ktor 1.1.1

Ktorの環境構築とかはこちらが参考になるかと。
KtorでHello World

構成

今回はDocker Composeで構築したnginxにKtorを導入してます(割愛)

コード

まず、共通処理としてテスト用のControllerを作成します。
doWithTestApplicationメソッドは設定読み込みとバグ回収のためwith(engine)を読み出しています。
この設定を読み込む際にDB周りも初期化されるため、DB Connection数の上限に気をつけてください。
getメソッドとpostメソッドはそれぞれのエンドポイントに対して、それぞれのメソッドで呼び出すためのものです。doWithTestApplicationでラップして、そのコールバックに処理を書いています。
基本的にはRequest Headerの設定とpostの場合のみ、bodyの指定を行っています。

APITestController.kt
@KtorExperimentalAPI
class APITestController {
    private fun doWithTestApplication(proc: TestApplicationEngine.() -> Unit) {
        val engine = TestApplicationEngine(createTestEnvironment {
            config = ///設定を読み込む処理
        })
        engine.start(wait = false)

        /// https://github.com/ktorio/ktor/issues/358#issuecomment-372897523
        with(engine) {
            proc()
        }
    }
    fun get(url: String, headers: Headers? = null, proc: TestApplicationCall.() -> Unit) = doWithTestApplication {
        handleRequest(HttpMethod.Get, url) {
            headers?.forEach { s, list ->
                list.forEach {
                    addHeader(s, it)
                }
            }
        }.proc()
    }

    fun post(url: String, body: String, headers: Headers? = null,  proc: TestApplicationCall.() -> Unit ) = doWithTestApplication {
        handleRequest(HttpMethod.Post, url) {
            headers?.forEach { s, list ->
                list.forEach {
                    addHeader(s, it)
                }
            }
            setBody(body)
        }.proc()
    }
}

テストケース側では、上記のControllerを使ってテストケースを組み立てます。
getの場合、header指定がないのであればctrl.get("エンドポイント")みたいな書式でかけます。
コールバックに正常系期待値判定を行います。以下の/xxx/aaa の場合、httpレスポンスが200なら正、としています。
postの場合、ボディとヘッダーの指定が必要となります。(最初ヘッダーを指定していなくてエラーでハマった…)
ボディはlistOf("key" to "value").fromUrlEncode()とすることで比較的平易に値の設定が可能になっています。
ヘッダーはHeaders.buildのコールバックにappendで HttpHeadersのプロパティ値をキーに、値は例にあるように、たとえばContentTypeであればContentType.Application.FormUrlEncoded.toString()のような形で指定可能です。最初、content typeを未指定にしていたところpostがエラーになってしまい、なかなか原因追求ができませんでした(分かれば当たり前なのですけれど)ちなみに、postの場合の期待値はルートディレクトリにリダイレクトされるのを正としています。

XXXControllerTest.kt
@KtorExperimentalAPI
class XXXControllerTest {

    val ctrl = APITestController()

    @Test
    fun recreate() = ctrl.get("/xxx/aaa") {
        assertEquals(HttpStatusCode.OK, response.status())
    }

    @Test
    fun recreatePost() = ctrl.post("/xxx/aaa",
                                    listOf("email" to "example@example.com").formUrlEncode(),
                                    Headers.build { append(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString()) }) {
        assertEquals(HttpStatusCode.Found, response.status())
        assertEquals("/", response.headers.get("Location"))
    }
}

結果

こんな感じでルーティングの各エンドポイントを網羅することでスモークテストが実現可能になりました!

ハマったこと・課題など

  • postのときのヘッダー設定がなかなかわからなかった
  • TestApplicationEngine を作成するたびに DB connection数が増えるため上限に気をつけること

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Kotlin】Sequenceオブジェクト生成方法5種

Kotlin の Sequence オブジェクトの生成方法を5種紹介します。

asSequence 拡張関数

Sequence を使ったことがある方は皆さんご存知でしょう。
List インターフェイスなどが継承している Iterable インターフェイスや、 ArrayIntArray などの配列系クラス。それらの拡張関数 asSequence を使うことで、それらを Sequence 化できます。

    val sequence = listOf('A', 'B', 'C').asSequence()
    sequence.forEach { print(it) } // > ABC

なお、関数名は toSequence ではなく asSequence です。
asSequence 関数で生成した Sequence オブジェクトには、元となるオブジェクトへの変更が反映されます。

    val mutableList = mutableListOf('A', 'B')
    val sequence = mutableList.asSequence()
    mutableList += 'C'
    sequence.forEach { print(it) } // > ABC

toList 関数などでは、元となるオブジェクトへの変更が反映されません。
この違いが関数名に現れているのでしょう。

sequenceOf 関数

引数で与えられた要素から Sequence オブジェクトを生成します。

    val sequence = sequenceOf('A', 'B', 'C')
    sequence.forEach { print(it) } // > ABC

Sequence インターフェイスの拡張関数

filtermap などの、Sequence オブジェクトを加工して別の Sequence オブジェクトを生成する、Sequence インターフェイスの拡張関数です。
コレクション系の型の拡張関数と同じ感覚で使えます。

    val sequence = sequenceOf(0, 1, 2)
        .map { 'A' + it }
    sequence.forEach { print(it) } // > ABC

generateSequence 関数

要素を生成する関数1から Sequence オブジェクトを生成します。
関数が null を生成すると終端するため、要素に null を含む Sequence オブジェクトは生成できません。
また、生成した Sequence オブジェクトは一度しか使えません。
このような制約があるものの、ある程度自由に手軽に Sequence を作成できます。

    var char = 'A'
    val sequence = generateSequence {
        if (char > 'C') null
        else char++
    }
    sequence.forEach { print(it) } // > ABC
    sequence.forEach { print(it) } // IllegalStateException をスローする。

sequence 関数

本記事で最もお伝えしたい方法です。
任意の Sequence を簡単に作成できます。

    val sequence = sequence {
        var char = 'A'
        while(char <= 'C') {
            yield(char++)
        }
    }
    sequence.forEach { print(it) } // > ABC
    sequence.forEach { print(it) } // > ABC

使い方

sequence 関数の引数である関数(以下、 block と表記)の中で yield 関数を呼び出すごとに、yield 関数に引数として渡した値が、生成した Sequence オブジェクトの要素となります。
block の処理が終了すると終端します。

yield 関数を呼び出して要素を生成すると、次の要素が要求されるまで block の処理は停止します。
そのため、終わりがない数列を表す Sequence オブジェクトを生成するような場合でも、必要な計算のみが行われます。

解説

blockSequenceScope クラスをレシーバーとする拡張関数です。
したがって、block 内では this.yield(もしくは this. を省略して yield)とすることで、SequenceScope#yield メソッドを呼び出せます。

yield 関数は中断関数(suspending function)です。
中断関数は、呼び出されてから処理を返すまでの間に処理を「中断」することができます。
「中断」すると、続きの処理は非同期で行われることになります。
中断関数は Kotlin 1.3 で正式に採用された Coroutines の機能に関連するものです。詳しくはこの辺りをご覧ください。→ Coroutines Overview

例:フィボナッチ数列

    val fibonacci = sequence {
        var a = 0
        var b = 1
        while (true) {
            yield(b)
            val c = a + b
            a = b
            b = c
        }
    }

    fibonacci
        .take(10)
        .forEach { println(it) }


出力

1
1
2
3
5
8
13
21
34
55

おわりに

以上、Sequence オブジェクトの生成方法を5種紹介しました。
一通り網羅したつもりですが、他にもあればご教授ください。


  1. ラムダ式を使う場合がほとんどでしょう。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

KtorのAutoReloadを有効にしました。

おはようございます。@naokiurです。
よろしくお願い致します。

2019年も引き続き、
個人的にKotlin/Ktorに興味を持っている毎日です。

AutoReload、便利ですよね。
公式に習い、設定したいと思います。

環境

  • MacBook Pro (Retina 13-inch、Early 2015)
  • macOS High Sierra 10.13.6
  • Java 1.8.0_191
  • IntelliJ IDEA CE 2018.3.4

実施したこと

  • application.confを修正した。

結果

  • AutoReloadが有効になった。

AutoReloadの設定

実施方法は2つあります。

  • embeddedServerに記載する
  • application.confに記載する

自分の今回のコードでは、 embeddedServerでサーバを起動しておらず、
application.confに設定を記載し、
io.ktor.server.netty.EngineMainを実行することで、
サーバを起動しておりました。

そのため、
application.confに記載する」方法を取りました。

application.conf
ktor {
    deployment {
        port = 8080
        watch = [ out/production ] // ← 追記する
    }

    application {
        modules = [ jp.ne.naokiur.api.ExecutorKt.api ]
    }
}

application.conf内の watchキーに、
今回のコードのコンパイル出力フォルダを設定します。
IntelliJのデフォルトになっているため、
out/productionを設定しました。

これによって、
コンパイル出力フォルダに変更があった場合、
AutoReloadするようになりました。

サーバ起動時、以下のようなログが出力されます。
16:36:19.876 [main] DEBUG Application - Watching /絶対パス/out/production/classes/〜 for changes.

ただし

コンパイル出力フォルダに変更があった場合なので、
gradle buildや、
IntelliJのcmd + F9で Build Projectを実施する必要があります。

勘違いしたところ

熟読しなかった自分のせいなのですが、
公式に watch = [ module1, module2 ]と書いてあったため、
最初、今回のコードのモジュール名である、 apiを設定してしまいました。

これでは有効にならず、
AutoReloadが有効になっていない旨の、
以下のようなログが出力されてしまいました。
16:20:24.636 [main] INFO Application - No ktor.deployment.watch patterns match classpath entries, automatic reload is not active

そのあたりのことは、ちゃんと公式にかかれていました…。

file:///path/to/project/build/classes/myproject.jarというクラスパスならば、
to/projectだとマッチします。
しかしパッケージ名のような com.mydomainではマッチしません。

今後

DBアクセスを実施したいと思っています。

参考にさせて頂きました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

KtorのAutoReloadを有効にする。

おはようございます。@naokiurです。
よろしくお願い致します。

2019年も引き続き、
個人的にKotlin/Ktorに興味を持っている毎日です。

AutoReload、便利ですよね。
公式に習い、設定したいと思います。

環境

  • MacBook Pro (Retina 13-inch、Early 2015)
  • macOS High Sierra 10.13.6
  • Java 1.8.0_191
  • IntelliJ IDEA CE 2018.3.4

実施したこと

  • application.confを修正した。

結果

  • AutoReloadが有効になった。

AutoReloadの設定

実施方法は2つあります。

  • embeddedServerに記載する
  • application.confに記載する

自分の今回のコードでは、 embeddedServerでサーバを起動しておらず、
application.confに設定を記載し、
io.ktor.server.netty.EngineMainを実行することで、
サーバを起動しておりました。

そのため、
application.confに記載する」方法を取りました。

application.conf
ktor {
    deployment {
        port = 8080
        watch = [ out/production ] // ← 追記する
    }

    application {
        modules = [ jp.ne.naokiur.api.ExecutorKt.api ]
    }
}

application.conf内の watchキーに、
今回のコードのコンパイル出力フォルダを設定します。
IntelliJのデフォルトになっているため、
out/productionを設定しました。

これによって、
コンパイル出力フォルダに変更があった場合、
AutoReloadするようになりました。

サーバ起動時、以下のようなログが出力されます。
16:36:19.876 [main] DEBUG Application - Watching /絶対パス/out/production/classes/〜 for changes.

ただし

コンパイル出力フォルダに変更があった場合なので、
gradle buildや、
IntelliJのcmd + F9で Build Projectを実施する必要があります。

勘違いしたところ

熟読しなかった自分のせいなのですが、
公式に watch = [ module1, module2 ]と書いてあったため、
最初、今回のコードのモジュール名である、 apiを設定してしまいました。

これでは有効にならず、
AutoReloadが有効になっていない旨の、
以下のようなログが出力されてしまいました。
16:20:24.636 [main] INFO Application - No ktor.deployment.watch patterns match classpath entries, automatic reload is not active

そのあたりのことは、ちゃんと公式にかかれていました…。

file:///path/to/project/build/classes/myproject.jarというクラスパスならば、
to/projectだとマッチします。
しかしパッケージ名のような com.mydomainではマッチしません。

今後

DBアクセスを実施したいと思っています。

参考にさせて頂きました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AACのViewModelをFactoryクラス不要で簡単に生成できる拡張関数

Androidで、MVVM及びdatabindingを行う場合、Android Architecture ComponentsのViewModelを使うことが多いと思います。
このViewModelを生成する際、コンストラクタに引数が必要だとFactoryクラスを作成する必要がありますが、ViewModelが多くなってくるとその分Factoryも増えていき、作成・管理が面倒になったので拡張関数を作成しました。

結論

忙しい人のために先に結論
Fragment.getViewModelFragment.viewModelProviderを作りました。

使い方

class SampleFragment: Fragment(), SampleNavigator {
    // フィールドでvalで持ちたい場合は、viewModelProvider
    private val viewModel by viewModelProvider {
        val str = requireNotNull(arguments?.getString(ARG_SAMPLE_STR))
        SampleViewModel(str, this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val str= requireNotNull(arguments?.getString(ARG_SAMPLE_STR))
        // 一時的に使用したい(or lateinit varで初期化したい)場合はgetViewModel
        val vm = getViewModel { SampleViewModel(str, this) }

        // ActivityのViewModelを参照したい場合
        val viewModelOfActivity = requireActivity().getViewModel{ SampleActivityViewModel() }
    }

    // 本質外
    companion object {
        private const val ARG_SAMPLE_STR = "ARG_SAMPLE_STR"

        fun newInstance(str: String) = SampleFragment().apply {
            arguments = bundleOf(ARG_SAMPLE_STR to str)
        }
    }
}

for Fragment

FragmentExt.kt
inline fun <reified VM : ViewModel> Fragment.getViewModel(crossinline factory: () -> VM): VM {
    return ViewModelProviders.of(this, object : ViewModelProvider.Factory {
        override fun <VM : ViewModel> create(modelClass: Class<VM>): VM {
            return requireNotNull(modelClass.cast(factory()))
        }
    }).get(VM::class.java)
}

inline fun <reified VM : ViewModel> Fragment.viewModelProvider(crossinline factory: () -> VM) = lazy {
    getViewModel { factory() }
}

for FragmentActivity

上の拡張関数で生やす元がFragmentActivityになっただけ

FragmentActivityExt.kt
inline fun <reified VM : ViewModel> FragmentActivity.getViewModel(crossinline factory: () -> VM): VM {
    return ViewModelProviders.of(this, object : ViewModelProvider.Factory {
        override fun <VM : ViewModel> create(modelClass: Class<VM>): VM {
            return requireNotNull(modelClass.cast(factory()))
        }
    }).get(VM::class.java)
}

inline fun <reified VM : ViewModel> FragmentActivity.viewModelProvider(crossinline factory: () -> VM) = lazy {
    getViewModel { factory() }
}

解説

Fragment(とFragmentActivity)に拡張関数を生やして、ViewModelProviders.of().get()をいい感じにしてくれればゴールです。
ViewModelProviders.ofに必要なのは

  • Fragment
  • ViewModelProvider.Factory クラスをもらってViewModelを返すもの
  • get(ViewModelのクラス)

Fragmentはthisが使えます。
ViewModelProvider.Factoryの中身は、createを実装するもので、これはViewModelを生成して返せばゴールです。
Factoryは、引数を拡張関数は知らなくて良い(外に任せる)ので、引数無しでVMを返す高階関数で表現できます。
factory: () -> VM
getにはクラスを入れる必要があります。
拡張関数の引数でもらってきて入れるのが簡単ですが、VMはViewModelを継承したクラスと分かっているので、VM::class.javaができればクラスを引数に取る必要はありません。
そこで reifiedの登場です。

reified

3行でまとめると

reified type paramter = 具象化された型パラメータ
→reifiedを使うことで、T::class.javaを実現できる
→reifiedを使うには、inline関数である必要がある

公式サイトや太郎さんの記事が参考になります。

公式サイト
日本語
太郎さんのサイト

reifiedを使えば良いですが、このためにはinline関数にする必要があります。
ただ、crossinlineをつけないとfactory: () -> VMがcreate内から参照できません。

crossinline

まとめると

inline関数では、渡されてラムダを関数本体から直接ではなく、
ローカルオブジェクトやネストされた関数などの別の実行コンテキストから呼び出すことは許可されていない
→ラムダパラメータにcrossinlineをつけることで可能になる

詳しくは公式サイトのreifiedの上に載っています。

requireNotNull(modelClass.cast(factory()))
は警告回避のために行っています。
as TのUNCHECKED_CASTの回避のために modelClass.cast(factory())
NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONSの回避のために requireNotNull

以上を組み合わせることで拡張関数を実現しています。
調べていたとき、reifiedが便利すぎて感動しました。
T::class.javaが出来るなんてジェネリクス好き歓喜。

道行き

結論に至るまでの道のりを備忘録がてら記載。

ViewModelProvidersの利用

通常、AACのViewModelを取得するためには、ViewModelProviders.ofを利用します。

// 引数がない場合
ViewModelProviders.of(Fragment).get(SampleViewModel::class.java)
// 引数が必要な場合
ViewModelProviders.of(Fragment, SampleViewModelFactory(arg1, arg2)).get(SampleViewModel::class.java)

多くの場合、引数が必要な場合はViewModelクラスと対になるFactoryClassを作ります。

SampleViewModelFactory.kt
class SampleViewModelFactory(arg1: String, arg2: Int) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass != SampleViewModel.class.java) {
            throw IllegalArgumentException("ViewModel class is not SampleViewModel")
        }
        return TextToSpeechDemoViewModel(arg1, arg2) as T
    }
}

Util化

毎回クラスを作るのは面倒です。
引数を見てみると、必要なのはViewModelProder.Factoryとなっています。
Factoryクラスを作るときに、実装しましたね。要はこれがあればよいのです。
image.png
リファレンス

Factoryの中にはcreateメソッドがあり、中を見てみるとクラスをもらって引数を利用してViewModelを生成し、Tにキャストして返しています。
ViewModelによって必要な引数はバラバラです。
なのでViewModelを生成する処理は引数にとり外に任せることにしましょう。
ViewModelを生成する処理を高階関数 () -> Tとして引数に渡せば、生成ロジックを外に任せることができます。
これでUtilクラスで汎用的にできそうです。

ViewModelProvidersUtils.kt
// 引数がない場合の生成メソッド
fun <T : ViewModel> create(fragment: Fragment, clazz: Class<out T>): T {
    return ViewModelProviders.of(fragment, object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return clazz.newInstance() as T
        }
    }).get(clazz)
}


// 引数がある場合の生成メソッド
fun <T : ViewModel> create(fragment: Fragment, clazz: Class<out T>, factory: () -> T): T {
    return ViewModelProviders.of(fragment, object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return factory() as T
        }
    }).get(clazz)
}

使う側

// 引数なし
val vm = ViewModelProvidersUtils.createViewModel(SampleViewModel::class.java)
// 引数あり
val vm = ViewModelProvidersUtils.createViewModel(SampleViewModel::class.java) {
    SampleViewModel(arg1, arg2)
}

Utilの2つのメソッドの実装をよく見ると、ほとんど同じ処理をしています。
引数一つの createViewModelは、引数二つの createViewModelfactory() as Tに当たる部分でclazz.newInstance()をしていますね。
kotlinはデフォルト引数が使えるので一つにできます。

ViewModelProvidersUtils.kt
fun <T : ViewModel> create(fragment: Fragment, clazz: Class<out T>, factory: () -> T = { clazz.newInstance() as T }): T {
    return ViewModelProviders.of(fragment, object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return factory() as T
        }
    }).get(clazz)
}

これで、
第二引数を指定しない場合はclazz.newInstance()が行われる
指定すれば引数ありの場合に対応できる
ようになりました。

拡張関数化

必ずFragmentから呼ぶもので、汎用的に使えそうなので拡張関数にしてみましょう。

FragmentExt.kt
fun <T : ViewModel> Fragment.createViewModel(clazz: Class<out T>, factory: () -> T = { clazz.newInstance() as T }): T {
    return ViewModelProviders.of(this, object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return factory() as T
        }
    }).get(clazz)
}

使う側

Fragment内からの呼び出し
// 引数なし
val vm = createViewModel(SampleViewModel::class.java)
// 引数あり
val vm = createViewModel(SampleViewModel::class.java) {
    SampleViewModel(arg1, arg2)
}

ここで拡張関数を見てみると、(人間的には)返す型がTと決まっているので、クラスを引数に渡すのは無駄じゃない?と思います。
.get(clazz)のところをT::class.javaとできれば、clazz: Classを無くせそうです。
そこで、reifiedが登場し、最終形になります。
解説へと続くわけです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む