Mock を極力使わないようにしていた私が Mockito を使わないとムズムズするカラダになった理由

2020.05.28 追記: やっぱり mock 使わない方が良さそうに思えてきましたので、この記事から考えが変わってます

はじめに

以前、私の著書では

時間が限られた中、本番に使用しない Mock オブジェクトを作成するよりも、依存する実クラスを使用してテストケースを増やした方が時間もかからず、効率的にテストが行えると思いませんか?

とか書いていたのですが、最近はそうでもなくなっているので書き散らかしてみようと思います。 (10年以上前に書いた奴ですからね...しょうがない)

前提条件

  • SpringBoot 使った Kotlin アプリのサーバサイド(Java でも同じことができます)
    • UseCase とか Repository とか layer に分けて DI します

どんなテストでムズるか?

例えば Controller がこんな流れだったとします。

http://www.plantuml.com/plantuml/png/SoWkIImgAStDuKfCBialKd04aLnWKa7NJi6vA3Mn93KaiJZRiI3JEJ-lf2W_9oUrY0kxE9gMqE9KvyJYL0KhXMIu65TUVacgGb5cUaQ9bG9CmPFTIr_Ex7tSkEvf-xBd4zeUDwv_shdfMSji1PpssEXYjQSejRWWFwyu5U81X2fC3pYavgK0VGq0

CreateTaskApiController の責務として、CreateTaskUseCase#createTask の呼び出し & 結果によるレスポンスのハンドリングがあると思います。 (SpringBoot だと Bean Validation を使った validation も責務になりますがここでは割愛します)

CreateTaskApiController では受け取ったリクエストパラメータを元に CreateTaskUseCase#createTask のパラメータを生成します。 パラメータ生成処理自体はメソッド化なり他のクラスに処理を移譲することで Unit テストが容易に書けるでしょう。

ただ、CreateTaskController が CreateTaskUseCase#createTask に対して「想定通りのパラメータを設定したか?」の観点でテストを行う時はどうでしょう? UseCase 以降でパラメータをそのまま永続化するのであれば永続化したデータを参照することで確認はできるかもしれません。 ですが、Controller の Unit テストなのに永続化したデータまで意識するのは責務を超えている感があります。

Mock を使う

そこで Controller の Unit テストに Mockito を使って UseCase を Mock 化します。

コードはこちらです

// setup
val taskUseCaseResult = TaskUseCaseResultFixtures.create()
whenever(createTaskUseCase.createTask(any())).thenReturn(taskUseCaseResult)

↑は、引数関係なく呼び出されたら TaskUseCaseResult インスタンスを返す Mock として定義しています。

で、Controller 呼び出しを行います。 (Controller なので MockMvc 経由で呼び出していますが、UseCase の Unit テストであればメソッド呼び出しで構いません)

事前に定義した Mock が想定した結果を返すのは当たり前なので Mock の呼び出しを verify します。 観点としては、Mock の引数が想定通りか? と言う点です。

// mock のパラメータに対する verify
argumentCaptor<CreateTaskUseCaseParameter>().apply {
    verify(createTaskUseCase).createTask(capture())
    val capturedParameter = firstValue
    val expectedParameter = parameter.toParameter()
    assertThat(capturedParameter).isEqualTo(expectedParameter)
}

↑ capture キャプチャすると、Mock 呼び出し時点のインスタンスが取れるので、そのインスタンスが想定通りか verify を行います*1

この観点が抜けていると、ただテストを通しているだけになるので価値が半減します。any() でお茶を濁さないようにしましょう*2。 mock 使ったら引数の verify も忘れずに。

また、インスタンスのプロパティ1つずつ verify するとプロパティ増えた時にテストの追従を忘れる可能性があるので、まるっと比較する方が後からテスト壊したことに気付きやすくなるのでオススメです*3

テストクラスのメンテナンスコストを意識すると...

