日々常々

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

JavaBeansって言葉に煩わされない

JavaBeansって言葉を目にして、ふと検索してみたらあまりに酷かったので書いておこうかと。対象は「JavaBeansってなんだろ?」と思ってしまった初学者さん。でもそんな人って私のブログ読むんだろうか……

今後は「このエントリ参照」にするつもりで書いてみる。 文字列連結と+演算子について整理しておく みたいな感じ。

ShortAnswer

JavaBeansを学ぶ必要はありません。JavaBeansと説明されているものの多くは、JavaBeansの名前を借りた独自の物体です。

長い説明

「あまりに酷い」と「要らない」だけだと流石にアレなので、仕様を斜め読みしながら説明していきます。あ、EJBには触れません。まぜるなきけん。

仕様について

JavaBeans仕様としてげったーせったーがーとか、こんすとらくたがーだとか、しりあらいざぶるがーだとか。よく見聞きするのだけど、仕様って読んだんだろうか……いや、嘘は書いてないけど、大事なところを落として話してる気がしてならない。仕様の話をするなら仕様へのリンクくらいつけて欲しいものです。

なお仕様は JavaBeans(TM) Specification 1.01 Final Release から入手できます。114ページもある英語の文章なんて読めない?私もです。

What is a Bean?

JavaBeansってなんなのよって話。再利用可能が云々とかはよく見ると思います。

で、7ページのIntroductionにはこう書かれてて。

The goal of the JavaBeans APIs is to define a software component model for Java, so that third party ISVs can create and ship Java components that can be composed together into applications by end users.

コンポーネント、なんですよね……なんでデータコンテナにすり替えられるのか。自分の理解している何かを置き換えたかったからなんだろか。まあコンポーネントってなんぞよみたいなところもあるんでスルーするとして。

これはこれとして、9ページに「What is a Bean?」ってまんまなのがあります。これは画像で。

f:id:irof:20190725113645p:plain

こんだけあからさまに太字で場所とって書かれてる。

A Java Bean is a reusable software component that can be manipulated visually in a builder tool.

ビルダーツールでビジュアリーにマニュピレートできる、ってさ。ビルダーツールってあれですよ、ドラッグドロップして線とか繋いで、プロパティをプルダウンとかからいじったりして、とかするあれ。最近だとiOSアプリ触るときに使ったかな……あれはUIもセットだけど。まあ、ビルダーツール使って視覚的に操作するためのものなのです。

もう「よく言われるJavaBeans」がJavaBeansに関係ないことわかりましたかね。これに触れずにJavaBeansがどうとか意味ないと思うんですよ。

JavaBeansの使われ方

12ページにこんな図があります。

f:id:irof:20190725120302p:plain

Local activationの項目で出てきてる図なので使われ方として示すのは不適切かもしれないけど。JavaBeanがただのデータの入れ物じゃないってのはわかるかと思います。

ところでこの図のJavaBeans ApplicationをFaaS(AWS Lambdaとか)って書いたらなんかイマドキっぽくないですか?あ、CORBA……何もかも懐かしい(Java11から目線)

あと java.beans.BeanInfo インタフェース

Beanの情報としてBeanInfoインタフェースがございます。仕様だと60ページとかなんですが、インタフェースなんでJavadocでもみれます。

BeanInfoって名前なんだから、Beanの情報として求められるものです。MethodDescriptorsとかPropertyDescriptorsとかはまあいいんですが、輝くのは getIcon​(int iconKind) メソッドですね。Beanならアイコンあるよね?とか言ってくるわけです。だってビルダーツールで操作するんだから。

と言うことでJavaBeansの必要条件をみてみると

WikipediaBeanの必要条件とかに書かれているようなのはこう言うことで。

  • デフォルトコンストラクタが必要なのは、ビルダーツールでインスタンス生成するため。コンストラクタ複数あったりすると作るのに手間取るからね。 で、必要ならデフォルトコンストラクタでプロパティのデフォルト値設定しとけと。
  • getter/setterが必要なのはフィールドを公開するためじゃなく、ビルダーツールにプロパティを表示するためのgetterと変更したときに呼ぶsetter。プロパティの変更イベントも処理できるよ。
  • シリアライズが要るのはビルダーツールで作ったコンポーネントの保存と復元をするため。

