日々常々

ふつうのプログラマがあたりまえにしたいこと。

どこでモックを使おうか

モッククラスを使うべきか否か から一歩踏み出してみましょう。

モックを使う選択をした。そこはゴールではありません。

お題

RestTemplate を使用して外部サービスを呼び出す場合を考えてください。

ここでの「外部サービス」はネットワーク越しにアクセスするあらゆるものです。 (12factorに準じてバックエンドサービスって書けばよかったかもしれない。)

f:id:irof:20190718004005p:plain

「なぞのぎじゅつ」はなぞでもなんでもないですが、そこは突っ込まないでください。あとで言い訳してます。

コードはこんな感じ。あくまで感じ。

@Component
class MyClass {
    RestTemplate restTemplate;

    MyClass(RestTemplateBuilder restTemplateBuilder) {
        restTemplate = restTemplateBuilder.build();
    }

    MyResponse method() {
        /* なんか処理あるかも */
        return restTemplate.getForObject("http://localhost/external", MyResponse.class);
    }
}

さて、どこにモックを使いましょう。

あ、外部サービスの場合は何気に前回のモックを使うべき理由のほとんどが検討対象になります。なのでノータイムでモックを使う選択をしてもだいたいよかったりします。これはこれで思考停止を招きかねないので嫌なんですが、適切な思考停止は速度があげられますし、たまに思い返すくらいでいいのかもしれない。

モック対象の検討

テスト対象以外の3つに対してモックが使えます。以降の図では、黄色がモック、緑がテストで動かされる範囲です。

RestTemplateをモック

f:id:irof:20190718010451p:plain

RestTemplateBuilderを使っているので、RestTemplateBuilderのモックを使ってRestTemplateのモックを作ってもらう感じになります。

RestTemplateBuilder restTemplateBuilderMock = mock(RestTemplateBuilder.class);
RestTemplate restTemplateMock = mock(RestTemplate.class);
when(restTemplateBuilderMock.build()).thenReturn(restTemplateMock);

MyClass sut = new MyClass(restTemplateBuilderMock);

……ここまでしてやる?と言う感じですが、メリットはSpringコンテキストを起動せずにテストできること。間違いなく高速です。

RestTemplate 以降に不安がない、もしくはこのテストの対象がMyClassであるという強い表明ですね。 テスト対象クラスにテストしたくなるロジックがどっかり乗っかってる時には有効かもしれません。先に挙げたコードの「なんか処理あるかも」が超入り組んでたり数百行あったりとか。

これを選択したくなったら真面目にクラス設計を見直すべきかと思いますが、対レガシーコードなら選択肢になり得ます。 道具箱に入ってると役に立つかもしれません。足掻くことがいい結果に繋がるとは限りませんが。

MockRestServiceServerを使う

f:id:irof:20190718011637p:plain

なぞのぎじゅつ(フレームワークやライブラリ)の中身に働きかけるテスト機能を使うパターンです。テスト機能は、公式で用意されているもの(MockRestServiceServerはこちら)かもしれませんし、他の誰かが組み上げた何かかもしれません。どちらにしてもそのテスト機能がどの程度のことをできるかは見極めて使いましょう。

@AutoConfigureMockRestServiceServer
@SpringBootTest
class MyClassTest {
    ... (省略)

AutoConfigureMockRestServiceServerによりSpring管理下のRestTempateBuilderが生成するRestTemplateClientHttpRequestFactoryMockRestServiceServerとやりとりするモックになります。なんのこっちゃ、という方はスルーしてもらってもとりあえずは大丈夫です。MockRestServiceServer の使い方はここでは説明しません。

なぞでもなんでもない「なぞのぎじゅつ」の中身の概要は TERASOLUNAのRESTクライアントを参照ください。もっと興味があるならコード読むといいと思います。本稿はこの中身を知っているかは焦点ではないので「なぞのぎじゅつ」としています。

MockRestServiceServerの場合、ClientHttpRequestFactory をモックにするので、通信ライブラリ(java.netApache HttpComponents、OkHttpなど)が行う部分以外は本物が動きます。 そのため HttpMessageConverter での変換(JSONとの変換とか)やResponseErrorHandlerなど、Springに対する設定で実現されることの多くがテスト可能ですが、通信ライブラリが担う部分や実際の通信はテストできません。SpringTestを使えるなら本命ですが、通信ライブラリ以降が外れていることは認識しておく必要があります。

ポイントはテストしたいこと、テストしていると思っていることが範囲から漏れていないかの見極めが必要になることです。

外部サービスをモック

f:id:irof:20190718012959p:plain

通信までは本物で動作します。

外部サービスのモックには、以下のようなHTTP通信を受け取れるサーバーを使用します。

テストの独立性を考えるとJavaから使えるもの、テスティングフレームワークで制御できるものが優位になりますが、Dockerで動作させるなどの選択肢もなくはありません。「MyClassのテスト」では過剰に見えますが、「とりあえずHTTPリクエストを処理できればいい」程度であれば環境として用意するのが適している場合もあります。

また、場合によっては自分で実装してしまっても良いかもしれません。テストだけで動作する Controller で誤魔化してしまうとか、nginxなどで適当なサーバーを作ってしまうとか。あくまで場合によっては。

「実際に通信する」と言うところから、ネットワークをテスト対象にするかなどもあります。ホスト名の解決やプロキシサーバーを含んだ通信をテストするならばこのパターンになります。

テストしたいことが意図せず実行される範囲から外れにくいですが、ここで挙げた3つの中でもっとも実行時間がかかる方法でもあります。ゆえにモックを使う理由が「実体を使用すると時間がかかる」であれば、解消されているかには注意を払う必要があります。

どこでモックを使おうか

今回はRestTemplateを例に挙げましたが、JDBCaws-sdkなどでも同じです。要するに、テストしたいところを適切に見極め、効果の高いところにモックを使うべきである、ということです。言葉で言うのは簡単……。

モックのやり方によってテストで動作する範囲が変わります。範囲を誤解していると、テストすべきところが外れてしまっていることもよくある話です。MockRestServiceServerのような道具が「なぞのぎじゅつ」のどの範囲を担えているかを見極めるのは難しいかもしれません。内部を理解するに越したことはないですが、全てを理解するのは私にはちょっと難しい。そんな時に使えるのがテストファースト、すなわちREDから始めています。RED->GREENを踏むことでその道具が担っている範囲を確かめられ、その過程で知識も増えてきます。

前回の モッククラスを使うべきか否か で使うと決めた理由があるはずなので、それに沿ったら自然と選べる場合もありますが、それだけでは定まらないことも多々あります。今モックを使おうとしているテストでテストしたいことはなんなのかや、他のテストとの兼ね合いなどを考えて、いい選択ができたらいいなといつも思ってます。(できているとは言ってない。)