Controller の Unit テストをするなら、Controller の責務を超えた verify は避けた方が良いと思います。

API 呼び出しから一連の流れは Integration テストで行うべきで、この時は永続化した内容、発行したイベント等を verify する必要があるでしょう。 Controller の Unit テストなのに実クラスを DI していると RDBMS の構造が変わってしまった時にテストクラスにも影響を及ぼします。

これはテストクラスの肥大化にもつながり、テストクラスのメンテナンスコストが余計にかかります。

Unit テスト / Integration テストそれぞれの観点のテストクラスを作る必要があると思っているので、

  • Unit テスト: Mock をフルに使い、テスト対象クラスのメソッドだけ意識する
    • Repository の Unit テストに関しては、実際の RDBMS (やコンテナ上の DynamoDB Local とか S3Mock)にアクセスする
  • Integration テスト: 必要な箇所だけ Mock にして API 呼び出しから、レスポンスや永続化したデータの verify を行う

とした方が、ケース毎にどこまでやるのか考えずに済むと思います。

まとめ

「呼び出し先のメソッドのパラメータを正しく渡せているか」を気に始めたら、Mock 好きじゃなかった私も Mock 使わないとムズムズするようになった、と言うお話でした。

これは Controller だけでなく、UseCase や Application Service、Domain Service でも同じことが言えます。 他言語の Mock フレームワークで capture 相当の機能ってあるんでしょうか?あれば他言語でもストレスなく開発できそうだなぁ。

とはいえ、Mockito の version up に追従しなきゃいけなくなった時は「できるだけ使わない」に戻りそうですけどね...

*1:このケースは素直に verify すれば良いのですが、capture 使うサンプルとして書きたかった

*2:自戒も込めて

*3:equals メソッドの実装に影響を受けるのでケースバイケースかもしれません

【読了】ドメイン駆動設計 モデリング/実装ガイド

https://booth.pm/ja/items/1835632

「エリック・エヴァンスのドメイン駆動設計」や「実践ドメイン駆動設計」でわかった気になったけど実際にどうすれば良いかいまいちイメージがつかなかった人に是非読んでもらいたいです。 QA 形式で実際に実践する時に躓きそうなポイントも書いてあるので身近に詳しい人がいなくても始められると思います。相談相手として良い本です。

CQRS の概念をわかりやすくまとめてくれている点でも読む価値ありです。

「CQRS = データソース分離」と思われることがありますが、これは誤解です。

「CQRS = イベントソーシング」も誤解です。

誤解してました。気づかせてもらいました。

本書を一通り読んだ後に「エリック・エヴァンスのドメイン駆動設計」や「実践ドメイン駆動設計」を読むと「そういうことか」となる点が多いんじゃないかと思います。 プロジェクトにドメインモデリングを適用してみたい、と思った時にメンバー間の認識合わせとして最初に読んでおく本として最適だと思います。

本当に DDD で難しいのはドメインエキスパートの見極めと会話だと思うのですが、開発する上でこのようにしておけば、変更があった時の影響範囲を少なくすることができると考えています。

業務ロジックを抱え込んで肥大化しているトランザクションスクリプトな XXXService をメンテするのに限界を感じる前に手を打ってみませんか?

JaSST'19 Niigata に参加してきた

JaSST'19 Niigata が長岡で開催されたので参加してきました。 お堅い空気のイベント久しぶりでした。

S1) 基調講演

レビューのキキメ ~あなたのレビューは何のため?

レビューは静的なテストの1つであるという所はなるほどなぁと思いました。最近レビューしてても意味あるのかいまいち確信無いままやりがちだったのでちょっと気を引き締めることができました。

レビューの目的

人間は間違えるんです。でも間違いが少ないものを作らなければならないという明らかに矛盾した作業を強いられています。

レビューの前は、関係者の頭の中ではイメージが似てるんだけどちょっとずつ違う状態だったのを レビューが終わった時にイメージの認識合わせができていることがレビューの目的のひとつです。

