クマは森で用を足しますか?

アウトプットは重要です。

Continuation による AssertionError で MockK に乗り換える

Repository 以下はだいたいこんな感じ

他の人が書かれた Kotlin のコードを見ながら、適当にそれらしく書いてみています。そんな調子でもそこそこちゃんと動いてくれるので、Kotlin の基礎みたいなところを全く習得できていないような気はします。

Android の TelephonyManager API を使って SIM カードに対して APDU コマンドの送受信を行う TelephonyInterface クラスを実装した後、Repository パッケージと Cache I/O パッケージを追加して(いろいろ端折っていますが)こんな感じになっています。

f:id:cheerio-the-bear:20200418233424p:plain
Repository / DataStores

Cache I/O パッケージ (link)

Room も今回初めて使ってみたのですが、データベース制御的なクラスがあまりに簡単に書けてしまうことにちょっとびっくりです。テンプレート的なものに適当な SQL 文を書き添えるだけで出来てしまうなんて。

f:id:cheerio-the-bear:20200418233636p:plain
DataStore (Cache I/O)

SIM アプリケーション上にある各種 EF/DF の FCP テンプレートは、おそらくそう簡単に変わるものではないでしょう。ファイルツリーを走査したときに得られる FCP テンプレートだけ、カードの ICCID と紐付けて ”SelectResponse” データベースに残すようにしようと考えています。ファイルツリーの走査を終えたカードの ICCID は "CachedSubscription" データベースに書き、キャッシュ処理を完了していることを覚えておくようにしようと思います。

Repository パッケージ (link)

Repository を通して送信できる APDU コマンドは、この時点では SELECT と READ BINARY、そして READ RECORD だけです。いずれ VERIFY PIN や UPDATE BINARY 等も必要になりますが、また折を見て追加してゆこうと思います。

f:id:cheerio-the-bear:20200418233615p:plain
Repository

Android のテレフォニーフレームワークの中に ApduSender というクラスがいるのですが、ほんの幾つかの APDU コマンドを投げただけで(というかおそらく現状実装では STORE DATA コマンドばかりなので概ね 1 コマンド毎に)論理チャネルをクローズしてしまいます。それに倣うかたちで設計すると論理チャネルのオープン・クローズを頻繁に発生させてしまいそうなので、ひとまずクローズ条件を「500ms タイマーをかけている間に後続のリクエストが来ない場合」にしておきます。

Mockito から MockK に乗り換えました

Mockito を使って CardRepository クラスのテストコードを書き始めたのですが、Suspend 関数の呼び出しを Verify しようとすると下記のエラーが発生しました。どうやら、Kotlin によって暗黙的に追加されている第二引数の Continuation の関係でアサートに失敗しているようですが、簡単に解決できそうな方法を速やかに見つけることができず。

java.lang.AssertionError: Verification failed: call 1 of 1:
SelectResponseDataSource(#2).insert(eq(
    SelectResponse(iccId=8988211000000282106F, aid=, path=, fileId=2FE2, data=[-104, -120, 18, 1, 0, 0, 32, 40, 1, -10], sw=36864)), any())).
    Only one matching call to
    SelectResponseDataSource(#2)/insert(SelectResponse, Continuation) happened, but arguments are not matching:

[0]: argument: SelectResponse(iccId=8988211000000282106F, aid=, path=, fileId=2FE2, data=[98, 30, -126, 2, 65, 33, -125, 2, 47, -30, -91, 6, -64, 1, 0, -54, 1, -128, -118, 1, 5, -117, 3, 47, 6, 4, -128, 2, 0, 10, -120, 0], sw=36864), matcher: eq(SelectResponse(iccId=8988211000000282106F, aid=, path=, fileId=2FE2, data=[-104, -120, 18, 1, 0, 0, 32, 40, 1, -10], sw=36864)), result: -
[1]: argument: continuation {}, matcher: any(), result: +

Kotlin の Coroutines にも対応していると書かれていた MockK に移行し、テストコードの続きを書きました。下記コードに登場するモックたちの関数は全て Suspend 関数なのですが、Verify も問題なく動いてくれています。今後のテストコードは、MockK を使って書くようにします。

    @Test
    fun initialize_notCached() = runBlocking {
        coEvery { subscriptionIoMock.get(ICCID) } returns null
        coEvery { cacheIoMock.delete(any()) } answers { nothing }
        coEvery { cacheIoMock.insert(any()) } answers { nothing }

        assertThat(repository.initialize()).isTrue()
        assertThat(repository.isAccessible).isTrue()
        assertThat(repository.isCached).isFalse()

        // Query is not yet available before finishing the caching operation.
        assertThat(repository.queryFileControlParameters(LEVEL_ADF)).isEmpty()

        coVerifyOrder {
            cacheIoMock.delete(ICCID)
            cacheIoMock.insert(SelectResponse(ICCID, FileId.AID_NONE,
                    FileId.PATH_MF, CardRepository.EF_ICCID,
                    hexStringToByteArray(FCP), Result.SW_NORMAL))
        }
    }

MockK を導入したときのコミットはこちら。

github.com

本家サイトの説明と、その日本語訳まとめページも参考にさせて頂いています(ありがとうございます)。

mockk.io
qiita.com

前後の記事というか日記

普段の業務では使わない Kotlin を学ぶべく、のんびりコードを書いています。

前回の記事というか日記はこちら。

cheerio-the-bear.hatenablog.com

次回はこちらです。

cheerio-the-bear.hatenablog.com