全部ビルダーツールで操作するためでございます。

触れなかったけど

イベントモデルとか図眺めてるだけでも面白い。英語でもクラス名とか図とかあったら私でも読める。

JavaBeansって言葉がよく使われた理由の憶測

ビルダーツールから操作しやすい条件が、当然のように他のフレームワークやライブラリからも操作しやすかったからだと思います。なので独自に定義せずにJavaBeans仕様に乗っかったインスタンス操作ツールがぽこぽこ出てきました。小さいものの組み合わせではなく、包括しているものから拝借する感じ、1990-2000年代だなー感ありますね。

先に挙げた条件(デフォルトコンストラクタとsetter/getterがセットであること)は機械的に操作するにはもってこいだったのでしょう。で、setter/getterを介して操作しようとすると、メソッドをリフレクションで拾ってくるとかになるわけで(PropertyDescriptorとかは使われず……内部では使ってたりするのでセットでなかったらエラーになったりする。)、他のメソッドがあったらエラーになったりしたんです。

リフレクションでこの辺処理するツール作ったことある人ならわかると思うんですが、メソッドをとってきてsetter/getterを並べるより、フィールドをとってきてそのsetter/getterを命名規則で処理するほうが楽なんですよね。なのでフィールドとsetter/getterの名前を合わせる、みたいな流れも生まれたし、フィールドには全てsetter/getterを作る、とかもできたわけで。ツールの怠慢と言えなくもないですが、それでなぜか「フィールド名と一致するsetter/getterがないと落ちる」「setter/getter以外のメソッドがあると誤作動する」から「フィールドとsetter/getter以外は作らない」とかよくわからない方向にいって、現在のlombok使わないとめんどくさいようなゴミができあがったんじゃないでしょうか。あ、ゴミって言っちゃった……。

最後に

というわけで、1997年の話でした。22年前。JavaBeansは、現在のJavaを使用したアプリケーション開発の主な文脈では、取り立てて重要なところは担っていません。(内部では使ってたりしますが、「JavaBeansであること」を求めてはないはずです。おそらく単に便利だから使ってる程度。)

最初に「学ぶ必要がない」と書いたのは、「JavaBeansについて知る」をJavaの勉強の直下におく必要がないと言うことです。他のタイミングで java.beans パッケージを使用していたら、あーJavaBeansってのを使ってるんだなーから、そのクラスやライブラリがどんな使い方をしているかを調べるって感じでいいと思います。

「JavaBeansはJavaの基本だろ?」とか言ってよくわからない説明をしているものに惑わされないでください。

レイヤーとレイヤーじゃないのと

レイヤーってあるよね。

f:id:irof:20190720234110p:plain

下方向に依存するやつね。

中をみたら

f:id:irof:20190720234209p:plain

な感じだと思ってるんだけども。

あ、レイヤー間の依存がどうこうとか、直下以外への依存がどうこう、って話は他でしてください。

で、なぜかよく見るのがこんなのだけど

f:id:irof:20190720234304p:plain

これは違うと思うわけで。だってあんま嬉しくない。 あ、こういうのを「賽の目」とか言う。さいのめあーきてくちゃ。縦でも横でも区切れるから。

賽の目。整理できてる風に見えるからか、整えるのが好きな人は推奨したりするんだけど、本来近くにあるべきものがぶった切られるデメリットはどうみてるんだろーなとか思ったり。どんな時に都合のいい分け方なんだろなーって考えると、分業推進した場合の一つの答えではあるんだろーなと思い当たりはするけれど、まあその手の話はあんま好きじゃないからしない。

賽の目やっちゃうならまだ

f:id:irof:20190720234626p:plain

のほうがマシだと思うのだけども。なぜかあんま見ない。

まあ、だいたい名前が悪い。この例でも「A_1」「B_1」「C_1」なんて名前つけちゃってて、前のアルファベットのグループ、後ろの数字でグループって見える。「そうじゃない」って言っても、そう言う名前になりがちなのも、まあある程度仕方ないかなーって思うわけで。

だからどう、って話はないです。

どこでモックを使おうか

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

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

お題

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を踏むことでその道具が担っている範囲を確かめられ、その過程で知識も増えてきます。

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