「レビュー対象や背景を理解」し、その上で「問題点や認識違いの検出」を行い、関係者による認識を共有することをレビューでやっていることとのことでした。なるほど。

「あの人しか知らない」状況を減らすことが組織としてもあるべき姿ですね。

レビューのキキメを得るために

  • 無駄なレビューをしない
    • 出来るだけリアルタイムフィードバックに近づける
      • 作ってからレビューして結果を反映のサイクルを短く
    • レビュー対象は小さく、フィードバックを得やすいように
  • ぼんやりレビューをしない
    • 人間特性を考慮してレビューのやり方を設計する
      • A さんはこの観点で見て、B さんは別の観点で見て、的な感じ -> みんなが同じ観点で見ても指摘が重複するので意味が薄れるし、「他の人が見てるから自分がちょっとくらい手を抜いても良いだろう」と思ってしまいがち
  • レビューをやりっぱなしにしない
    • 指摘したこと、それを反映したことを確認する

時間は大事なリソースです。何人かで寄ってたかってだらだらやってレビューした気になるよりも、集中して効果的なレビューができるようにしたいものです。

対象の質が悪いとレビューのパフォーマンスが落ちる

確かにその通りです。誤字脱字あるとそればっかりに目がいってしまって、本質的なレビューができません。レビュー開始基準を設けてそれを満たさないとレビューしないのは良いことかもしれません。

コードもそうで、意味不明な処理のまとまりとか可読性が悪いとレビューに時間がかかると言うかあんまり見たいとは思いませんからね。気をつけよう。

人間特性

レビューする側もされる側も人間なので、正論を投げ合うのと面倒だなと思うものです。不具合の指摘も「見つけたから直せ」だと正しくてもちょっと感情的になります。建設的にできれば良いですよね。

こんな感じで意識すると良さそうです。

  • レビュアーが行うのはレビューイに対する支援
    • 粗探しではなく、より良い成果物を作成する為に行う
    • 可能な限り相手が受け取りやすいようにコメントする(やらされる -> 自分から進んでやるように)
  • 断定的な物言いをしない
    • お互いバックグラウンドが違う
    • e.g. 「もしかすると XX かもしれないと私が感じたので確認してもらえます?」
  • 手段よりも事実や目的にフォーカスする

S2) 事例紹介

エンタープライズシステムでのレビューによる品質向上

いしばしさんの発表でした。重厚な SIer での取り組みをお話ししてくれました。

ソフトウェア信頼度成長曲線とか書いてましたけど、集計するのが大変でそれ専用の人がいないとツラくて、燃えてるプロジェクトに参画していると特に「こんな集計するよりもやることない?」とか思い出し胸が熱くなりました。

とは言え、プロジェクトの状況を客観的に計測する事は大事で、何を使うかはさておき、「うまくいってる」「ちょっとまずそう」「ヤバイ」がわかるようにしておくのは必要な気がしました。計測する為の情報を出したりこねくり回して数値化するのがツライんですけどね...

S3) 事例紹介

実践コードレビュー

ウォーターセルの松井さんの発表で、レビューも組み込んだ開発フローをお話ししてくれました。

わかりみしかない発表で、この辺は組織自体を変えないと上手くいかないし、成功するかは組織による感じがするので銀の弾丸ではないとは思いますが、上手くまわれば多大な効果を発揮するやり方だと思います。

中でも

  • 自分のことは棚に上げてレビューする
  • 忖度しない
    • わからないことは遠慮しないで聞く
  • 指摘は具体的に
    • ふわっとした指摘はお互い疲弊する

この辺りは頷くしかありませんでした。

ウォーターセルさんみたいに組織内で何でも言える空気って割と当たり前に考えてましたけど、そうでもない組織も多いんだろうな...。思ってるのは自分だけ、みたいにならないようにしよう。

