MockK-Kotlinのモック作成用ライブラリ

MockKロゴ Kotlinはまだ非常に新しい技術であり、これはより良いことをする多くの機会があることを意味します。 私にとって、これが道でした。 Nettyとコルーチンで簡単なWeb処理レイヤーを書き始めました。 すべてが整然としていて、ルーティング、Webソケット、DSL、完全な非同期性を備えたWebフレームワークのようなことさえしました。 初めて、すべてが簡単に学べるようになりました。 実際、コルーチンはコールバックを線形で読み取り可能なコードにします。







すべてをテストし始めたとき、サプライズが私を待っていました。 Kotlinとモックは互換性のあるものにするのが難しいことがわかります。 まず第一に、最後のフィールドのためです。 さらに、kotlinをテストするためのライブラリが1つだけあり、これはMockitoです。 DSLのようなものを提供するラッパーが作成されました。 しかし、ここでは、すべてが順調に進んでいるわけではありません。 まず、名前付きパラメーターを使用して関数をテストしています。 Mockitoでは、すべてのパラメーターをマッチャーの形で絶対に設定する必要がありますが、Kotlinでは多くの場合これらのパラメーターが多く、一部にはデフォルト値があります。 それらをすべて聞くのは高すぎる。 さらに、ラムダブロックはしばしば最後のパラメーターとして渡されます。 ArgumentCaptorを作成し、それを呼び出すために複雑なキャストを実行します-バスティング。 コルーチン自体は、Continuation型の最後のパラメーターを持つ関数です。 また、特別な処理が必要です。 彼らはそれをMockitoに追加しましたが、ほとんどのコルーチンを呼び出す便利さを追加しませんでした。 全体として、これらすべてのささいなことから、このラッパーは言語に調和して収まらないという感じがあります。







仕事の量を見積もった後、私は一人がうまく対処できるという結論に達し、彼のライブラリを書き始めました。 言語に近づけて、テスト中に発生した問題を解決しようとしました。







次に、何が起こったのかを説明します。 シードの最も簡単な例を次に示します。







val car = mockk<Car>() every { car.drive(Direction.NORTH) } returns Outcome.OK car.drive(Direction.NORTH) // returns OK verify { car.drive(Direction.NORTH) }
      
      





ここではマッチャーは使用されず、全体のDSL構文のみが表示されます。 最初に、every / returnsブロックはモックが戻ることを指定し、verifyブロックは呼び出しが行われたかどうかを確認します。







もちろん、MockKには、変数、多くのマッチャー、スパイなどの構造をキャプチャする機能があります。 以下に、より詳細な例を示します。 これはすべてMockitoにもあります。 したがって、違いを説明したいと思います。







道具 そのため、これらすべてが機能するためには、Javaエージェントを使用して最終的な属性をすべて削除する必要がありました。 Maven / Gradleからは機能しますが、IDEにはあまり適していません。 「-javaagent:<some path>」パラメーターを割り当てる必要があるたびに。 Javaエージェントを簡単に実行できる一般的なIDEのプラグインを作成するという考えさえありました。 しかし、結果として、JavaエージェントなしでJUnit4およびJUnit5を実行するためのサポートを作成する必要がありました。







JUnit4の場合、これは標準の@RunWithアノテーションを使用した起動です。これは私自身は好きではありませんが、行き先がありません。 なんとか生活を楽にするために、私はChainedRunWithを追加しました。 チェーン内の次のランナーを指定して、2つの異なるライブラリを使用できます。







JUnit5の場合、エージェントを使用してJARに依存関係を追加するだけで十分です。すべての魔法は自動的に発生します。 しかし、実装では、これはUnsafe、Javassist、およびReflectionを使用した本当のハックと言えます。 このため、Javaエージェントを介した起動は、まだ正式な起動方法と見なされています。







次の機能は、すべてのパラメーターをマッチャーとして設定するのではなく、一部のみを設定する機能です。 この機会を実現するために、私は自分の脳を傷つけなければなりませんでした。 そのような機能がある場合:







 fun response(html: String = "", contentType: String = "text/html", charset: Charset = Charset.forName("UTF-8"), status: HttpResponseStatus = HttpResponseStatus.OK)
      
      





そしてどこかに彼の電話があります:







 response(“Great”)
      
      





Mockitoでこれをテストするには、すべてのパラメーターを指定する必要があります。







 `when`(mock.response(eq(“Great”), eq("text/html"), eq(Charset.forName(“UTF-8”)), eq(HttpResponseStatus.OK)))).doNothing()
      
      





これは明らかに制限されています。 MockKでは、必要なマッチャーのみを指定でき、他のすべてのパラメーターはeq(...)に置き換えられます。マッチャーallAny()が指定されている場合はany()に置き換えられます。







 every { response(“Great”) } answers { nothing } every { response(eq(“Great”)) } answers { nothing } every { response(eq(“Great”), allAny()) } answers { nothing }
      
      





アイデア これは、このようなトリックによって実現されます。すべてのブロックが複数回呼び出され、マッチャーがランダムな値を返すたびに、データが照合され、必要なマッチャーが見つかります。 マッチャーが指定されていない場所では、引数はほとんど常に一定です。 「ほぼ常に」というのは、デフォルトのパラメータが時間などを返す関数になる場合があるためです。 これは、マッチャーを明示的に指定することで簡単に回避できます。







DSLのテストの詳細。 たとえば、次のコードを検討してください。







 fun jsonResponse(block: JsonScope.() -> Unit) { val str = StringBuilder() JsonScope(str).block() response(str.toString(), "application/json") } jsonResponse { seq { proxyOps.allConnections().forEach { hash { "listenPort"..it.listenPort "connectHost"..it.connectHost "connectPort"..it.connectPort } } } }
      
      





彼が現在何をしているのかは関係ありません。これは、JSONを収集するDSLの構成の構成であることが重要です。







それをテストするには? MockKには、このための特別なマッチャーcaptureLambdaがあります。 便利なのは、1つの式でlambdをキャプチャし、それに応じて呼び出すことができるという事実にあります。







 val strBuilder = StringBuilder() val jsonScope = JsonScope(strBuilder) every { scope.jsonResponse(captureLambda(Function1::class)) } answers { lambda(jsonScope) }
      
      





メインコードの正確性を確認するために、StringBuilderの内容を応答に含めるサンプルと比較できます。 唯一の便利な点は、最後のパラメーターとして渡されたブロックが言語のイディオムであるということです。モックフレームワークで特別な処理方法を用意すると便利です。







また、コルーチンサポートは実装が難しい機能ではなく、言語が箱から出してすぐに実行できる便利な方法です。 呼び出しを単に置き換えて、coEveryおよびcoVerifyで検証し、内部のコルーチンを呼び出すことができます。







 suspend fun jsonResponse(block: JsonScope.() -> Unit) { val str = StringBuilder() JsonScope(str).block() response(str.toString(), "application/json") } coVerify { scope.jsonResponse(any()) }
      
      





その結果、プロジェクトの目標は、PowerMockとMockitoが持つ数千の機能を構築するのではなく、Kotlinでのモッキングを可能な限り便利にすることです。 これにさらに努力します。







出口






私は一般の人々に厳密な判断をせず、彼らのプロジェクトで図書館を試し、新しい機能を提供し、現在の機能を思い起こさせてください。







プロジェクトのウェブサイト: http : //mockk.io








All Articles