フロントエンドの自動テストをどうやってるか聞きたかったけど、今回のお題(レビュー)とはちょっと離れてたので自重しました。いつか聞きたい。

まとめ

何となくやってた / 依頼してたレビューへの姿勢が少し正された気がします。 自分がどんなコメントをもらうか分析して不足している観点に気づいたり、他の人がどんなコメントしているか分析して自分が不足している観点に気づければ個人として成長できるかなーと思えた時間でした。計測してみよう。

QA 的な仕事をしてた時は、

  • 現場もレビューやテストに対して関心を持ち、開発サイクルに組み込む
    • みんな設計者であり開発者でもあり QA である
  • QA の言う事を絶対に聞く
    • 失敗の全ては QA が負う
    • スケジュールが遅れそうでも QA が No と言ったらリリースできない

のどちらかでないとうまく回らない気がしていましたが、現場がちょっとずつ QA への意識を持っている感じがして良い流れだと思っています。お互いの立場だけから意見言っても「現実わかってんの?」と言われる事はわかりきってますからね。

レビューをした人が失敗に対して責任を持つのではなく、失敗した時のフォローを組織で行う空気にしていかないとこの辺りの文化はうまく回らないと思いました。QA が強すぎるとレビューも萎縮しちゃって受け身になっちゃいますよね。目的はレビューを通すことではなくて、作ったものをリリースして対価を頂くことですから、目的を履き違えちゃいけない。

今回参加して良かったです。関係者の皆様お疲れ様でした。

Doma 2 入門

f:id:nemuzuka:20190714095600p:plain

前回 の SpringBoot2.1 + Kotlin のリポジトリDoma 2 対応しました。

いちいち Insert を書かなくて済んだり、場合によっては SQL をかけたり(SQL ファイルで定義したり、アノテーションSQL 定義したり)、項目を絞った Update が SQL 文書かずにできるのが良いですね。

FOR UPDATE を制御するのに SelectOptions を使わなければならない(?) とかちょっと癖がありますが、やりたいことは十分できます。

関連するSQL ファイルが無かったり、bind する変数がメソッドに定義されていないとエラー出すのがとてもありがたく、初めて使う人にも優しいフレームワークだと思います。

レイヤー間は interface で切っていたので、SpringJDBC 使っている箇所だけの修正で済みました。 Kotlin でも問題なく動作して、既存のテストも大した修正なしです。

IntelliJ の設定が必要だったのでそこだけ注意でしょうか。

動的に SQL 変えたいケースはロジックで組み立てるのではなく 2WaySQL が良いと思っている人にはオススメです。

JAWS-UG 新潟県 (新潟/長岡/上越 合同) + NDS第60回 に参加してきた #jawsug #jawsug_niigataken #nds60

参加 してきました。 なんと、今回は1日使ったイベントでした。

午前: ハンズオン

github.com

AWS Systems Manager 触れたので良かったです。 EC2 に入りたい時に踏み台サーバが不要になるので良い感じ。

午後: メインセッション

「EAT SLEEP DEPLOY REPEAT 〜 AWS Code n 兄弟について 〜 」@wokamoto さん

ここだけの話、クラフトビールの話で終わるかと思いましたw。 Circle CI を使っているんだけど、AWS でも提供してた奴がありましたね。 少なくともビルド職人分のリソースを別のことに使えるようにした方が トータルで幸せになれることが多い気がします。

docker image 作るまでは自動化できるけど、 デプロイすることと環境(お客さん、本番/ステージング/開発)毎の変数をどう管理すべきなのか悩ましいですよね。 (そしてそれが正しく漏れなく設定していることを検証することの難しさよ...)

「4年分アップデート EC2の最新管理ハウツー」 @uekikazuki さん

t.co

AWS = EC2 の認識の方も多いと感じているのですが、やっぱり EC2 のアップデートも多いんですよね。使われてるんだなぁと実感しました。 発表してる植木さんが一番楽しそうでした。

AWSコンテナサービス入門」@hayajo さん

speakerdeck.com

わかりやすかった。はやじょさんを SRE として呼びたい所はいっぱいあるだろうなぁ。 k8s は組織で本気でリソースを割いて取り組めないなら避けた方が良さそうなイメージを持ちました。 運用で心をすり減らす人が減って欲しい。はやじょさんマジイケメン。

AWSのサービスをできるだけ紹介する」 @sakapun さん

www.slideshare.net

お疲れ様でした。 AWS、サービスの数が多すぎますよね。全部理解するのは諦めよう。

AWSを使ったシステムのテスト方針」(nemuzuka)

speakerdeck.com

NDS 60 回のお祝いを言えたので良かったです。本当に NDS は大好きなコミュニティです。

エンタープライズシステムでの品質向上プロセス」(hiro_ishibashi さん)

SI の品質に関する知見は Web サービスとかでも必要になることを再認識しました。 契約を盾にした立ち振る舞いとかでなく、SI の知見を元に効率的に品質上げるための施策を考えたい気分です。 品質は結果でなく「作り上げるもの」。大事ですよね。 いしばしさん、JaSST で発表されるのかー。行こうかな(行こう)。

LT

「EC2でサイト構築したけど躓いたことのまとめ」(nomeshi さん)

私も AWS から少額利用料が毎月引き落とされているけど、カード会社からは連絡こないなー

「ダイアログを考える」(gonchan93 さん)

そういや、window.showModalDialog あったなぁ...。 無理して Edge 使わなくて良いんじゃないかと思う今日この頃。

AWS Summit」(yas123 さん)

AWS Summit Tokyo 2019 のレポート。すごい人だったみたいですねー。

kasacchiful さん

DeepRacer の話。3時間待って走らせたのかー。

お時間ある方はぜひ。

jawsug-niigata.connpass.com

connpass.com

感想

やっぱりAWS == EC2 みたいなイメージが強そうに思いました。 組織の問題点や成し遂げたいことに対して自分で作るにしてもAWSや他のサービス使うにしても費用対効果を考えないと運用で死ぬことになるというのは常に考えておきたいと思います。開発や構築してる時は間違いなく楽しいですけど、運用が辛いのはツライ。

Kotlin 入門

f:id:nemuzuka:20190501150617p:plain

色気づいてサーバサイド Kotlin に興味を持ち始めたので勉強してみました。

今回のコード

構成

  • SpringBoot: 2.1.4.RELEASE
  • Kotlin: 1.3.30
  • あと使用してるのはこの辺見てください

内容

JSON 返す API サーバとして作ってます。

Repositry

とりあえず SQL 実行できればいいと思ったので、使いづらいと思いながらも JdbcTemplate を使いました。

コンストラクタで JdbcTemplate をインジェクションする感じです。
ここのテストは H2 にアクセスして実際に SQL を発行します(application-unittest.properties を見てる)。テストすべきは SQL ですからね。
FlywayTest でテスト用の Fixture を入れてます。
JdbcTemplate でないとすると、選択肢としては Spring JPA なんでしょうか。ScalikeJDBC みたいなの無いかしら。

Service (依存 class を Injection するケース)

Repositry のように依存する奴は Mockito で Mock 化します。 昔は Mock のメンテコストが割と高かった気がするので使わないようにしてたのですが1、最近は簡単に使えますね。
Kotlin で Mockito 使うなら Mockito-Kotlin 使うべきですかね。 Mockito-Kotlin との出会いのきっかけ

Controller

依存する奴は Mockito で Mock 化するパターンで。
正常パターンは大丈夫っぽいですが、パラメータの validation とかは Server 立ち上げないとできないのが、アレだなと思いました(Kotlin 関係ない)。

Controller のパラメータとか

この辺が Kotlin でよかったと思えるところでしょうか。
data class にのアノテーションでちょっとハマりました(field: の辺り)。
JSON にしても@JsonProperty 効かなくて焦りました。 field: との出会いのきっかけ

body パラメータの validation がアノテーションで定義できるのは嬉しいですけど、Controller 呼び出し時のエラーハンドリングが面倒なのは皆さまいかがお過ごしでしょうか(Kotlin 関係ない)。

Integration

server 立ち上げて実際に API 呼び出すパターンです。
H2 にアクセスして SQL 発行します(application-integrationtest.properties)。

レスポンスの JSON の verify に JsonAssert 使ってるのですが、最近の人は何を使っているのでしょうか。org.hamcrest.Matchers 使うのがアレな気分です。

感想

  • Kotlin について
    • Java + lombok より書きやすい気がします
    • 名前付き引数 + data class の copy 最高
    • @Slf4j は欲しいなぁ...
    • 明示的に open にしないと継承できないとか固くかけるのが良い
    • null 安全の為に Java のライブラリ使うとき面倒だな、と思うこと多し。!! 使いすぎないようにしないと
  • 結構 GitHub に Spring + Kotlin のサンプル上がってますね。Kotlin 好きが多いなw
  • Kotlin 自体はとっつきやすいです。ただ、Spring と Scala が仲良しならなぁ...という思いが隠せません
  • ktlint 入れて CI 回したり、Docker image がビルドできなくてハマった時間の方が長かったです
  • エルビス演算子 ?:命名センスに惚れた。でも他の言語でも使ってた

読んだ本

Kotlinイン・アクション

Kotlinイン・アクション

Kotlin Webアプリケーション 新しいサーバサイドプログラミング

Kotlin Webアプリケーション 新しいサーバサイドプログラミング


  1. EasyMockとかね…

【読了】失敗から学ぶRDBの正しい歩き方

失敗から学ぶRDBの正しい歩き方 (Software Design plus)

失敗から学ぶRDBの正しい歩き方 (Software Design plus)

アプリ開発の経験年数=RDBに触ってきた年数でもあるので、知識の棚卸しに読んでみました。頷くポイントが多すぎて首が痛くなりました。

人の失敗から学んで同じ失敗を繰り返さない姿勢、大事です。夜はゆっくり寝たいじゃないですか。

データモデルが大事ではあるとは思うのですが、使う RDBMS の癖や得意/ 不得意なことを知っておくとそうでないとでハマることから避けやすくなる筈です。 時間も別のことに使えるわけです。

SQLもデータ件数がメモリに乗るレベルであればパフォーマンスもそこそこ出ます。メモリに乗らなくなってから慌ててチューニングすると、table構造の見直しが発生する訳です。

ボトルネックになるのは永続化層なことがほとんどなんですよね。で、データが溜まってから問題が顕著にあらわれるわけです。その時は開発時のエースはプロジェクトから抜けて...というあまり望ましくない状況だったりするわけです。いやー、困る。

そうすると既存のデータどうすんの?という問題になって、データ移行バッチを作る羽目になったり、既存データでデグレらないよね、とヒヤヒヤしたりする訳です。夜はゆっくり寝たいじゃないですか。

ある程度設計を任せられるポジションの人も、若さと気合と根性でカバーできる人もこの本は読んでおくと良いと思います。先人が犯した(と思ってる)失敗をあなたが繰り返し経験する必要はないわけですよ。もっと違うことに悩む時間を使えるハズです。

  • あえてアンチパターンを適用して目先の時間を取って維持するのに手間をかけるアプリにするか
  • ちょっと立ち止まり時間をとって考えて、維持しやすいアプリにするか

どっちが良いか考えてみませんか。作った後にメンテナンスする人が変わったとしても後ろ指刺されることが無くなるんじゃないかな、と思います。

「データベースの寿命はアプリケーションより長い」は確かにそうだと思います。アプリケーションはリファクタリングしやすいけどRDBMSのデータはリファクタしづらいですしね。

合わせて読みたい

SQLアンチパターン

SQLアンチパターン