2013年12月26日木曜日

【GAE】スタート編1 アカウント作成

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」について連載を始めたばかりです。

さて、GAEについて連載すると言いましても、その規模が超膨大でどこから初めて良いやら……、という状態です。
ひとまず、連載最初は「スタート編」と題しまして、とにかくプロジェクトを始めてみましょう。

スタート編でやること

  • Googleアカウントを作る。
  • とにかくプロジェクトを始める。(接続先URLの確保)
  • Eclipseセットアップ
  • Eclipse内でプロジェクト作成
  • リリース

以上です。
「これくらい、マウスでちょっとカチカチやるだけで終わるだろ」と思ってはいけません。
これだけでも詰まるのがGAEです。

悪戦苦闘した私の記録が、少しでも後の世の発展に繋がることを切に願います。

アカウント作成の前に

最初はアカウント作成です。
GAEを使うにはGoogleアカウントが必要です。

ここで早速、第一チェックポイントです。

Googleアカウントを使ってGoogleにアクセスして開発するわけですから、「社内/社外」という概念はありません。

普通の開発の場合、

  • 開発環境は社内からしか参照できない場所にあるサーバを使う。
  • 本番環境はインターネットに公開するサーバに展開する。

まず大抵はこのような体制で開発しています。

GAEにはこのような「社内/社外」という概念はありません。
本番環境も開発環境も同じくインターネットに繋がっており、家からでも普通にアクセス出来ます。

場所を問わないことがクラウド開発の非常に大きなメリットです。
お客様にもアカウントを用意して貰えばお客様も開発環境に入ることが可能ですので、開発の進捗状況をリアルタイムかつダイレクトに報告出来るのです。
仕様変更などを電話相談する時も便利そうですね。

しかし一方で、家からでも開発環境、本番環境、両方にログイン出来てしまう環境ですから、セキュリティ的には従来開発よりも低いです。
このセキュリティ水準を認められない開発の場合は諦めるしかありません。

ただし、アカウント毎に権限を付与して、「本番環境にログイン出来る人はこの人だけ」「開発環境にアクセス出来るのはこの人だけ」というような運用は出来ます。
また、「この人が何時何分にこういう操作を行った」という記録も残っています。

この辺りがGoogleのセキュリティ水準の方針なのですね。

その辺の管理権限につきましては、しばらく後、別の章でご紹介することになると思います。

アカウント作成

では、アカウント作成に行きましょう。
すでにアカウントを有している人は作成する必要はありませんが、それでも読んで損はない情報を以下に記載します。

皆様ご存じ、Googleのトップページの右上に「ログイン」というボタンがありますね?


その人のログイン状況に応じて少し画面は違いますが、とにかくその先から以下のような「アカウント作成画面」に進むことが出来ます。
ここに名前や、希望するアカウント名、パスワード等を入力していきます。


ここで第二チェックポイントです。

名前は個人名にして下さい。

上の画像には「遠藤 太志郎」と入っていますよね?
そう、名前は個人名でなければいけません。

「このアカウントは管理者間で使い回しする予定だから、名前は『ジェニシス アドミン』にしよう」

とかダメなんですよ。

『ジェニシス アドミン』のように個人名ではない名前を入力してアカウントを作ると、そのタイミングでは確かに入力出来ます。
しかし、数日内にGoogleより「ポリシー違反です。修正しなければアカウントを削除します」みたいな警告が届いてしまいます。
「個人名判定ロジック」が定期実行されているのです。

これはGoogle+の名前付けポリシーによるものです。

https://support.google.com/plus/answer/1228271?hl=ja

最近のGoogleは全機能をGoogle+を中心に連結する試みがなされており、Google+を使うつもりが無くてもGoogle+の影響下に置かれてしまいます。

「アカウントの使いまわしをするな。ちゃんと権限で管理しろ」というGoogleの意思表示なんですね……。

よって、共通の使い回しアカウントはなるべく使わないことをオススメします。
どうしても共通アカウントが必要な場合は、代表者の名前を入力すると良いでしょう。
(偽名を考える、という手もありますけどね)

ここまで

長くなってきたので今回はここまでです。
アカウント一個作るだけでもこれだけ色々あるのです。

次回はプロジェクト作成からスタートします。

2013年12月23日月曜日

【GAE】Google App Engine 始めに

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

最近……、というほど最近ではありませんが、
最近のIT業界ではクラウドへの関心が高まっていますね。

しかしIT業界にクラウドがどれくらい普及しているかと問われると、まだまだ発展途上だと思います。
発展途上であるということは、今がチャンス、狙い目です。

クラウドにも複数種類がありますが、これからしばらくの連載では、私がロックオンしたGoogle App Engine(以下、GAE)についてご紹介していきたいと思います。

クラウドとは

GAEの話は膨大ですので、最初である今回はクラウド周りの小話からスタートしたいと思います。

さて、この連載のテーマである「クラウド」ですが、「クラウドとは何か?」と問われると、私も困ってしまいます。
余り定義が明確ではないのです。
どんなクラウドでも必ず共通する特徴としましては「インターネットの先にデータが置いてある」ということですけれども、それを言っちゃあ今の世の中、何でもインターネットの向こうじゃないですか。
昔はインターネット常時回線の無い時代があったと伝え聞いていますが、今の若い世代にとっては「歴史の授業」みたいな感覚です。
今の時代は「インターネット=当たり前」ですので、そこをアピールしても特徴になりません。

また、一時期クラウドという言葉が流行りましてね、「単なるレンタルサーバなのに、クラウドサービスを騙る」という詐欺みたいなサービスも少なくなかった。。。

定義が曖昧なので「クラウド」と言っても人によって認識が分かれてしまい、私は軽率に「クラウド」という単語を使うことを避けています。
しかし、最近はようやく「クラウド基盤」として認められるだけの価値のあるサービスが安定してきました。
いずれ「クラウド」という言葉の乱用乱発にも終止符が打たれると思います。

今回、連載のテーマとしているGAEも、代表的なクラウドたるクラウド基盤として認知されているものです。

クラウド基盤

クラウド基盤を標榜するサービスは複数あります。

  • Google App Engine(Google)
  • Amazon EC2(Amazon)
  • Force.com(Force.com)
  • Windows Azule(Microsoft)
  • さくらのクラウド(SAKURA)

キリが無いのでこの辺にしておきますが、とにかくクラウドを名乗るサービスは沢山あります。
海外製の方が主力ですが、日本製もありますよ。

しかし、上記にもありますように、「詐欺みたいなクラウドサービス」もありますので、クラスド基盤選択は慎重にご検討を。
ここでは名指ししませんが、昔、担当者の作業ミスでバックアップも含めて多数のサーバのデータを丸ごと消し飛ばしたトンデモ企業があり、発展途上だった日本のクラウド業界に盛大に冷や水をぶっかけたのです。。。

まあ、そんなトンデモサービスは置いておいても、クラウド基盤には個性があります。
アプリケーションの要件に応じて向き不向きがありますので、導入の際はそれらに見識を持っていることが肝要です。

寄らば大樹の陰

そうは言うものの、複数のクラウドサービスに精通している人なんてそうそういません。
クラウド基盤というのは、その利用方法を習得して理解を深めるのに多大な勉強コストが必要になります。
一口に「クラウド基板」と言っても、それぞれ全然別物。
一人で全部習得するのはかなり厳しく、むしろ特定のクラウド基板を「得意分野」として習得する方が現実的だと思います。

その中で今回、私がGAEを選んでいる理由としましては、以下があります。

1.Googleであること

最大の理由はコレ、提供企業がGoogleであること、ネームバリューです。
クラウドサービスはデータの保管を他社に丸投げしてしまいますので、その会社が潰れたり、障害でデータが消し飛んだりしたら一貫の終わり。
何よりも信頼性が第一です。
技術、経営、歴史、実績、知名度……。そういった全方位的信頼性が何よりも重いのです。

「日本のクラウド基盤業者の方が日本語が使えて分かりやすいし……」とか、そんなのは二の次。
クラウド基盤本体が頑強であること、これが最優先となります。

2.簡単らしい

上記にも「クラウド基盤は習得に勉強コストが必要」と書きました。
クラウド基盤開発は普通の開発よりも、開発者に高い技術的ハードルを要求してきます。
GAEは、そのハードルが比較的低めであると言われています。
実際、この連載を始めるにあたってある程度調査を行っていますが、シンプルな作りになっていると思いました。
まあ、それでも十分難しいと思いますが、他よりはやりやすそうな印象です。

クラウド基盤は以下の相反するどちらかの性質があると言われていますが、

・特定分野の開発に絶大な開発効率を発揮するけど、応用が効かない。
・色々と応用が効くけど、開発効率はそんなに高くない。

GAEは下です。
シンプルである代わりに手間暇は必要ですが、その分だけ理解が早く、リスクも低いです。

「参入ハードルの低さ」

私はここが気に入っています。

ただし、相対的に簡単というだけで、やってみるとやっぱり結構難しいです。
経験の浅い人がいきなり始めるのはオススメ出来ません。
最初は普通の開発で基本的スキルを身につけ、応用力をつけてからGAEに参戦するのが良いと思います。

3.Javaが使える

これは完全に私個人の都合ですが、私はJavaが第一言語ですので、Javaを使わせて貰えると大変助かります。
他のクラウド基板でもJavaを使える所はありますが、Googleと言えばAndroidの提供も行っている会社ですよね。
AndroidもJavaで動いているのは言うまでも無く。

つまり、Googleは全てのクラウド基板業者の中で一番のJavaの守護神なのです。

他の基盤を選んでおいて数年後、
「ウチはJavaを支持しないからJavaのサポートをやめます。代わりに.NETを使ってね」
なんて事になったら困りますから。

Javaプログラマーとしては、やっぱりGoogleの存在は絶大なのです。

おわりに

第一回である今回はご挨拶ということで、雑談ベースになりました。

クラウド基板というのは、「サーバ」でもなければ「OS」でもなく、言語でも無ければDBでもネットワークでも無い。
サービスリリースに必要となる全ての技術をまとめた総合パッケージですので、必然的に習得しなければならない技術が膨大になります。

連載に当たっては相当な長期化が予想されますが、末永くお付き合い下さい。

2013年12月12日木曜日

Yahoo! テキスト解析API ルビ振りAPI

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在は世に散らばるWebAPIの活用方法を検証します。
今回も前回と同じく、Yahooのテキスト解析APIから、ルビ振りAPIという機能を検証します。

ルビとは

ルビとは、以下のように漢字にフリガナをつける機能のことです。

遠藤えんどう 太志郎たしろう

このような表示を可能としているのは、<ruby>というHTMLタグです。
ブラウザによって対応していたり、していなかったりしますが、
最近のHTML5対応型ブラウザであれば対応していることが多いです。

HTML5の情報については、すでにネットに大量に転がっていますので、ここでは割愛とさせて頂きます。

しかし、上記のような「ルビ=漢字の読み仮名」という図式は、まあ一応は標準的な使い方とされているわけですけれども、使い道は色々と自由になりますよね。
だって、ベースとなる文字の上に小さな文字をつけるというシンプルな機能であって、別に「カタカナ拘束」のような制約はありませんから。

例えば、

遠藤 太志郎技術開発事業部

遠藤 太志郎

遠藤 太志郎80%オフ


みたいに、その単語にちょっと付属情報を入れる、みたいな使い方が出来ます。
こういうのを駆使してより良いインターフェースを作っていきたいものです。

ルビ振りAPI

上記では<ruby>の使い道について記述しましたが、今回のメインであるYahooのルビ振りAPIは、「漢字かな交じり文に、ひらがなとローマ字をつける」という標準的な用途の為のAPIです。

公式サイトは以下です。

http://developer.yahoo.co.jp/webapi/jlp/furigana/v1/furigana.html

さて、ここを見ますと、以下のような説明がありました。


どうやら、「子供向けにフリガナを振る」というのが本来の趣旨のようです。
子供用の教育サイトとか、子供用新聞記事とか、そういったサイトであれば需要がありそうです。

とはいえ、私は業務システム開発がメインのSEですので、そんな子供用機能は需要が無いです。

しかし、そこを何か別の形で利用出来るのではないかな、と思って考えてみました。

例えば、以下のように名前とフリガナを入力する機能があるとします。

  名
セイ メイ

でも、両方入れるのは面倒なので、漢字だけ入れればカタカナは自動補完してくれると便利なのに、と常々思っています。

というわけで、私の名前でルビ振りAPIを実行してみました。

実行プログラムは依然と全く一緒。送信先URLを変えるだけで実行出来ました。
以下の過去記事をご確認ください。

  1. Yahoo! Web API 登録編
  2. Yahoo! テキスト解析API キーフレーズ抽出(送信機能作成編)

さて、実行した結果は以下になりました。

  • 遠藤⇒えんどう(endou)
  • 太志郎⇒ふとししろう(hutosisirou)

ん~、ダメか。。。
やる前からなんとなく想像がついていましたが、私の名前は珍しいので対応出来ません。
「遠藤太郎」でやってみたら、正常に機能しました。

言語解析である以上は仕方のないことかもしれませんが、


「精度が100%ではない。」


という問題がついて回ってきてしまいます。
このため、業務システムに導入するのはタイミングが難しそうです。

活用方法考案

このAPIは、「精度が100%ではない」という絶対的な問題があります。

だからと言って、「それじゃ使えねーよ」と断じてしまうのは早計でしょう。
別の考え方をすれば「大体は合っている」わけですので、「精度が大体OKであれば十分」という用途ならば使えるわけです。

例えば、私はルビ振りAPIの機能のうち、「ローマ字機能」の方に着目してみました。

システムを扱っていると、「ファイル名は半角英数字で保存しなければならない」という制約に必ずぶつかります。
例えば、ファイルアップローダーです。

「打ち合わせ資料20130112.zip」をアップロードすると、大抵の場合は「142453.zip」みたいに名前がシーケンス番号等に変換されてリンク先URLになっています。

  • http://tasy.com/download/142453.zip

みたいな感じですが、これじゃあURLだけでは中身が何か分かりません。
そこを、このローマ字変換機能を使います。すると、

  • http://tasy.com/download/142453/utiawasesiryou20130112.zip

という形で保存出来るわけですよ。
URLの一意性はディレクトリ構成で対処、ファイル名はローマ字変換です。
完璧とは言えませんが、シーケンス番号よりは良くなったでしょう?


「システム=精度100%とは限らない。気持ち便利になった程度でも価値がある」


こういうシチュエーションもあるということです。

終わりに

引き続き、世の中にあるWebAPIの検証をしていきます。

2013年12月6日金曜日

Yahoo! テキスト解析API キーフレーズ抽出(レスポンス検証編)

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

今回はいよいよ、「Yahoo! テキスト解析API キーフレーズ抽出」を実行してその結果を見てみたいと思います。

使い方

キーフレーズ抽出APIの公式サイトは以下です。

http://developer.yahoo.co.jp/webapi/jlp/keyphrase/v1/extract.html

ヤフーの良い所は、公式ドキュメントが日本語で整っていることですね。
GoogleやFaceBook、Twitterなどは英語しか無いのでどうやって使えば良いのか、仕様を読み取るだけでも一苦労です。

そのような解読が難しいAPIであれば、APIのパラメータ指定方法等についてもこのブログで解説する所ですが、
ヤフーの場合は不要でしょう。
公式サイトを見た方が分かりやすいと思います。

というわけで、前回作ったリクエスト送信機能を使って、早速レスポンス結果をチェックしてみます。

テスト送信

最初にテストとして流すのは、

「私は株式会社ジェニシス、技術開発事業部の遠藤 太志郎です。」

この一文を実行してみます。

すると以下のようなXMLのレスポンスが返ってきました。



このAPIでは「xml」「json」「PHP Serialize」の3パターンでレスポンスを取得出来ますが、特に指定が無ければXMLの形で帰ってきます。

ヤフー側で文章を単語毎に分割して、抽出した単語をKeyphraseタグ、その重要度をScoreタグで表現してくれています。

この結果を読みやすいようにリスト化した結果が、以下のようになりました。

順位キーフレーズ重要度
1太志郎100
2株式会社ジェニシス81
3技術開発事業部51
4遠藤45
510

なるほどなるほど。
ちゃんと『日本語の単語』毎に分割してくれていますね。
この辺りの正確さがヤフーの「日本語形態素解析」の技術力なのです。

このケースだと、私の名前「太志郎」が重要度TOPに輝くことになりました。

検証:単語のレア度

上記の結果では私の名前「太志郎」が重要度TOPになりました。
しかし、自分で言うのも何ですが、私の「太志郎」という名前は珍しい名前だと思います。
同じ名前の人に会ったこと無いですし、ネットで検索しても余り出てきません。

では、名前がありふれた名前だったら?
そこで、次に以下の文章を流してみます。

「私は株式会社ジェニシス、技術開発事業部の遠藤 太郎です。」

「太郎」という日本一普通な名前にして実行してみたところ、以下のようになりました。

順位キーフレーズ重要度
1株式会社ジェニシス100
2技術開発事業部62
3遠藤55
4太郎48
513

何と、太郎さんは一気に4位まで転落してしまいました。
この結果から考察すると、このWebAPIには以下の傾向があると推察されます。


単語毎に「レア度判定」があり、レアな単語ほどポイントが高い。



検証:長文解析

次に、短い一文を流すのではなく、長文の解析を行います。
このAPIは一度のリクエストサイズは「100KBまで」という制限があります。
この為、大量情報を一気に解析することは出来ませんが、短い小説くらいなら耐えられます。

そこで、サンプルとして、青空文庫より芥川龍之介の羅生門を拝借してきました。
比較的短めのストーリーで、知名度も高く、サンプルとしては向いていると思います。

その結果がこちら。
全部掲載するのは冗長である為、ベスト5だけ掲載します。

順位キーフレーズ重要度
1下人100
2老婆71
3面皰(にきび)64
4羅生門64
5雨やみ61

なるほど。
まさに「羅生門」な結果になりました。
羅生門における重要フレーズを見事に突いていると思います。
「何の小説を解析した結果でしょう?」というクイズに使えそうなくらいの正確さであると思います。

次にもう一つ、同じく青空文庫より白雪姫を解析してみます。

サイズオーバーしたので少し文章を削りましたが、その結果がこちら。

順位キーフレーズ重要度登場回数
1おまえさん10010
2白雪姫7950
3こくたん674
4まま母675
5小人6352

TOPは「おまえさん」ですか。。。
これはちょっとハズレな気がしますね。

それぞれの単語の登場回数もカウントしましたが、余り順位との相関関係が見られません。
「文章中にその単語が何回使われているか」も重要度を決定する上では考慮されている可能性もありますが、それだけではないのは確実です。
上記で考察した「レア度」など、複数の要素でポイントを算出しているようです。

そして注目しなければならないのは、この表に「リンゴ」がありませんね。
ベスト5のみならず、ベスト20にも入っていません。
白雪姫のストーリー上、「毒リンゴ」は最重要キーワードなはずですが、カスリもしないという結果になりました。

つまり、


単語を解析する機能であって、ストーリー上の重要性は考慮されない。


ということです。
やはりストーリー性までをアルゴリズム化することは不可能なようです。
「そりゃそうだろう」という結果ですが、利用者はちゃんと認識しておかなければならない要素です。

利用方法検討

以上の検証により、「Yahoo! テキスト解析API キーフレーズ」の傾向が分かってきました。

  • 特徴的な単語を抽出することが可能である。
  • 特徴的な単語を抽出する機能であって、重要な単語を抽出する機能ではない。
  • フリーの長文を解析することが可能である。

これらを総合すると、「フリーの長文を読み込ませて特徴的な単語を抽出し、その中で何が重要であるかは人間が見て判断する」という用途に役立てることが出来そうです。

現実の業務でどのようなタイミングで使用出来るかと考えると、アンケート解析に使えそうです。

「その他、ご自由にお書き下さい」とか、アンケートにはフリー項目の欄が設けられていることも多いです。
もちろん、それらは担当者の人が実際に読んで、大事な部分を読み取る作業が必要不可欠です。
その作業を完全に自動化することは不可能ですが、補助ツールとしては利用価値があるのではないでしょうか?

終わりに

最後にもう一つ、テキストを読み込ませてみたいと思います。

わが社の公式ホームページに掲載されている、「社長からのご挨拶」。

http://www.genesis-net.co.jp/company/message.html


これをキーフレーズ抽出します!!
結果は以下でした。

順位キーフレーズ重要度
1ジェニシス100
2最先端56
3フィードバック49
4新大陸45
5輝かしい財産43

この解析結果の評価につきましては、一社員である私はコメントを差し控え、読者である皆様各自にてご判断頂きたく思います。

2013年11月29日金曜日

Yahoo! テキスト解析API キーフレーズ抽出(送信機能作成編)

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在は巷にあるWebAPIの検証を行っています。

リクエスト送信機能

さて、WebAPIを実行するにはリクエスト送信機能が無くてはなりません。

HTTPリクエスト送信には一般に「Getメソッド」と「Postメソッド」があります。

本当はHTTPには「Deleteメソッド」とか「Putメソッド」とか色々と策定されているのですが、HTTPブラウザを初めとして送受信するソフトウェアの方が非対応だったりすることが多く、ほとんど使われていません。
「各メソッドはちゃんと決められたルールに則って使おう」という呼びかけもありますが、それが実現するのはまだ相当先となるでしょう。(永久に来ないかも)

というわけで、今回は「Getメソッド」と「Postメソッド」のみを考察します。

GetとPostの本来の用途は以下です。

  • Get:リソースの取得に使用する。(例えば、テーブルの主キーをGet送信して検索し結果を取得する、など)
  • Post:情報の送信に使用する。(例えば、入力フォームの全情報をPost送信してテーブルに保存する、など)

今回のテキスト解析APIは「テキスト解析結果を取得する」機能ですので、Getを使う方が標準と言えます。

しかし、Getメソッドの場合、Webサーバに最大長制限があります。
(Apacheの場合はデフォルトで「8190バイト」など。)

今回のテキスト解析APIでは、解析するテキストの長さがどれくらいになるか分かりませんので、
「Getで大量データを送信したらエラー」といったケースが想定されます。

その一方、Postにも上限はありますが、こちらはGetよりも大量データを送信出来ます。
(Tomcatの場合はデフォルトで「2メガバイト」など。)

この為、今回はPost送信を使います。

  • テーブルの主キー検索など、リクエスト長の上限が明確に決まっている場合はGetメソッド。
  • 今回のテキスト解析APIのテキスト送信みたいに、リクエスト長が不明 or 大量な場合はPostメソッド。

こういう使い分けにするのが合理的でしょう。

以下に私がJavaで作ったPost送信機能のサンプルソースを記載しておきます。
特に変わった機能は無く、普通に送信して、人間が目で見て分かりやすい形に出力するソースです。

HTTP通信は汎用的なプロトコルですので、JavaScript、PHP、Rubyなど、各言語でも作成できます。
各自の環境で都度、都合の良いように作れば良いでしょう。


public class RequestSender {

 /** logger */
 private Logger logger = Logger
   .getLogger(RequestSender.class);

 /**
  * リクエストをPOST送信する
  *
  * @param url
  * @param postBody
  * @return
  * @throws IOException
  */
 public String sendPost(String url, String postBody) throws IOException {

  //log4j使用
  //送信パラメータをトレースログに出力
  if(logger.isTraceEnabled()){
   logger.trace("送信先URL:" + url);
   logger.trace("リクエストボディ:" + postBody);
  }

  URL sendURL = new URL(url);
  URLConnection con = sendURL.openConnection();

  // POST送信モードを指定
  con.setDoOutput(true);

  // 送信実行
  OutputStreamWriter osw = null;
  BufferedWriter bw = null;
  try {
   osw = new OutputStreamWriter(con.getOutputStream());
   bw = new BufferedWriter(osw);

   // POSTの内容を書き出す
   bw.write(postBody);

  } finally {
   if (bw != null) {
    bw.close();
   }
   if (osw != null) {
    osw.close();
   }
  }

  // 受信
  InputStreamReader isr = null;
  BufferedReader br = null;

  StringBuilder bul = new StringBuilder();

  try {
   isr = new InputStreamReader(con.getInputStream());
   br = new BufferedReader(isr);

   String line;
   while ((line = br.readLine()) != null) {
    //肉眼で見やすいように改行コードを付与
    bul.append(line).append(System.getProperty("line.separator"));
   }

  } finally {
   if (br != null) {
    br.close();
   }
   if (isr != null) {
    isr.close();
   }
  }

  //apachecommons使用
  //最後の改行コード削除
  return StringUtils.chomp(bul.toString());

 }

}


リクエスト発行編へ


文面が長くなってきましたので、今回はここまで。
次回はいよいよ、上記ソースを使ってレスポンスの中身を検証してみます。

2013年11月18日月曜日

Yahoo! Web API 登録編

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

しばらくは世に溢れる「WebAPI」の機能について検証していきたいと思います。

最初はヤフーのWeb APIです。

Yahoo! Web API


YahooはWebAPIをいくつも提供しておりまして、その種類も豊富。
WebAPI界の大御所と言えるでしょう。

しかも良い所は、日本語の説明ドキュメントがある点ですね。
Google、FaceBook、Twitter辺りだと英語しか無いので、大変助かります。

公式サイトは以下です。


さて、早速テキトーにリクエストを投げて……ん?



どうやらリクエスト送信には「アプリケーションID」の取得が必要なようですね。

このようにWebAPIにはIDの取得を求められることが多いです。
WebAPIは使えば使うほど、WebAPI側のネットワークやサーバの負荷が掛かりますので、誰がどれくらい使っているかを管理する為にIDが必要ということですね。

従量課金制になっている所も多く、リクエストが多いIDほど、多くの金が取られるということです。



  • WebAPI業者はIDで使用者を管理している。



逆に言いますと、「IDさえ分かればなりすまし可能」という意味ですよ。

つまり、「JSファイルにIDをベタ書きして、Ajaxでリクエスト発行」とかやるとJSソースを解析されてIDが盗まれますので、
大事なIDはサーバ側で保管しておき、WebAPI発行はサーバ側から行うと良いでしょう。




  • WebAPIのリクエスト発行はクライアントサイドからAjaxでも行えるが、セキュリティを考えるならサーバから発行した方が良い




以上、簡単なことですが大事なことなのでお忘れ無く。


アプリケーションID取得


では、本題のアプリケーションID取得に入りましょう。

YahooJapanにログインしている状態で以下のページにアクセスします。





ここの「新しいアプリケーションを開発」のボタンをクリックします。


すると、以下のような画面が表示されます。
(メアドが表示されていますので、その部分は隠させて頂きました)



これを見ると、「サーバサイド」「クライアントサイド」と、利用目的に応じて違う設定が必要なようです。
クライアントサイドの方に「OAuth 2.0 Implicit」と書いてありますが、これは超巨大なセキュリティホールが出来るという噂の認証方式……。
今回は「サーバサイド」を選択して進んでみようと思います。


そこから先に続くのは「アプリケーション名」や「サイトURL」等ですが、今回は動作検証用ですので、
デフォルト値のままにしておきます。

最後に「同意」を選んで確認ボタンをクリックしますと、確認画面を表示した後に、IDの取得が完了します。

次の画面に進んだ時には、もうIDが発行されています。
(モザイクで見えなくさせて頂いておりますが)



簡単ですね!!

アプリケーションIDは「ハッシュ値」みたいな文字列です。

上記にもありますが、アプリケーションIDは大事な値ですので、推測も出来ないようになっていなければなりません。
更に一意性も必要ですので、「ログインID+シーケンス番号した文字列をハッシュ変換」といった方式で出力しているものと推測されます。

そしてもう一つ、「シークレット」というものが出ています。
これにつきましては、調べたのですが詳細が分かりません。
YahooにはユーザIDがバレてもログイン出来なくする「シークレットID」なる機能もありますが、こちらはそれとは別物の模様。
詳細が分かり次第、記事にしたいと思います。


さて、これでIDの取得は完了です。
もうWebAPIを発行出来る状態になっています。

試しに、画面に表示されているサンプルリクエストを送信してみました。



ちゃんとレスポンスが返ってきていますね。
これで動作確認も取れました。

後は発行するだけでOKです。
実に簡単でした。

次回


次回からは、いよいよ本格的にWebAPIの機能検証に入ります。

2013年11月5日火曜日

WebAPI 導入編

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

今回から「WebAPI」の連載をスタートしたいと思います。

WebAPIとは

連載のテーマであるWebAPIとは、特に厳密な定義があるわけではないのですが、要するにインターネット経由で余所からデータを取ってくる機能のことです。

例を挙げるとすると、ヤフーのニュース記事の下にあるTwitter部分。



これです。
これはヤフーのその記事と関連のあるツイートを表示しているわけですが、これをどうやって取得しているのかと言うと、Twitterが提供しているWebAPIを利用しているのです。


カラクリとしましては、HTTP通信でリクエストを投げると、レスポンスで「XML」か「JSON」かの形式で返ってきますので、それをパースして……。
と難しい話は以降の連載にしましょう。

ようするに、「リクエストを送信すると文字列が戻ってくるので、それを画面に出す」というだけのシンプルな機能です。

もちろん、こういった機能は自前で作ることも、別に難しいことではありません。
自前でWebAPIを作って公開し、使用料を取るというビジネスも普及しています。

WebAPIのニーズ

このWebAPIというサービス形態は昨今……というにはちょっと遅い気もしますが、最近注目のサービスです。
理由としましては、やはりスマートフォンやタブレットといった次世代携帯端末の存在でしょう。

これらの機器は内部にアプリを入れて機能するわけですが、やはり携帯端末ですのでリソースが少なく、そんなに重い機能を実装出来ないのです。
そこで、重い処理はWebAPIを動かしている巨大サーバで行って、必要な情報だけを取得して携帯端末に表示する。
そんなエンドユーザ端末の機器に優しいことがWebAPIの長所です。

WebAPIの現状

とは言え、私はWebAPIを使ったシステム開発は一度しかありません。
注目度の割にはあんまり出番が来ないサービスというイメージがあります。

その理由として、二つの理由を考えてみました。

1.出番が無い


まずはこれでしょう。
「Twitterの情報を取得したところで遊びにしか使えないじゃないか」みたいに、不特定多数のエンドユーザを想定したサービスが多いです。
元からオシャレなスマホアプリ作成を生業としている人ならともかく、私のような企業向け業務システム開発が中心のエンジニアには滅多に使用機会が訪れないのです。

2.お金&余所様のデータであること


もう一つの理由は、これ。
WebAPIはどこまで行っても他社様のデータを貰ってくるサービスですので、契約とか権利とかの理由で、業務用には敬遠されてちまいがち。軽く使いたいだけでも「法務部」とかそんなレベルの話になってしまい、内部調整が非常に大変です。

何より困るのが「仕様変更」ですね。
少し前にTwitterが急に仕様変更したせいで取れていたデータが取れなくなり潰れたサービスがありました。
WebAPI提供業者の都合に振り回されてしまうのも欠点です。

WebAPIの利用価値

このように難点も多いWebAPIですが、一方で凄く便利で高機能なサービスがあるのも確かです。

業務用システムエンジニアである私の立場では、「天気予報取得」「居酒屋の口コミの取得」など永遠に出番が無さそうなサービスも大量にありますが、中には「漢字に読み仮名を降る」「PDF変換」など、もしかしたら使い道もありそうな雰囲気のサービスもちらほら見かけます。
興味が湧いてきましたね。

というわけで、今回の連載では「業務用システムエンジニアから見たWebAPI」というテーマで、調査したWebAPIのご紹介をしていこうと思います。

次回予告

最初はヤフー提供のWebAPI『テキスト解析API』から調査開始です。

2013年10月30日水曜日

最強モックツール JMockit その11 Eclipseプラグイン

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

長く続いた「JMockit」の記事も本日が最終回です。
JMockitは非常に多機能なライブラリなので他にもまだまだ機能はあるのですが、大抵の開発ならば前回までの内容で対応出来ると思います。
何回か使ってみて、『もっとこういう機能が無いかな?』という状況になった時、改めて探してみるのが良いかと思います。

さて、最後はチラッと小ネタをご紹介します。

Eclipseプラグイン


JMockitには、Eclipse上での入力を支援してくれるプラグインがあるようです。


導入すると、「ctr + スペース」で以下のように入力候補が出てくるようになります。


他にも、「書き間違えている場合に赤くエラーを出す機能」「テスト対象になっているメインソースのジャンプする機能」などがついています。

別に導入必須という程の大した機能ではありませんが、こうした「ちょっと便利な機能」も忘れず押さえておくことで、日頃の開発効率を高めていくようにしたいものですね。

これからのJMockit~JMockit2~


何と、Googleの開発サイトでは「JMockit2」の開発が始まっているらしいです。


まだ開発途中ですが、『JMockit1とは違う視点から、もっとシンプルで優れたモックライブラリを作る!!』との触れ込みです。

現時点で十分に優れたライブラリかと思いますが、確かに調べていて少し難しいと感じることもあり、「シンプルさ」にはまだ改善の余地があるのかもしれません。

完成がいつになるかは分かりませんが、Java単体開発者としては耳寄りな状況として要チェックです。

首を長くして完成を待ちたいと思います。

終わりに


これにてJMockitの連載は終了です。

連載を終えるに辺り、JMockitに関する所感を述べるならば、上にもありますが「結構難しい!!」ですね。
そもそも、モックという発想自体が黒魔術ですので、何年もJava開発を行って基本を押さえている技術者ならば兎も角、経験の浅い人が触ったら混乱することになると思います。

また、『チームの全員がJMockitに精通している』という超精鋭チームを用意することも至難でしょう。

というわけで、「せっかく覚えたJMockitだ。ぜひ使いたい!!」という流行る気持ちは押さえて、

  • モックを使わなくてもテスト出来るクラス設計にする。
  • どうしても必要な例外部分だけ、JMockitを使用する。

といった程度の水準で導入するのが一番現実的ではないでしょうか。

例えば「正常系は普通にテストする。異常系はJMockitを使う」など、開発指針のレベルで適切な運用方法を考える所からがJMockitの始まりかと思います。


以上でJMockitの連載を終わります。
ご愛読ありがとうございました。

2013年10月21日月曜日

最強モックツール JMockit その10 コンストラクタ差し替え

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はモックツール「JMockit」の使い方をご紹介しています。

今回はコンストラクタの差し替えです。

コンストラクタの差し替え

前回は「メソッドを丸ごと差し替え」を行いましたが、
今回はその延長線で「コンストラクタ」の差し替えです。

コンストラクタは普通のメソッドとは違うということか、他のモックツールを見てもコンストラクタには非対応ということが多いようです。
しかしコンストラクタのモック化を必要とするシチュエーションも少なからずあるもの。
例えば、バッチの開発。偶に見るのですが、コンストラクタで環境設定ファイルを読み込む作りになっていることがあるのです。
しかも、前もって用意されている正規の環境設定ファイルは本番実行用で、テストの時はテスト用ファイルを読み込まなければならない、という場合も結構あります。

しすれmそういう現場では必ず以下のような会話で盛り上がります。


「あれ? テストが動かないぞ?? どうなってんの???」⇒「設定ファイルの差し替えを忘れてるんですよ」


これを何回でも繰り返すのが人間というものです。どの現場に行っても100%コレでした。
というわけで、ファイルの差し替えとか設定値の切り替えとか、そういう作業はJMockitを使ってテストソースの中に埋め込んでおく方が楽です。
ぜひ使ってみてください。

では、JMockitにおけるコンストラクタ差し替えの記述方法をご紹介しましょう。
まず、サンプルとして以下のようなコンストラクタを含んだクラスを作成します。

public class ConstructorSample {

 public ConstructorSample() {
  System.out.println("★コンストラクタ処理★");
 }

}

サンプルですのでprintlnのみの処理ですが、本番ではここで色々処理しているという想定です。
これに対し、テストソースの方は以下のように書きます。

public class ConstructorSampleTest {

 @Test
 public void test() {
  new MockUp<ConstructorSample>() {

        @Mock void $init() {
         System.out.println("★コンストラクタ差し替え★");
        }
  };

  new ConstructorSample();
 }

}

コンストラクタの場合はメソッド名の指定が普通には指定できませんので、『$init』という特殊記号を使うわけです。

書き方の要領は前回と全く同じですので、『$init』を使うのだということを知っていれば簡単に使うことが出来るでしょう。


終わりに


JMockitは高機能なライブラリなので連載が長引きましたが、
今日までの記事で殆どのケースに対応可能だと思います。

次回は最終回、JMockit開発を支援するEclipseプラグインをご紹介します。

2013年10月15日火曜日

最強モックツール JMockit その9 メソッド丸替え

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はモックツール「JMockit」の使い方をご紹介しています。

今回はメソッドの中身の丸ごと差し替えです。

メソッド全体の差し替え

今までご紹介してきたモック機能というのは、『元々のメソッドを実行しないで、仮の値を返す』というものです。

例えば、ログインチェック機能の場合、
本来の機能であれば『ログインIDとパスワードをチェックして、正しい場合はtrue、間違っている場合はfalseを返す』だったとします。
しかし、モック化することによって『いつも固定でtrueにする』とか、そういう感じです。

この場合、元々のメソッドは実行していませんので、DBにアクセスに行っていません。
ただ単に『true』という値が帰ってくるだけです。

このように、『返り値だけあればいい』という状況であれば今までのやり方でOKです。
しかし、そうではなくて、『別の処理をちゃんと実行して欲しい』という場合は、これではダメです。

例えば、以下のような状況です。

  • DBに間違った値が入った時のテストだから、本来の機能はモック化して潰し、代わりに間違った値をインサートさせたい。
  • メール送信機能のテストなんだけど、メール送信処理自体は実際にはメールを送信しなくていいからモック化する。でも、同時に行っている一時ファイル削除処理は行っておく必要がある。

このように、『ダミーの値を返すだけではなくて、違う処理をやりたい』という場合です。
こういう場合は、元々のメソッドをぶっ潰して、違う機能を書いてしまいましょう。

『メール送信後、ファイルを削除する』機能をモック化するというシチュエーションを想定します。
(サンプルなので、printでその処理を行ったという想定とします)

public class MailManager {
 public void sendMail(){
  System.out.println("★メールを送信する。★");
  System.out.println("送信した添付ファイルを削除する。");
 }
}

JUnitは単体テストですが、メール送信はプログラムだけでなくメールサーバも必要とします。
こういう風に違うサーバを必要とする処理のテストは結合テストで行って、単体テストフェーズではモックで済ます、というのは常套手段と言えるでしょう。

しかし、同時に行っている添付ファイル削除処理は実施しなければ都合が悪いということもあるでしょう。
もしくは、『メール送信は成功したけど、ファイル削除は失敗した』という状況を作りたかったりするなど、細かい要望は色々あります。

そういう場合、モックでメソッドを新規作成してしまえば良いのです。

以下がそのやり方です。

@Test
 public void testCreateFile() {
  new MockUp<MailManager>() {
   @Mock
   void sendMail() {
    System.out.println("ファイル削除(差し替え)");
   }
  };
  //メール送信
  MailManager manager = new MailManager();
  manager.sendMail();
 }

JMockitの場合、今回新規登場の『MockUp』というクラスを使うことで実現できます。

使い方も分かりやすいかと。
MockUpクラスの中で同じ名前のメソッドを書いて、@Mockをつければ、それだけで後は自動でモック化されます。

直感的に分かるようによく出来ているライブラリだと感心してしまいます。

終わりに


今回ご紹介したサンプルは、実際のところは回避可能でもあるんですよね。
だって、『メール送信』と『ファイル削除』を別々のメソッドにして、メール送信メソッドだけモック化すれば良いのですから。
しかし、実際の開発だとクラス設計がJUnitのことまで考えられていなくて、こういう作りになっていることもしばしば。
不本意ながらに結構お世話になってしまう機能かもしれません。

もしくは、『ファイル削除失敗』みたいに現実には実現出来ないシチュエーションを作るにも便利です。

代替機能を自分で作る手間はありますが、こんな風に自由に作り込める機能は大変助かりますね。

引き続きJMockitのご紹介をしていきます。

2013年9月30日月曜日

最強モックツール JMockit その8 例外実行

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はモックツール「JMockit」の使い方をご紹介しています。

今回は例外、『Exception』の出し方についてです。

例外呼び出し


数あるテストの中で悩みがちなのが、「そのテストを実行出来ない」というパターンです。

  • ネットワークエラーのテストをしたい。⇒そのタイミングでネットワークエラーを発生出来ない。
  • 不正なファイルが来た場合に例外を発行するテストをしない。⇒それより前の処理でチェックが済んでいるので、その場所ではエラーを発行出来ない。

こういうのですね。
絶妙なタイミングでLANケーブル抜いたりとか、そうそう上手には出来ません。
そういう時こそ、モックツールの活躍のタイミングです。

JMockitを使って無理矢理例外を発生させてしまいましょう。

そのやり方が以下です。

new NonStrictExpectations() {{
      loginService.isLogin(anyString,anyString); result = new Exception();
  }};
}

ソース全体を見せるよりも、該当部分だけ記載した方が分かり易いでしょう。
resultにExceptionを設定するだけでOKです。

今回はサンプルとして「Exception」の型を使いましたが、IOExceptionとか、自前で作ったExceptionとか、例外クラスならどんなものにも対応出来ます。

特記事項としては、本体ソースがvoid型でも「result = new Exception();」の形は使えるということです。
void型なのに返り値を設定するのは変な気もしますが、そういうものだと理解して下さい。

終わりに


引き続きJMockitのご紹介をしていきます。

2013年9月25日水曜日

最強モックツール JMockit その7 複数回呼び出し

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はモックツール「JMockit」の使い方をご紹介しています。

さて、今まで使ってきたJMokitのソースは、ずっと「NonStrictExpectations」を使ってきました。

第三回の時に「NonStrictExpectations」でほとんど事足りると記載しましたが、ここから先はそれ以外のケースについてご説明します。

複数回呼び出し


英単語の説明から入りますが、NonStrictExpectationsの「Strict」とは日本語で『厳密な』という意味となります。Expectationは『期待』です。
つまり、NonStrictExpectationsとは『期待値の定義を厳密にしない』という意味になります。

当然ながら、「じゃあ、厳密な場合は何を厳密にするの?」というのが当然の疑問でしょう。
チェックが厳密化するのは複数あるのですが、例として「実行順序」と「実行回数」があります。
今回は「実行順序」と「実行回数」をチェックしてテストを実行する「Expectationsクラス」のご紹介です。

「実行順序」と「実行回数」をチェックしなければならないテストケースとしては、以下を想定します。

  • Webシステムのログイン機能で、ログインに失敗した後の処理をテストする。
  • ログイン失敗が1回目、2回目の時はログイン画面に戻る。3回目以降はアカウントロック画面に遷移する。

まずは本体ソースから。

public class LoginErrorAction extends HttpServlet {
 public String doAction(HttpServletRequest req, HttpServletResponse res){

  //リクエストからエラーになったIDを取得
  String id = req.getParameter("login_id");

  LoginService service = new LoginService();

  if(service.loginErrorCount(id) <= 2){
   return "login/login.jsp";
  }

  //ログイン失敗
  return "login/loginLock.jsp";
 }
}
これについては説明はいらないでしょう。 ログイン失敗回数がDBに保存されているので、それを取得して2回以下だったらログイン画面に戻る。 3回以上だったらアカウントロック画面に遷移します。 これに対するテストケースは以下になります。
public class LoginErrorActionTest {

 /** HttpServletRequestのモック  */
 @Mocked
 private HttpServletRequest mockRequest;

 /**  HttpServletResponseのモック */
 @Mocked
 private HttpServletResponse mockResponse;

 /**  LoginServiceのモック */
 @Mocked
 private LoginService loginService;

 @Test
 public void test_3回ログインに失敗するとロック画面に遷移する() {

  LoginErrorAction action = new LoginErrorAction();
  loginService = new LoginService();

  new Expectations() {{
   loginService.loginErrorCount(anyString);
   result = 1;  //ログイン失敗1回目
   result = 2;  //ログイン失敗2回目
   result = 3;  //ログイン失敗3回目
                }};

        String jsp1 = action.doAction(mockRequest, mockResponse);
        assertThat(jsp1, is("login/login.jsp"));

        String jsp2 = action.doAction(mockRequest, mockResponse);
        assertThat(jsp2, is("login/login.jsp"));

        String jsp3 = action.doAction(mockRequest, mockResponse);
        assertThat(jsp3, is("login/loginLock.jsp"));

 }

}
ほぼほぼNonStrictExpectationsの頃と変わらないのですが、Expectationsにしたことで微妙な差異が生まれています。 まずはこれです。
loginService = new LoginService();
自分でモック化するクラスをnewしています。 NonStrictExpectationsならば勝手にインスタンスを作ってくれるのでこの行は不要でした。 Expectationsは厳密な処理を行うクラスですので、そういう自動機能は使わず、自分で明示的に指定しろということなのでしょう。 次にこちら。
new Expectations() {{
   loginService.loginErrorCount(anyString);
   result = 1;  //ログイン失敗1回目
   result = 2;  //ログイン失敗2回目
   result = 3;  //ログイン失敗3回目
   }};

呼び出し順番を指定しています。
1回目は「1」、2回目は「2」、3回目は「3」を指定しています。

JMockitの複数回指定はこのように書くわけです。
この書き方自体はNonStrictExpectationsも同じでして、NonStrictExpectationsでも同じように、1回目、2回目、3回目の指定は出来ます。

ExpectationsとNonStrictExpectationsの違いは以下です。

  • Expectations:呼び出しは3回までしか出来ない。4回呼び出すとエラーになる。
  • NonStrictExpectations:呼び出しは何回でもOK。4回目以降は3回目と同じ値になる。

なるほど、やはり厳密になっていますね。
つまり、「NonStrictExpectationsを使っていたら動いていたけど、実は見落としがあってテストケースが機能していなかった」という事態を避けることが出来るわけです。
テストの品質としてはExpectationsの方が上と言えるでしょう。

しかし、全部Expectationsを使っていては工数を消費してしまいますので、「NonStrictExpectationsで簡単に済ませて良いパターン」と「Expectationsでしっかりチェックしなければならない複雑なパターン」を切り分けてコーディングしていくことが肝心です。

終わりに


引き続きJMockitのご紹介をしていきます。

2013年9月19日木曜日

最強モックツール JMockit その6 staticクラス

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はモックツール「JMockit」の使い方をご紹介しています。

今回は「staticクラス」のモック化をご紹介します。

staticクラスのモック化


staticクラスというのは、「StringUtils」とか、「FileUtils」とか、そういったタイプのクラスのことです。
staticクラスのモック化は難しいらしく、他のモックライブラリでこの機能を提供しているクラスは少ないようです。
しかし、最強モックツールであるJMockitならば簡単に出来てしまいます。

まず、本体クラスとして「StaticUtils」というサンプル用クラスを作りました。
「genesis」という文字列を返すだけの簡単なクラスです。

public class StaticUtils {

 public static String returnGenesis(){
  return "genesis";
 }

}

これをモック化すると以下のようになります。

public class StaticUtilsTest {

 /** StaticUtilsのモック  */
 @SuppressWarnings("unused")
 @Mocked
 private StaticUtils mockStaticUtils;

 @Test
 public void testReturnGenesis() {
  new NonStrictExpectations() {{
   StaticUtils.returnGenesis(); result = "ジェニシス";
  }};
        assertThat(StaticUtils.returnGenesis(), is("ジェニシス"));
 }

}


今までに紹介した書き方と殆ど同じです。強いて言えば、以下の部分でしょうか。

new NonStrictExpectations() {{
 StaticUtils.returnGenesis(); result = "ジェニシス";
}};

今までだとモック化した「mockStaticUtils」をモック対象にしていましたが、今回はstaticクラスにつき、普通に「StaticUtils.returnGenesis()」と書いています。
それ以外については、特に意識する部分はありませんね。

過去の記事も同様でしたが、ケースバイケースで書き分けねばならない難しさは余り無く、同じ要領で書き進めることが可能なのです。

終わりに


引き続き、JMockitの解析を進めます。

2013年9月17日火曜日

最強モックツール JMockit その5 privateメソッド

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はモックツール「JMockit」の使い方をご紹介しています。

今回は「privateメソッド」のモック化をご紹介します。

privateメソッドのモック化

前回の記事では「内部でnewしているクラスのモック化」をご紹介しました。
内部newクラスのモック化につきましては、特に何も意識せずとも出来てしまったわけですが、
しかし、今回の「「privateメソッドのモック化」につきましては、新情報が登場します。

まずは、サンプルとして何度も出しているWebシステムのログイン機能の本体ソースを以下に記載します。

public class LoginExcuteAction extends HttpServlet {
 public String doAction(HttpServletRequest req, HttpServletResponse res){

  //リクエストからログインIDとパスワードを取得
  String id = req.getParameter("login_id");
  String pw = req.getParameter("login_pw");

  //ログイン判定
  if(isLogin(id,pw)){
   //ログイン成功
   return "menu/memu.jsp";
  }

  //ログイン失敗
  return "login/loginError.jsp";
 }

 /**
  * ログイン
  *
  * @param id
  * @param pw
  * @return
  */
 private boolean isLogin(String id,String pw){

  //DB認証
  LoginService service = new LoginService();
  boolean success = service.isLogin(id,pw);

  return success;

 }
}

前回ではログイン機能が本体メソッドの中に入っていましたが、今回はprivateメソッド「isLogin」を作成して分割しました。
これをモック化します。

つまり、今までは「HttpServletRequest,HttpServletResponse,LoginService」といった「本体が使用している他クラス」のモック化でした。
しかし今回はprivateメソッドをモック化する都合上、「LoginExcuteAction本体」をモック化しなければなりません。

このため、テストソースの方は以下になります。
public class LoginExcuteActionTest {

 /** HttpServletRequestのモック  */
 @Mocked
 private HttpServletRequest mockRequest;

 /**  HttpServletResponseのモック */
 @Mocked
 private HttpServletResponse mockResponse;

 /**  LoginExcuteActionのモック */
 @Mocked("isLogin")
 private LoginExcuteAction mockLoginExcuteAction;

 @Test
 public void ログインに成功してメニュー画面に遷移する() {

  LoginExcuteAction action = new LoginExcuteAction();

  new NonStrictExpectations() {{
   invoke(mockLoginExcuteAction,"isLogin",anyString,anyString); result = true;
        }};

        String jsp = action.doAction(mockRequest, mockResponse);

        assertThat(jsp, is("menu/memu.jsp"));

 }

 @Test
 public void ログインに失敗してログイン画面に遷移する() {

  LoginExcuteAction action = new LoginExcuteAction();

  new NonStrictExpectations() {{
   invoke(mockLoginExcuteAction,"isLogin",anyString,anyString); result = false;
        }};

        String jsp = action.doAction(mockRequest, mockResponse);

        assertThat(jsp, is("login/loginError.jsp"));

 }

}

分割してご説明しますと、まずフィールドでのモック宣言です。

/**  LoginExcuteActionのモック */
@Mocked("isLogin")
private LoginExcuteAction mockLoginExcuteAction;

今回は「テスト対象本体のモック化」ですので、このようにLoginExcuteActionをモック化する必要があります。
しかし、テスト対象本体を丸ごと全部モック化してしまってはテストの意味が無いですよね?
そういうわけで、「@Mocked("isLogin")」と書くことで、モック化したいメソッドだけをモック化して、それ以外の機能はモック化しないというスタイリッシュな手法が使用可能です。
この「モック化するクラスのうち、一部メソッドだけモック化」というのは、今回のようなprivateメソッドのモック化専用の話では無く、汎用的なシチュエーションで使用可能です。「この部分だけモック化すれば、他はそのままでいいのになぁ」という状況になったらこれを使って下さい。

次に、モック化後の振る舞いを記述する部分です。

@Test
public void ログインに成功してメニュー画面に遷移する() {

 LoginExcuteAction action = new LoginExcuteAction();

 new NonStrictExpectations() {{
  invoke(mockLoginExcuteAction,"isLogin",anyString,anyString); result = true;
        }};

 String jsp = action.doAction(mockRequest, mockResponse);

 assertThat(jsp, is("menu/memu.jsp"));

}

最初に「new LoginExcuteAction()」と、フィールドで宣言したモックのLoginExcuteActionとは別にテスト対象クラスを作っています。
しかし、実はこれ、意味がありません。
先頭でmockLoginExcuteActionを定義した為、後でnewしようが何をしようが、容赦なく暗黙的にモック化されます。
動作としては、ここでnewなんかしないで、以下全部mockLoginExcuteActionを使えば済む話です。
しかし、だからと言って「mockLoginExcuteAction」をテストメソッド本体で使用すると、「テストケースの目的がLoginExcuteActionのテストであること」がイマイチ読み取り難いものになってしまいます。
このため、私はあえてnewでクラスを作成して、テスト対象を明示するやり方を推奨します。

さて、肝心の「privateメソッドのモック化」の書き方ですが、それが以下の部分です。

new NonStrictExpectations() {{
  invoke(mockLoginExcuteAction,"isLogin",anyString,anyString); result = true;
        }};

「invoke(モックオブジェクト,メソッド名,引数,引数,……,引数)」

これでモック化完了です。
モック化するメソッド名をダイレクトに文字で書いて指定するわけです。

こうやってメソッドを普通の書き方で呼び出すのでは無く、文字列指定で呼び出すやり方を「リフレクション」と言います。
これはJavaに標準で備わっている機能でして、jmockitもそれを流用しているわけですね。

終わりに


これでprivateメソッドのモック化も出来ました。
jmockitの連載も第5回になりましたが、そろそろjmockitの使い方の勘所が分かってきたのではないでしょうか?

jmockitは特殊な機能を提供するライブラリですので最初は戸惑いますが、慣れてくると実に便利に作られていることが分かります。

次回以降も、引き続きjmockitについて解析を行います。

2013年9月9日月曜日

最強モックツール JMockit その4 内部newクラス

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はモックツール「JMockit」の使い方をご紹介しています。

今回は「内部でnewしているクラス」のモック化をご紹介しましょう。

内部newクラスのモック化


前回の記事で紹介した「ログイン処理」のソースを改変して以下に記載します。

public class LoginExcuteAction extends HttpServlet {
 public String doAction(HttpServletRequest req, HttpServletResponse res){

  //リクエストからログインIDとパスワードを取得
  String id = req.getParameter("login_id");
  String pw = req.getParameter("login_pw");

  //DB認証
  LoginService service = new LoginService();
  boolean success = service.isLogin(id,pw);

  //ログイン判定
  if(success){
   //ログイン成功
   return "menu/memu.jsp";
  }

  //ログイン失敗
  return "login/loginError.jsp";
 }
}

前回モック化したのは「HttpServletRequest req」と「HttpServletResponse res」の2つです。
つまり、「引数で渡す値のモック化」でした。

こういう風に「引数で制御可能」なテストケースの作成は、比較的簡単と言えます。
しかし、今回は前回と違う部分があります。

//DB認証
LoginService service = new LoginService();
boolean success = service.isLogin(id,pw);

ここです。
本体ソースの中で「new」している部分です。

このように、「本体ソースでnewして別インスタンスを作っている」というテストケースについては、普通にJUnitテストケースを書く場合、newしているクラスの中身まで考慮して引数を渡して、テストケースを実現しなければなりません。
つまり、上記ケースの場合、「LoginExcuteAction」「LoginService」の2つのクラスが合体したテストケースになってしまうわけです。


しかし、「二つのクラスが合体させてのテスト」というのは、厳密には「結合テスト」と言ってしまって良いでしょう。
単体テストフェーズであるユニットテストで行うのは、もちろん「単体テスト」です。
よって、ユニットテストで複数のクラスを結合したテストパターンを作るのは、作るのも大変ですし、ポリシーも満たしていないので、余り理想的とは言えないのです。
(現実としては、余りその辺りのポリシーには拘らす゛、書きやすいようにやってしまうのが一般的ですが……)

そこで、今回は「LoginService」をモック化して、真の意味での「LoginExcuteActionの単体テスト」をやってみたいと思います。

そのソースはこちらです。

public class LoginExcuteActionTest {

 /** HttpServletRequestのモック  */
 @Mocked
 private HttpServletRequest mockRequest;

 /**  HttpServletResponseのモック */
 @Mocked
 private HttpServletResponse mockResponse;

 /**  LoginServiceのモック */
 @Mocked
 private LoginService mockLoginService;

 @Test
 public void ログインに成功してメニュー画面に遷移する() {

  LoginExcuteAction action = new LoginExcuteAction();

  new NonStrictExpectations() {{
   mockLoginService.isLogin(anyString, anyString); result = true;
        }};

        String jsp = action.doAction(mockRequest, mockResponse);

        assertThat(jsp, is("menu/memu.jsp"));

 }

 @Test
 public void ログインに失敗してログイン画面に遷移する() {

  LoginExcuteAction action = new LoginExcuteAction();

  new NonStrictExpectations() {{
   mockLoginService.isLogin(anyString, anyString); result = false;
        }};

        String jsp = action.doAction(mockRequest, mockResponse);

        assertThat(jsp, is("login/loginError.jsp"));

 }

}

答えを言ってしまいますと、「jmockitは、モック化対象が内部newかどうかを意識する必要は無い」です。
前回と全く同じやり方で、「@Mocked」をつけて、「NonStrictExpectations」を使って定義すれば、内部newであっても容赦なくmock化してしまいます。
実に簡単ですね。

今回は新情報として、以下の部分もご説明します。
new NonStrictExpectations() {{
 mockLoginService.isLogin(anyString, anyString); result = false;
}};

「anyString」です。

jmockitは引数のパラメータに応じて、モック化するかしないかを分岐する機能があります。

  • mockLoginService.isLogin("tacy", "password"); result = false;

こういう風に書いてあったら、それは「IDがtacy、パスワードがpasswordで引数が来た時だけモック化する。それ以外はモック化しない」という意味です。

これによってモック化を必要とするケースと必要としないケースを厳密に制御出来るのでテストケースの品質向上に繋がるわけですが、
ハッキリ言って、「何でもいいから結果がfalseなってくれればいいんだ」という風に厳密さを必要としない時の方が多いです。

今回の場合、

  • 認証に成功したらログイン成功
  • 認証に失敗したらログイン失敗

ですから、厳密な定義など不要。「true/false」だけしっかりしていれば良いのです。


そういう場合は、「NonStrictExpectations」の中で「anyString」を使って下さい。
これで「文字列ならば何でもモック化する」という意味になります。

無論、これはString型を対象にした場合です。
Integer型の場合は「anyInt」、Boolean型の場合は「anyBoolean」、汎用objectの場合は「any」など、一通り用意されておりますので、その時々に応じたものを選んで下さい。


終わりに


普通にやる場合は苦労する「内部new」クラスも、jmockitを使えば簡単に実現出来ました。

次回は、またしても普通にやっては苦労するパターン「privateメソッドのモック化」についてご紹介します。

2013年9月4日水曜日

最強モックツール JMockit その3 Webリクエスト編

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はモックツール「JMockit」をご紹介しています。

さて、JMockitというのは非常に高機能なライブラリでして、大きく分けて以下3つのAPIに分類できます。

  • Expectations API
  • Verifications API
  • Mockups API

しかしながら、現実のプロジェクトにおける開発風景を考えますと、


「Expectations API」の一部機能だけ分かっていれば十分


と言ってしまっても差し支え無いかと思います。

というわけで、各APIの掘り下げは以降の連載で追々ご紹介するとして、当面は「とりあえず使ってみる」をテーマとして、JMockitを使えばこういうことが出来るのだという実例をご紹介したいと思います。

最初は「Webリクエスト」です。

Webリクエストのモックテスト

ここで言うWebリクエストとは、「画面のフォームに値を入れてサブミットする」という一般的なWebシステムの通信に使用される、あのリクエストのことです。

例として「ログイン画面からIDとパスワードを入力してログインする」という機能をtomcatで実現する機能を挙げてみます。


public class LoginExcuteAction extends HttpServlet {
 public String doAction(HttpServletRequest req, HttpServletResponse res){

  //リクエストからログインIDとパスワードを取得
  String id = req.getParameter("login_id");
  String pw = req.getParameter("login_pw");

  /**
   * 実際にはここでDBにログイン認証を行う。
   * サンプルは文字列一致で済ませる。
   */
  if(id.equals("tacy") && pw.equals("password")){
                        //ログイン成功
   return "menu/memu.jsp";
  }

                //ログイン失敗
  return "login/loginError.jsp";
 }
}

このサンプルでテストすべきは以下機能です。

  • ログイン成功(ログインIDがtacy、パスワードがpassword)だったら、ログイン成功してメニュー画面へ進む。
  • ログイン失敗だったら、ログインエラー画面に進む。

至ってシンプルで平凡なWeb機能です。
画面で実際に手動でIDとパスワードを入れて動作確認するのであれば、簡単にテストできます。

しかし、これをJUnitで行うとしたら、どうでしょう?

そう、普通には出来ないんですよね。

と言うのも、「HttpServletRequest」「HttpServletResponse」はtomcatライブラリのクラスで、普通に「new」して作ることが出来ません。
(リクエストやレスポンスというのは、人間が画面に入力した項目以外にも色々なパラメータを持っていますからね。)

つまるところ、直球で「HttpServletRequest」「HttpServletResponse」を作ってJUnit実行とは出来ないのです。

そこで黒魔術登場です。
「HttpServletRequest」「HttpServletResponse」をモック化して、ダミーのリクエストでテストをやってしまいましょう!!


public class LoginExcuteActionTest {

 /** HttpServletRequestのモック  */
 @Mocked
 private HttpServletRequest mockRequest;

 /**  HttpServletResponseのモック */
 @Mocked
 private HttpServletResponse mockResponse;

 @Test
 public void ログインに成功してメニュー画面に遷移する() {

  LoginExcuteAction action = new LoginExcuteAction();

  new NonStrictExpectations() {{
   mockRequest.getParameter("login_id"); result = "tacy";
   mockRequest.getParameter("login_pw");result = "password";
                }};

                String jsp = action.doAction(mockRequest, mockResponse);

                assertThat(jsp, is("menu/memu.jsp"));

 }

 @Test
 public void ログインに失敗してログイン画面に遷移する() {

  LoginExcuteAction action = new LoginExcuteAction();

  new NonStrictExpectations() {{
   mockRequest.getParameter("login_id"); result = "tacy";
   mockRequest.getParameter("login_pw");result = "error";
                }};

                String jsp = action.doAction(mockRequest, mockResponse);

                assertThat(jsp, is("login/loginError.jsp"));

 }

}

まず、フィールドにアサーション「@Mocked」を宣言して「HttpServletRequest」「HttpServletResponse」のモックを作ります。
そして、各メソッドの「new NonStrictExpectations」の中で、モックとしての挙動を定義します。
最後にモックを引数として普通にメソッドを実行すれば、テスト成功です。

簡単ですね!!


終わりに


今回紹介した「@Mocked」+「new NonStrictExpectations」の組み合わせ、これがjmockitで一番簡単なやり方です。

もちろん違う書き方もあり、例えば「モックが呼ばれた回数も確認したい」「モックを呼ぶ順番も制御したい」など細かな要望にも対応可能ですが、
現実として、大概はこの「@Mocked」+「new NonStrictExpectations」で済んでしまうかと思います。

次回もjmockit活用法のサンプルをご紹介します。

2013年8月26日月曜日

最強モックツール JMockit その2 インストール編

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はモックツール「JMockit」をご紹介しています。
今回はインストールと動作確認まで行ってみましょう。

ダウンロード&インストール

まずはJUnit本体のダウロードです。

何度も書いていますが、Eclipseの標準JUnitだと不足がありますので、自前でJUnitセットをダウンロードして下さい。


次にJMockitをダウンロードします。
この記事を執筆している現時点での最新は1.4です。


ダウンロードしたzipファイルを解凍すると「jmockit.jar」と「jmockit-coverage.jar」の二つのjarファイルがあります。
それぞれ、jmockit本体とカバレッジ出力ツールです。

今回の連載ではカバレッジも紹介しますので、両方とも導入しましょう。

ダウンロードしたら、これら全部をEclipseのクラスパスに入れて完了。
単にjarファイルを導入するだけの簡単な作業です。


では無いんですよね!!


何と、jmockitは「クラスパスをJUnit本体より先にしなければならない」という制約があります。

普通の開発ではクラスパスの順番など意識しないと思いますが、今回は違います。
Javaのプロジェクトに複数のjarファイルを導入する際、異なるjarファイル間で同じクラスが存在していた場合は、先に書いた方のjarファイルの方が優先されます。

つまり、jmockitはJUnit本体より先にクラスパスを書いて、JUnit本体の一部を潰して機能を実現しているわけです。
導入段階から早くも黒魔術です。

Eclipseの場合、プロジェクトのプロパティから「順序およびエクスポート」の画面に進むことでクラスパスの順番を変更出来ます。

私の環境では以下のような状態になりました。



これでセットアップ完了です。

動作確認

次に細かいことは抜きにして、まずは動作確認してみましょう。

本体ソースの方は、以下のようにただ「テスト」という文字列を返すだけのクラスとメソッドを作ります。

public class Sample01 {
 public String doTest() {
  return "テスト";
 }

}

これに対し、jmockitを使って「テスト」という返り値を「モック」に差し替えてみたクラスが以下です。
/**
 *
 */
package jp.co.net.genesis.sample;

import mockit.Mocked;
import mockit.NonStrictExpectations;

import org.junit.Test;

public class Sample01Test {

 /** モック  */
 @Mocked
 private Sample01 mockSample01;

 /**
  * {@link jp.co.net.genesis.sample.Sample01#doTest(java.lang.String[])} のためのテスト・メソッド。
  */
 @Test
 public void testDoTest() {

  new NonStrictExpectations() {{
   mockSample01.doTest(); result = "モック";
        }};

        System.out.println(mockSample01.doTest());

 }

}

ひとまず実行してみて下さい。
「モック」という文字になったかと思います。
これでjmockitの動作確認が出来ました。

上記ソースにありますように、モック化するクラスをフィールド変数に定義し、「@Mocked」というアサーションを付けるのがお約束です。
そして、具体的にどんな機能に差し替えるのかを書いているのが、「NonStrictExpectations」の内部の部分です。

意味は以後の連載でご説明するとして、今回の所は動きを確認出来ればOKでしょう。

カバレッジ


私の環境だと、Eclipseのコンソールには以下のように表示されます。


どうやらカバレッジも出力してくれたようですね。
導入段階で「jmockit-coverage.jar」を入れましたので、自動的にカバレッジまで出力してくれたようです。

さっそく見てみましょう。

まず、index.htmlを開くと、以下が表示されます。


どうやら全体のカバレッジ率を出してくれる画面のようです。
今回の場合「0%」になっていますが、これは本体ソースをモックで潰して実行してしまった為、本体ソースは全く実行していないからです。
このように、jmockitはちゃんと「本体ソースを通った箇所」「モックで潰した箇所」を別々にカウントしてくれますので、カバー漏れ等は発生しません。

クラス名をクリックすると、カバレッジ詳細が表示されます。


画像は赤一色になっていますが、通過した箇所としていない箇所をちゃんと別々の色で表示してくれますので、分かりやすいです。

これにてカバレッジの出力も確認出来ました。

注意点


ここで一つ注意を入れておきたいと思います。
jmockitの公式サイトを見ると、以下のような文章が。

HTML reports: a multi-page HTML report is written in the "coverage-report" directory, under the current working directory (a different output directory can be specified if needed). The directory is created if it doesn't yet exist; its contents are overwritten if previously generated. The report will include pages containing all Java source files covered by the test suite. By default, the tool looks for ".java" source files inside all directories of name "src" found directly or indirectly under the current working directory; any intermediate sub-directories between "src" and the top-level package directory, such as "src/java" for example, are also searched.

何と、カバレッジを出力する際は「フォルダ名」の指定があるようで、デフォルトでは「src」の配下がソース本体として扱うだそうです。

つまり、ソースフォルダの構成が影響します。

src
 L main
    Ljava
    Lresource 
 L test
    Ljava
    Lresource

みたいにsrc配下に「main」と「test」を置いてしまうと、testソースまでカバレッジ対象になってしまい、HTML出力にゴミが混ざってしまいます。

また別のパターンとして、

main
  Ljava
  Lresource 
test
  Ljava
  Lresource

みたいにsrcという名前のフォルダが無いと、カバレッジが出力されません。

では私はどうしたかと言うと、

src
  Ljava
  Lresource
test
  Ljava
  Lresource

こういう構成にしてみました。
これなら綺麗に出力出来るようです。

「カバレッジが出ないぞ!?」と困っている人は大概はコレだと思いますので、ご確認下さい。


終わりに


ひとまずこれでJMockitのインストールが出来ました。

次回からはJMockitの各種詳細機能の検証に進みます。

高機能なライブラリですので長くなると思いますが、最後までお付き合い下さい。

2013年8月17日土曜日

最強モックツール JMockit その1

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

今回から新シリーズ「モック編」の始まりです。

タイトルに「最強」などと派手な名前をつけてしまいましたが、実際に最強と呼び声の高い強力なJUnit支援ツールがあります。
それがモックツール「JMockit」です。
しかし、第一回である今回はJMockit個別の話の前に「モックとはなんぞや?」という所からご説明しましょう。

百聞は一見に如かず。まず、例をお見せします。


まず、以下のように「ジェニシス」という文字を返すだけの、シンプルなクラスを作ります。

public class Simple {
 public String getStr(){
  return "ジェニシス";
 }
}

これに対してテストケースを作り、System.out.printlnで出力結果を見てみます。
public class SimpleTest {
 @Test
 public void testGetStr() throws Exception {
  Simple simple = new Simple();
  System.out.println(simple.getStr());
 }
}

まあ、当然ながらコンソールには「ジェニシス」と出ます。当たり前です。

これに対し、モックを入れてみます。

public class SimpleTest {
 @Mocked
 private Simple mockSimple;
 @Test
 public void testGetStr() throws Exception {
  new NonStrictExpectations() {{
   mockSimple.getStr(); result = "モックになりました。";
        }};
  System.out.println(mockSimple.getStr());
 }
}

すると、コンソールには何故か「モックになりました。」と出てきてしまいました!?

不思議ですね。。。

本体ソースを見る限りはどうやったって「simple.getStr() == "ジェニシス"」以外にありえないのですが、
実際には違う値が出てきてしまいました。。。

このように「本体ソースの機能をダミーに差し替えて実行する」というハッキングみたいなことをする。
それがモックです。
テスト上の役割としては「スタブ」に近いものとなります。

定義の上では厳密言えば「スタブ」と「モック」は違うものなのですが、下位機能を本体とは別のソースで置換するという点は同じです。

本体ソースではテストが難しいケースに対し、ダミーの値に差し替えてテスト出来るというのがスタブの存在意義です。

モックの自作


上記のモックソースはJMockitを使った結果ですが、別にツールを使わなくても自分でモックすることは可能です。

public class SimpleTest {
 @Test
 public void testGetStr() throws Exception {
  Simple simple = simpleMock();
  System.out.println(simple.getStr());
 }
 private Simple simpleMock(){
  Simple mock = new Simple(){
         public String getStr(){
          return "モックになりました。";
         }
        };
        return mock;
 }
}

「Simpleを継承した別クラス」を作って、メソッドをオーバライドした上で差し替えてるわけです。
以下の部分がそれに該当します。

Simple mock = new Simple(){
  public String getStr(){
    return "モックになりました。";
  }

普通にクラスを継承して作るのでは無く、コードの中でクラスを作っています。
こういう書き方をしたクラスを「無名クラス」と言います。

今回私が作ったサンプルモックは低機能かつSimpleクラス専用ですが、JMockitを初めとした各種モックツールは、
これをどんなクラスにでも動的に対応してくれます。

動的対応には「リフレクション」という文字列からクラスを動的に生成する機能を使うのですが、
そこまで説明すると記事が長くなり過ぎるので割愛します。

それに、プライベートメソッドも容赦なく変えたり、クラスの中でnewしているクラスまで差し替えたりと何でもござれ。

でも、普通はこんなコーディングしませんよね。
このように、モックツールというのはJavaの中でも『黒魔術』とまで称される禁断のテクニックをフル活用した裏技ツールなのです。

モックツールの選別


さて、モックツールは世の中に沢山ありますので、どれを使うのが良いかという問題があります。
有名所と言えば以下辺りなんじゃないでしょうか?

  • EasyMock
  • Mockito
  • jMock

しかし、あえて私はタイトルにもありますように、JMockitを推したいと思います。
機能の豊富さが理由です。

以下のサイトをご覧下さい。


JMockitの公式サイトなので多少の贔屓が入っているのかもしれませんが、
やはり機能面では最強と見て間違い無いようです。

一方、「機能が多ければ使いこなすのも難しいのではないか?」という印象を受けるのも事実です。
しかし、逆に言えば使いこなせるだけの知識があれば無敵!!

ここは一つ、気合い入れて研究してみようではありませんか。

次回


次回から本格的にJMockitについて解説。
最初はインストール編です。

2013年8月12日月曜日

データベースのテスト支援ツール DbUnit その4 デザインパターン編

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

データベースのテスト支援ツール「DbUnit」の連載も今回で最終章です。

DbUnitの意義、基本的使い方につきましては前回まででご紹介済みです。
もう十分にDbUnitを有効活用していくことが出来るかと思います。

最終章の今回は、殆ど小ネタに過ぎない話ですが、デザインパターンについてご紹介します。

さて、DbUnitを用いたDB関連のテストは、概ね以下の構成を取ります。

  1. DBのセットアップ(テーブルのクリア、レコードの登録)
  2. テスト本体
  3. 終了処理(テーブルの掃除、セッションの切断)

ここで地味に問題になってくるのが、「DBのセットアップ」の処理の重さです。

「DBセットアップ=レコードのインサート」である為、この部分の処理に必要とする時間がオンメモリの処理と比べて重いのです。
一回一回のインサート処理ならばそんなに気にならない程度の重さですが、塵も積もれば山となる。
大きなプロジェクトで複数人が大量のDbUnitテストを作成した場合、段々段々と時間が掛かるようになり、仕舞いには一回のテスト実行で何時間も待たされたり、下手すれば一晩待っても終わらないです。
また、そんなに派手に遅くならない場合でも、ユニットテストは何度も繰り返すものですので、長い目で見れば馬鹿にならない時間を待ち時間に費やしていることになります。
これを「スローテスト問題」と言いまして、JUnit開発者の悩みの種です。

ユニットテストは、処理速度を意識しなければならない。

処理速度は、ユニットテストにおいて意外に大事な要素なのです。
今回のテーマはデザインパターンに加え、処理効率化についてもご紹介します。

デザインパターン


デザインパターンの要点は簡単です。
「Enclosedを使用し、DBのセットアップの種類に応じてグループ化すれば良い」です。

Enclosedについて忘れた方はこちら

/**
 * @author 技術開発事業部 遠藤 太志郎
 *
 */
@RunWith(Enclosed.class)
public class ShohinServiceTest {

 public static class テーブルが空の状態から始まるテスト {
  @Before
  public void setUp() throws Exception {
   //セットアップ処理(軽い)
  }
  @After
  public void tearDown() throws Exception {
   //終了処理
  }
  @Test
  public void testInsert() throws Exception {
   //テスト本体1
  }
  @Test
  public void testInsert2() throws Exception {
   //テスト本体2
  }
  @Test
  public void testInsert3() throws Exception {
   //テスト本体3
  }
  //以下続く……

 }

 public static class テーブルに100件のレコードが入っている状態から始まるテスト_select {
  @BeforeClass
  public static void setUpBeforeClass() throws Exception {
   //セットアップ処理(重い)
  }
  @AfterClass
  public static void tearDownAfterClass() throws Exception {
   //終了処理
  }
  @Test
  public void testFindById() throws Exception {
   //テスト本体1
  }
  @Test
  public void testFindById2() throws Exception {
   //テスト本体2
  }
  @Test
  public void testFindById3() throws Exception {
   //テスト本体3
  }
  //以下続く……
 }

}

こんな感じです。
この要に「テーブルの初期状態でグループ化」しておくと可読性が高まります。
DbUnitのデザインパターンとしては鉄板の構成と言えるでしょう。

そして、処理効率化については以下のテクニックを使用しています。
上のソース、実は二種類のセットアップ方法を使い分けています。

@Before
public void setUp() throws Exception {
 //セットアップ処理
}

@BeforeClass
public static void setUpBeforeClass() throws Exception {
 //セットアップ処理
}

そう、「@Before」と「@BeforeClass」を使い分けているのです。
正確に言えば、「@After」「@AfterClass」も使い分けています。

このアサーションの意味は以下です。

アサーション意味
@Before各テストメソッドの実行前に毎回呼び出される。
@BeforeClass各テストクラスの実行前に一回だけ呼び出される。
@Afterデータ各テストメソッドの実行後に毎回呼び出される。
@AfterClass各テストクラスの実行後に一回だけ呼び出される。

つまり、

  • 「select系のテスト」のようにテストによってDBの中身が変わらない場合は「@BeforeClass」を使う。
  • 「insert、update、deleteのテスト」のようにテストによってDBの中身が変わる場合は「@Before」を使う。

こうすることによって、「select系のテスト」におけるセットアップ処理の実行回数を減らしているわけですね。
「insert、update、deleteのテスト」は高速化出来ませんが、これは仕方ありません。

しかし、多くのシステムの場合、「selecte系テストの機能の数 > 他の機能の数」という構成の事が多いですので、
select系のテストだけ高速化しても結構な成果を上げることが出来るのです。

注意事項


しかし、これだけは明言しておかなければいけません。

「@BeforeClassはなるべく使うな」

こんな記事を書いておいて何だと思われるかもしれませんが、これは大事な事です。
JUnitのテストは「各テスト間で干渉しない」ということが絶対必須。
メソッド毎の独立性は高ければ高いほど良いのです。

このため、メソッド毎に初期化を行う「@Before」は独立性が高いのに対し、
クラスで一回しか初期化しない「@BeforeClass」は独立性が低いと言えるでしょう。

「@BeforeClass」はテストの品質を下げる諸刃の刃なのです。

この為、

  • 「どうしてもこれらのテスト群は処理が重いから@BeforeClassで高速化しよう」
  • 「こっちのテスト群はそこまでの重さではないから、@Beforeにしておこう」

と言うように、@BeforeClassは最終手段の特殊対応。
基本は@Beforeを使うように心がけて下さい。

終わりに


以上でDbUnitのご紹介は終わりです。

今までの記事ではJUnitの機能をオンメモリで使う例を挙げて来ました
しかし今の時代、大抵は「DBの登録、更新、削除、検索」がシステムの主な機能ということが殆ど。
オンメモリで終わるシステムなんぞ滅多に無いでしょう。
このDbUnitの使い方をマスターした段階で、ようやく現場で使えるレベルに入ったといった所かと思います。

次回からは新章、「モック編」です。
JUnit界最強と名高いモックツール「JMockit」についてご紹介します。

2013年8月2日金曜日

データベースのテスト支援ツール DbUnit その3 DB参照編

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はデータベースのテスト支援ツール「DbUnit」の連載中です。

前回の記事では、DbUnitとは以下の作業を簡単にやってくれるライブラリであるとご紹介しました。

  • テーブルのセットアップ
  • テーブルの中身のチェック

前回のテストデータ登録編は「テーブルのセットアップ」に相当する内容でしたので、今回のDB参照編は「テーブルの中身のチェック」について執筆したいと思います。

DB参照機能の利用シチュエーションとしては、例えば「レコードの登録(insert)」を行う時です。

「テーブルにレコードを登録した結果、テーブルの中身が想定した内容になっていること」

この機能、自分で作る場合は自分でselect文を書かねばならず大変面倒ですが、DbUnitを使えば簡単に実現することが可能です。

処理の流れとしては以下になります。

  1. 現在入っているレコードを全部クリア
  2. レコードを登録
  3. テーブルの中身をチェック

赤文字の部分はDbUnitの機能がやってくれますので、開発者は自分の作ったソースである「レコードの登録」の部分だけテストコードを書けば良いというわけです。

実例編(テストデータ登録)


では、実際にサンプルソースをお見せしましょう。

商品テーブルにレコードを一件登録する「insert」のテストソースは以下になります。

public class ShohinServiceTest {

 /** コネクション */
 private Connection conn = null;
 /** DbUnit専用 */
 private IDatabaseConnection connection = null;

 /**
  * 最初にコネクションを接続する。
  *
  * @throws java.lang.Exception
  */
 @Before
 public void setUp() throws Exception {
  //JDBCドライバーをセット
  Class.forName(GenesisConfig.JDBC_DRIBER);
  //Connectionの取得
  conn = DriverManager.getConnection(GenesisConfig.DB_URL,
    GenesisConfig.DB_USER, GenesisConfig.DB_PASSWORD);
  //IDatabaseConnectionの作成
  connection = new DatabaseConnection(conn);
  //データセットの取得
  IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream(
       "src/test/resource/jp/co/net/genesis/entity/商品登録テストの予想結果.xml"));
  //テーブルをクリア
  DatabaseOperation.DELETE_ALL.execute(connection, dataset);
 }

 /**
  * コネクションをクローズして終了
  *
  * @throws Exception
  */
 @After
 public void tearDown() throws Exception {
  if(connection != null){
   connection.close();
  }
  if(conn != null){
   conn.close();
  }
 }

 /**
  * {@link jp.co.net.genesis.service.ShohinService#insert()} のためのテスト・メソッド。
  */
 @Test
 public void testInsert() throws Exception {

  ShohinService shohinService = new ShohinService();

  Shohin shohin = new Shohin();
  shohin.setShohinName("ジェニシス商品");
  shohin.setPrice(100);

  SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
  shohin.setCreateAt(format.parse("2013-07-31"));
  shohin.setUpdateAt(format.parse("2013-08-01"));

  //レコードを登録
  shohinService.insert(shohin);

  //レコードを登録した結果の、レコード構成の予測値
  IDataSet expectedDataset = new FlatXmlDataSetBuilder().build(
                     new File("src/test/resource/jp/co/net/genesis/entity/商品登録テストの予想結果.xml"));
  ITable expected = expectedDataset.getTable("shohin");

  //実際のテーブルのレコード構成
  ITable actual = connection.createDataSet().getTable("shohin");
  actual = DefaultColumnFilter.excludedColumnsTable(actual, new String[] { "id" });

  Assertion.assertEquals(expected, actual);

 }

}

量が多いですので、分解してご説明します。

コネクションの取得とテーブルのクリア


DBに接続して「IDatabaseConnection」を取得する部分については、前回と同じです。
前回はセットアップ時に1回接続するのみでOKでしたので、コネクションは使用したらすぐにクローズしていました。
しかし今回は「セットアップ」「チェック」の2回コネクションが必要となりますので、コネクションはグローバル変数に持つことにしました。
そして処理終了後の「tearDown」でクローズしています。

「setUpは各処理の最初に呼ばれる」
「tearDownは各処理の終了時に呼ばれる」

これはJUnitの中でも基本的で有名な部分ですので、あえて特筆する必要も無いでしょう。

今回の記事で特筆すべきは、以下の部分です。

IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream(
       "src/test/resource/jp/co/net/genesis/entity/商品登録テストの予想結果.xml"));
     //テーブルをクリア
     DatabaseOperation.DELETE_ALL.execute(connection, dataset);

前回の記事でも似たようなコードがありましたが、
前回との違いは処理モードが「DELETE_ALL」になっている点です。

パラメータ機能
NONEダミー。何もしない。
UPDATEデータセットで定義されているレコードを更新する。
INSERTデータセットで定義されているレコードを登録する。
REFRESHデータセットで定義されているレコードがテーブルに無ければ登録、あれば更新する。
DELETEデータセットで定義されているレコードをテーブルから削除。
DELETE_ALLデータセットで定義されているテーブルから全レコードを削除。
TRUNCATE_TABLEデータセットで定義されているテーブル自体を抹消。
CLEAN_INSERTDELETE_ALLしてからINSERT

前回も掲載したこの表にありますように、セットアップ時には複数の処理モードを選択することが出来ます。
今回はレコードの登録テストである都合上、テーブルの初期状態は空っぽである必要があるため、DELETE_ALLを選択しました。

「商品登録テストの予想結果.xml」の中に定義されているテーブルから全レコードを削除します。
もちろん、このxmlの中には「shohin」が定義されていますので、商品テーブルが全抹消となるわけです。

これでテーブルが綺麗になりました。

レコードの登録

レコードの登録は「そういう機能を自分で作っているだけ」ですので、DbUnitとして特記すべき機能はありません。
Shohin shohin = new Shohin();
 shohin.setShohinName("ジェニシス商品");
 shohin.setPrice(100);
 
 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
 shohin.setCreateAt(format.parse("2013-07-31"));
 shohin.setUpdateAt(format.parse("2013-08-01"));
 
 //レコードを登録
 shohinService.insert(shohin);

しかし、ここで予め断っておかなければならない点は「IDはセットしていない」ということです。
IDはserialによる自動インクリメントのパラメータですので、「insertメソッド」にはIDをセットする機能は入っていないのです。
つまり、IDにどんな値が入るかはその時の環境次第。
テストコードで制御出来ない厄介なパラメータということになります。

テーブルのチェック

最後にテーブルのチェックです。果たして「insert」は正常に機能してくれたでしょうか?

//レコードを登録した結果の、レコード構成の予測値
 IDataSet expectedDataset = new FlatXmlDataSetBuilder().build(
             new File("src/test/resource/jp/co/net/genesis/entity/商品登録テストの予想結果.xml"));
 ITable expected = expectedDataset.getTable("shohin");

 //実際のテーブルのレコード構成
 ITable actual = connection.createDataSet().getTable("shohin");
 actual = DefaultColumnFilter.excludedColumnsTable(actual, new String[] { "id" });

さて、ここでまたxmlファイル「商品登録テストの予想結果.xml」が登場していますね。

このxmlファイルの中身は以下です。

<dataset>
 <shohin shohin_name="ジェニシス商品" price="100" create_at="2013-07-31"  update_at="2013-08-01" />
</dataset>

ただテーブルのレコード構成を書いておけば良い、というシンプルで分かり易い仕様です。
しかし、ここで一つ問題が。
「ID」が入っていません。

つまり、「ID」にどんな値が入ってくるか分からないため、IDはスルーしているわけです。
このような特定カラムのスルーは、Javaのソース上で「DefaultColumnFilter.excludedColumnsTable」を使ってスルー対象カラムを定義し、その上でxmlファイルに記述しなければOKです。
これで無事にテーブルの照合が出来ました。

終わりに


これでDbUnitの基本的な使い方は終了です。
独自のクラス、設定、xmlなどを使うので最初は大変ですが、慣れてしまえば簡単なものです。

実際の開発では、DbUnit周りは別途フレームワーク化して、DatabaseConnectionやIDataSetなどゴチャゴチャしている所はフレームワークに預けてしまう形を取るのが良いでしょう。

次回は応用編と題しまして、JUnit本体の機能とDbUnitを組み合わせた、テストケースの整理についてご紹介します。

2013年7月24日水曜日

データベースのテスト支援ツール DbUnit その2 テストデータ登録編

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

前回よりデータベースのテスト支援ツール「DbUnit」のご紹介をしております。

さて、前回の記事ではDbUnitの説明というより、JUnit開発というのは全般的に環境設定周りが面倒だ、というお話をしました。
今回はいよいよ、DbUnitの何が便利なのかをご紹介しましょう。
簡単に言えば、DbUnitとは以下の作業を簡単にやってくれるライブラリです。

  • テーブルのセットアップ
  • テーブルの中身のチェック

これだけです。
たったこれだけですが、これを自前の開発でやろうとすると実に大変な労力を要するもの。

例えば、「テーブルから主キーで1アイテムを検索する機能のテスト」を想定してみましょう。

テーブルの検索機能のテストである以上、テーブルの中身が前もって想定された状態になっていなければJUnitが正常に動作しません。
そこで、テストとしては以下の順番を辿ります。
  1. 現在入っているレコードを全部クリア
  2. そのテスト専用のレコードをセットアップ
  3. テスト実行
この「1」と「2」ですが、自分でSQL文を書いてこの機能を実現していたのでは非常に面倒ですし、そもそも「DBとの処理を行う機能のテストの為に、DBとの処理を行う機能を作る」では意味が分かりません。
新しくバグを埋め込むのがオチです。
しかし、DbUnitには「1」と「2」の機能が最初から入っています。
これにより、簡単で、高機能で、頑強なテストを行えるわけです。

今回はDbUnitの二大機能「セットアップ」「チェック」のうち、セットアップの方をご紹介します。

実例編(テストデータ登録)

では、実際にサンプルソースをお見せしましょう。

商品テーブルを主キー検索する「findById」のテストソースは以下になります。

public class ShohinServiceTest {

  /**
   * @throws java.lang.Exception
   */
  @Before
  public void setUp() throws Exception {
 Connection conn = null;
 IDatabaseConnection connection = null;
 try{
  //JDBCドライバーをセット
  Class.forName(GenesisConfig.JDBC_DRIBER);
  //Connectionの取得
  conn = DriverManager.getConnection(GenesisConfig.DB_URL,
    GenesisConfig.DB_USER, GenesisConfig.DB_PASSWORD);
  //IDatabaseConnectionの作成
  connection = new DatabaseConnection(conn);
  //データセットの取得
  IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream(
    "src/test/resource/jp/co/net/genesis/service/商品_登録数3.xml"));
  //セットアップ実行
  DatabaseOperation.CLEAN_INSERT.execute(connection, dataset);
 }finally{
  if(connection != null){
   connection.close();
  }
  if(conn != null){
   conn.close();
  }
 }
  }

  /**
   * {@link jp.co.net.genesis.service.ShohinService#findById()} のためのテスト・メソッド。
   */
  @Test
  public void testFindById() throws Exception {
 ShohinService shohinService = new ShohinService();
 Shohin shohin = shohinService.findById(111);
 //エンティティのチェックにはMacherを使いましょう。
 assertThat(shohin,new ShohinMacher(shohin));
  }

}

重要点は沢山あるので、分解してご説明します。

コネクション取得

DbUnitはデータベースに接続するライブラリですので、
DbUnit専用に別途コネクションを取得する必要があります。
以下がその部分です。

public void setUp() throws Exception {
 Connection conn = null;
 IDatabaseConnection connection = null;
 try{
  //JDBCドライバーをセット
  Class.forName(GenesisConfig.JDBC_DRIBER);
  //Connectionの取得
  conn = DriverManager.getConnection(GenesisConfig.DB_URL,
    GenesisConfig.DB_USER, GenesisConfig.DB_PASSWORD);
  //IDatabaseConnectionの作成
  connection = new DatabaseConnection(conn);

                //省略

 }finally{
  if(connection != null){
   connection.close();
  }
  if(conn != null){
   conn.close();
  }
 }
  }

  • JDBCドライバーをセットする。(GenesisConfig.JDBC_DRIBER)
  • 接続先データベースをセットする。(GenesisConfig.DB_URL)
  • 接続ユーザをセットする。(GenesisConfig.DB_USER)
  • 接続パスワードをセットする。(GenesisConfig.DB_PASSWORD)
  • コネクションをセットする。

至ってシンプルな段取りを踏む作業です。
「IDatabaseConnection」というDbUnit独自のインターフェースが登場しています。
これによってDbUnitはデータベースに接続するわけですが、特記事項と言うべき程の事も無く、こういうものだとご理解頂ければ良いかと。

今回はソースを紹介する都合上、全ソースを「ShohinServiceTest」に記載していますが、
実際にはこのコネクション取得部分は別途ユーティリティクラスを作るか、スーパークラスを作ってライブラリ化することをオススメします。

データ定義

次にデータの登録です。
今回は主キー検索で1件を取得する機能のテストですので、テーブルには3件のレコードを入れておき、その中から狙ったレコードが取得出来ることを以てテストOKとするシナリオにしたいと思います。

そのうち、データを定義する部分が以下です。

//データセットの取得
 IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream(
     "src/test/resource/jp/co/net/genesis/service/商品_登録数3.xml"));

ファイル名から分かりますように、データの定義は別途xmlで定義します。
これをデータセットと言います。


ここで前回でご説明した「ソースフォルダの切り分け」が効いてきます。
xmlファイルは別にどこに置いても動作は可能えすが、テストリソースフォルダのパッケージの配下に置くことによって、そのxmlが何のデータなのか整理がつくわけです。

このxmlファイルの中身は以下です。

<dataset>
 <shohin id="111" shohin_name="テスト商品1-1-1" price="111" create_at="2013-01-20"  update_at="2013-01-17" />
 <shohin id="222" shohin_name="テスト商品2-2-2" price="222" create_at="2013-02-20"  update_at="2013-02-17" />
 <shohin id="333" shohin_name="テスト商品3-3-3" price="333" create_at="2013-03-22"  update_at="2013-02-23" />
</dataset>

つまり、以下のような構造になっていればOKというわけです。
<dataset>
   <テーブル名 カラム名="値" ……/>
   <テーブル名 カラム名="値" ……/>
</dataset>

見易くで綺麗なフォーマットですね。

備考

上記の例はxmlファイルですが、データセットはエクセルで用意することも可能です。
エクセルなら行を増やす時にエクセルの機能でカウントアップとかコピー&ペーストが出来て簡単とう便利な側面がある反面、ファイルを開くのが重くなったり、svn等での比較が難しかったり、ファイルサイズが大きくなったりするなどのデメリットもあります。
どちらを使うかは好みの問題と言えるでしょう。

登録実行

「データベースとのコネクションは取得した。」「登録するデータセットも作成した」
後はテーブルに流し込むだけです。

//セットアップ実行
 DatabaseOperation.CLEAN_INSERT.execute(connection, dataset);

DbUnitでは作成したデータ定義を流し込む際に、実行モードを指定することが出来ます。
それが以下です。

「DatabaseOperation.CLEAN_INSERT」

データ定義に記入されているテーブルの中身を全部クリアした後、新規で登録するというモードです。
登録モードには複数種類がありまして、以下がその一覧です。

パラメータ機能
NONEダミー。何もしない。
UPDATEデータセットで定義されているレコードを更新する。
INSERTデータセットで定義されているレコードを登録する。
REFRESHデータセットで定義されているレコードがテーブルに無ければ登録、あれば更新する。
DELETEデータセットで定義されているレコードをテーブルから削除。
DELETE_ALLデータセットで定義されているテーブルから全レコードを削除。
TRUNCATE_TABLEデータセットで定義されているテーブル自体を抹消。
CLEAN_INSERTDELETE_ALLしてからINSERT

色々な機能が揃っていますね。

しかし、私はこの中で使うのは「CLEAN_INSERT」「DELETE_ALL」の2つで十分というか、この2つ以外には使わないと縛った方が良いケースが殆どかと思います。
状況次第では「全部消してインサートだと処理が重くなるので、UPDATE等を使って必要な部分だけ差し替える」といった需要も出てくるかもしれません。
しかし、それだとテスト間の結合が密になってしまい、「片方のテストケースを直したら、別のテストケースが壊れた」という事態が発生する可能性が高まります。
「全部抹消」と「新規登録」。少々非効率であったとしても、この2パターンの組み合わせでテストケースを作っていくことが、堅牢なテストを行うには一番だと思います。

チェック

最後は普通にチェックを行うだけです。
「テーブルのセットアップ」と「チェック」を分けて定義することによりソースが綺麗になっています。

@Test
  public void testFindById() throws Exception {
 ShohinService shohinService = new ShohinService();
 Shohin shohin = shohinService.findById(111);
 //エンティティのチェックにはMacherを使いましょう。
 assertThat(shohin,new ShohinMacher(shohin));
  }

終わりに

今回は初めてのDbUnit使用でしたので内容が盛り沢山になってしまいました。
上の方では「DbUnitを使えば簡単にデータベースのテストが出来る」と書いてしまいましたが、それはある程度使い慣れて、フレームワークが整った後の話です。
最初の導入段階ではある程度の負荷が発生するかもしれません。

しかし、最初こそ大変かもしれませんが、使いこなせれば非常に便利なライブラリであることも確かです。
ぜひ使いこなして、充実したユニットテストを実現して下さい。

2013年7月18日木曜日

データベースのテスト支援ツール DbUnit その1 インストール編

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はテスト自動化シリーズと題しまして、JUnit関連のご紹介を執筆しています。
今回からはデータベースのテスト支援ツール「DbUnit」のご紹介になります。

さて、JUnitの開発で発生しがちなのが「環境依存」です。

  • あの時は正常に動いたのに、今は動かない。
  • Aさんが動かすと正常なのに、Bさんが動かすと動かない。

といったことが結構頻繁に起こりがちです。

原因としましては、

  • データベースが完璧にセットアップされている時だけ正常。ゴミレコードが入っているとNGになる。
  • AさんのPCには動作に必要なファイルが置いてある。BさんのPCには無い。(ソースを書いたのがAさんだから)

など、「動かなくて当たり前」な原因であることが殆どです。

前提条件となる環境が整備されていなくてはプログラムが動かないのは当たり前です。
しかし、その環境整備が結構面倒であるのも無視できない事実。
面倒だから後回しにしているうちに品質が低下していく、というのがJUnit開発の良くある風景です。

面倒な環境整備を簡単に済ませるのも技術力の一つ!!
というわけで、この連載では環境整備を簡単にするテクニックもテーマに掲げたいと思います。

しかし、環境整備と言っても条件は様々です。

  • データベースのレコードのセットアップ状態
  • 読み書きするファイルの状態
  • プログラムを実行するサーバの状態(読み取り権限、書き込み権限など)
  • 接続先サーバの状態(FTPサーバにファイルがある/無いなど)

色々あります。
それぞれ「スタブやモック」を使ったり「ダミーサーバ」を作ったりと色々なテクニックがあるのですが、今回からしばらくはこのうちの一つ、「データベースのセットアップ」並びに「登録後のレコードの照合」の作業を効率化する支援ツール「DbUnit」についてご紹介しようと思います。

プロジェクト構築

最初はプロジェクト構築からです。
以下の単位でソースフォルダを切ります。

ソースフォルダ役割
メインソーステスト対象となるメインのJavaソースを置く場所
メインリソースプロパティファイルなど、ソースから読み込みに使用する定義ファイルなどを置く場所
テストソースJUnitのソースを置き場所
テストリソースJUnitで使用する定義ファイルを置く場所


ここで早くも重要点ですが、プロジェクト開始時にこの粒度でソースフォルダを切って下さい。
もちろん、ソースフォルダなどどんな構成でもモジュールは動くわけですが、
プログラムの見通しを良くするという観点では、この粒度での切り分けが鉄板だと言い切ってしまって良いと思います。

ライブラリインポート


次にライブラリのインポートです。

以前の記事でご紹介したように、まずはJUnitの最新ライブラリを公式サイトよりダウンロードしてきます。
(Eclipseの自動機能でインポートすると、古いJUnitが入ってきたり、hamcrestが入ってなかったりするので、手動でインポートすることを忘れないで下さい)


次にDbUnitのライブラリをインポートします。
DbUnitは名前のとおり、JUnitを拡張したライブラリですので、JUnitとセットで使うことになります。
DbUnitの公式サイトからjarファイルをダウンロードし、インポートして下さい。


これにてライブラリのセットアップは完了、と思いきや、ここで罠があります。
このまま先に進んでしまうと、後で「java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory」とかエラーが出てきてしまいますのでご注意を。
内部で「slf4j」を使っているようですので、これもダウンロードしてインポートしましょう。



中には沢山のjarファイルが入っていますが、以下をインポートすればOKです。


  • slf4j-api-1.7.5.jar
  • slf4j-nop-1.7.5.jar


これにてjarファイルの準備完了。
最終的に、以下のようにjarファイルが揃っていればOKです。


DB準備

データベースのテストですので、当然ながらデータベース、テーブル、カラムが定義されており、そのテーブルに対し登録、更新、削除、参照の機能が作成済みの状態でなければ開始出来ません。

この記事では以下の環境でご紹介します。


  • PostgreSQL 9.1
  • SaStrutsやHibernateといったライブラリは使用せず、postgres標準ドライバのみで機能を作成済み


テーブルとしては簡単な「商品テーブル」を想定して以下を定義します。


物理名論理名
idIDserial(主キー)
shohin_name商品名text
price価格integer
create_at登録日時timestamp without time zone
update_at更新日時timestamp without time zone


  • IDは自動インクリメントするパラメータです。
  • 商品名と価格は特記事項の無し。
  • 登録日時、更新日時は、それぞれ「登録」「更新」を行った時に、その時の時間が入るという想定です。

終わりに

これにて準備は整いました。
実際にDbUnitを使ってのご紹介は次回からスタートします。

2013年7月14日日曜日

JavaScriptでstatic風なutilクラスを作成

お久しぶりです。
株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

業務多忙により更新が滞ってしまいましたが、また復活して更新を続けていきたいと思います。

前回から間が空いてしまいましたので、今回は単発の小ネタをご紹介しようと思います。
対象言語はJavaScriptです。

実は我々技術開発事業部では、もうすぐ社内企画としてとあるWebシステムを作ろうとしています。
Webシステムで必ず付いてくる機能と言えば、入力チェックです。

  • 必須項目が空欄だった場合、「○○を入力して下さい」とアラートを出す。
  • 半角数値項目に数値以外が入力されていた場合、「○○には半角数値を入力して下さい」とアラートを出す。
  • 日付項目に存在しない日付が入力されていた場合、「○○に正しい日付を入力して下さい」とアラートを出す。

よくある機能です。
こういった入力チェックはシステム全体で使い回しすることが多く、大抵の場合は「common.js」のような共通ファイルを作って、その中に以下のようなファンクションを作ったりします。

function isEmpty(str){
 return (str == null || str == "");
}

この例は「文字列の空白チェック」です。
他にも「isHankakuNum:半角数値チェック」「isDate:日付チェック」など入力チェックfunctionをズラッと作っていくことでライブラリ化するわけです。

しかし、こうやって単純にfunctionをバンバン作っていってしまいますと、名前空間が汚れてしまいます。
さらに言えば、そのfunctionがcommon.jsから呼び出したものなのか、画面個別に書いたものなのか、パッと見て分かりません。

つまるところ、機能としては出来ているのかもしれないが、イマイチ洗練されていないソースという状態なわけです。

そこで、もうちょっとスマートなやり方をご紹介しましょう。

まず、例としてJavaのケースを挙げてみます。
Javaでは「StringUtils」というライブラリが普及しておりまして、「StringUtils.isEmpry("aaa")」と記述してパラメータの空白チェックを行います。
「StringUtils」というクラスにパッケージされているのでメソッド名が被ったりしないですし、そもそも何のライブラリを呼び出しているのかも一目瞭然です。

これはStringUtilsクラスのisEmptyというメソッドがstaticなメソッドだから、このような記述方法が出来るわけです。
これと同じように、「JavaScriptでもstaticなメソッドを作ればスッキリする」というのが今回の記事の趣旨でございます。

JavaScriptの場合、以下のように書きます。

var StringUtils = new function(){
 this.isEmpty = function(str) {
  return (str==null || str=="");
 };
}

JavaScriptでは関数も変数の一つに過ぎません。
functionを「new」して作成し、StringUtilsという変数に代入することが出来ます。
(JavaScriptに「new」とかあるの?みたいな話も偶に聞きますが、普通にあります。)

これで「なんちゃってstaticメソッド」の完成です。
まあ、JavaScriptにstaticという概念はありませんので、プログラマーの使い勝手がstaticっぽいというだけですが。。。

これを共通JSファイルに書いておけば、後は「StringUtils.isEmpry("aaa")」と書いて空白チェックが行えるという寸法です。
ファイル名は「StringUtils.js」として保存しておけば、どこから呼び出されてきているのかも一目瞭然。
JavaScriptの可読性が少し上がりそうですね。

追伸

さらに小技ですが、型を意識するJavaならStringUtilsという名前のクラスで良いですけれども、型判定が大雑把なJavaScriptではそんなことをしても無駄に複雑化するだけ、という考え方もあります。

文字列チェック用にStringUtils、数値チェック用にIntegerUtils、日付チェック用にDateUtils、なんて細かく分けても面倒なだけです。

というわけで、私は「InputCheckUtils」として『入力チェック用』という機能単位で一括りにすることが多いです。
Javaのようにガチガチにせず、これくらいの緩い制約でクラス設計するのが開発しやすいのではないかと思います。

文字通り、型に囚われない自由自在なコーディングテクニックです。

2013年6月17日月曜日

パラメータテスト TheoriesとFixture(後編)

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

前回からJUnitのパラメータ網羅テストを行うのに便利な「Theoriesアサーション」についてご紹介しています。

しかしこの「Theoriesアサーション」、前回に記載した通り、これ単品では余り便利ではありません。
一連のJUnit記事で紹介してきた「Encrosed」「カスタムMacher」、そして標準のJavaテクニック、これらを複合させて使い、初めてその真価を発揮するものなのです。

では、今回は「商品テーブルに対し、商品名の前方一致検索を行う」という、
どんなWebシステムにでも応用が利きそうなシチュエーションで行ってみましょう。

このメソッドに対し、テストパターンは以下です。

  • 商品名が完全一致するような文字列で検索して、一件の結果を取得する。
  • 商品名が前方一致するような文字列で検索して、二件の結果を取得する。
  • 商品名が後方一致するような文字列で検索して、結果を取得出来ない。

もちろん件数だけ数えるテストではありません。
きっちり「商品名」「商品コード」「値段」など、全フィールドが正しいことを確認します。

となれば、
「え? これってもう、普通にテストソースを3コ作るしか無いんじゃない?」
と思う方もいらっしゃるかもしれませんが、そんなことはありません。

今までご紹介してきたスキルを総動員すればスッキリと解決出来るのです。

@RunWith(Enclosed.class)
public class ShohinServiceTest {

 @RunWith(Theories.class)
 public static class 商品名前方一致検索パラメータテスト {

  @DataPoint
  public static ShohinFixture 検索結果無し = new ShohinFixture("無し無し",
    new ShohinSearchExpected() {
     public List<Shohin> getExpected() {
      return null;
     }
    });

  @DataPoint
  public static ShohinFixture 検索結果一件 = new ShohinFixture("ジェニシス石鹸",
    new ShohinSearchExpected() {
     public List<Shohin> getExpected() {
      List<Shohin> list = new ArrayList<Shohin>();
      Shohin s1 = new Shohin();
      s1.setShohinCode(1);
      s1.setShohinName("ジェニシス石鹸");
      s1.setShohinPrice(100);
      list.add(s1);
      return list;
     }
    });

  @DataPoint
  public static ShohinFixture 検索結果二件 = new ShohinFixture("ジェニ",
    new ShohinSearchExpected() {
     public List<Shohin> getExpected() {
      List<Shohin> list = new ArrayList<Shohin>();
      Shohin s1 = new Shohin();
      s1.setShohinCode(1);
      s1.setShohinName("ジェニシス石鹸");
      s1.setShohinPrice(100);
      list.add(s1);
      Shohin s2 = new Shohin();
      s2.setShohinCode(2);
      s2.setShohinName("ジェニシスシャンプー");
      s2.setShohinPrice(200);
      list.add(s2);
      return list;
     }
    });

  /**
   * 商品Fixture
   *
   */
  private static class ShohinFixture {

   /** 検索条件の商品名 */
   public String shohinName;

   /** 検索結果 */
   public ShohinSearchExpected expected;

   public ShohinFixture(String shohinName,
     ShohinSearchExpected expected) {
    this.shohinName = shohinName;
    this.expected = expected;
   }

  }

  /**
   * 商品検索結果インターフェース
   *
   */
  private interface ShohinSearchExpected {
   public List<Shohin> getExpected();
  }

  @Theory
  public void パラメータテスト(ShohinFixture fixture) {

   ShohinService shohinService = new ShohinService();
   List<Shohin> actual = shohinService.findByShohinName(fixture.shohinName);
   List<Shohin> expected = fixture.expected.getExpected();

   if(expected == null){
    assertThat(actual, is(nullValue()));
   }else{
    assertThat(actual, is(new ShohinListMacher(expected)));
   }

  }

 }

}

これはちょっと強烈なので、分解してご説明しましょう。

テストメソッド本体

まずこのテストコードはテスト本体が一つしかありません。

@Theory
public void パラメータテスト(ShohinFixture fixture) {

 ShohinService shohinService = new ShohinService();
 List<Shohin> actual = shohinService.findByShohinName(fixture.shohinName);
 List<Shohin> expected = fixture.expected.getExpected();

 if(expected == null){
  assertThat(actual, is(nullValue()));
 }else{
  assertThat(actual, is(new ShohinListMacher(expected)));
 }

}

3パターンあるテストもこれ一発で全部対応しています。
ソースの共通化や再利用は基本中の基本。これが「Theoryアサーション」のパワーです。
(なお、「標準マッチャーのnullValue()」と「カスタムマッチャー」も使っています。
これらについて忘れた方は昔の記事をご参照下さい。)
ところでこのメソッド、引数で「ShohinFixture」を受け取っていますね。
これが今回紹介するテクニックの中核です。

フィクスチャ

ここからがポイントです。
「DataPointアサーション」を定義することでフィールドにパターンを定義出来ることは前回の記事の通りです。
しかし、前回は「検索条件はString型一コ、結果はtrueで固定」という単純なものでしたが、これでは今回のようなバリエーションに富んだテストにた対応出来ません。
こういう場合は、テストパラメータと予測結果をパッキングした専用のクラスを作るのです。
このような役割を持つクラスのことを「フィクスチャ」と呼びます。(別にJUnitの機能というわけではなく、普通のJavaクラスです。)

/**
 * 商品Fixture
 *
 */
private static class ShohinFixture {

 /** 検索条件の商品名 */
 public String shohinName;

 /** 検索結果 */
 public ShohinSearchExpected expected;

 public ShohinFixture(String shohinName,
   ShohinSearchExpected expected) {
  this.shohinName = shohinName;
  this.expected = expected;
 }

}

今回はブログ記事として一括表示する都合上、内部クラスとして定義しましたが、本番では別途「fixture」というパッケージを作って外出しにした方がいいでしょう。
似たようなテストパターンでソース共有が出来ますし、ソースの見通しも良くなります。

しかし、コンストラクタで渡している検索条件の「String shohinName」はいいとして、検索結果である「ShohinSearchExpected expected」とは一体何でしょう?
以下に続きます。

パターン定義と無名クラス

テストメソッド本体は書きました。
フィクスチャも作りました。
後は「DataPointアサーション」を使って、検索条件と予想結果を専念すればOKです。

@DataPoint
public static ShohinFixture 検索結果一件 = new ShohinFixture("ジェニシス石鹸",
  new ShohinSearchExpected() {
   public List<Shohin> getExpected() {
    List<Shohin> list = new ArrayList<Shohin>();
    Shohin s1 = new Shohin();
    s1.setShohinCode(1);
    s1.setShohinName("ジェニシス石鹸");
    s1.setShohinPrice(100);
    list.add(s1);
    return list;
   }
  });

「ん? な、何か変だぞ!?」
と思われた方もいるかもしれません。
先に定義したフィクスチャにコンストラクタで値を渡しています。
引数の1コ目は普通にString型ですが、2コ目は……????

実はこの「ShohinSearchExpected」というのはインターフェースでして、
Javaではこのようにインターフェースを実現するクラス本体の無い、インターフェース単品でインスタンスを作ることも出来るのです。
これを「無名クラス」と言います。

「無名クラス」は使い方を間違えるとソースの可読性が下がってしまうので要注意ですが、
このテストケースのように、ローカルでちょっとクラスが欲しいケースには最適と言えるでしょう。

これによって、今回のテストパターンのように複数行に渡ってデータを定義しなければならないようなケースでも、フィールド一個の中に全て梱包出来るのです。

まとめ

如何でしたでしょう?
JUnitのパラメータ網羅テストは「似たようなソースのコピペを減らし、テストパターンの定義に専念出来る」かどうかが、品質を高める上でのキーとなります。

今回の記事は今までご紹介した機能を総動員したヘビーな内容でしたが、
最終的には「@DataPoint1コで1テストパターン。テスト本体は同一」という、テスト毎の独立性とソース共有を両立した理想的なテストソースを作ることが出来ました。

今回お見せした例は「検索条件がString一コ、結果がリスト」というものですが、それ以外のケースでも「検索条件が引数2つならフィクスチャのコンストラクタで渡す引数を増やす」「検索条件が複雑なら無名クラス用インターフェースを作る」など、応用で何とでもなります。プログラマーの腕の見せ所です。

今回のサンプルソースは「商品名検索3パターン」という小さなものであるため、わざわざフィクスチャやインターフェースを作らねばならない労力が大変という印象が先立ってしまうかもしれません。
しかし、このような高機能型JUnitは、プロジェクトの規模が大きくなる程に効果が大きくなります。
大規模ソース、大量テストパターンでキッチリこの書き方でJUnitを作れば、それはもう美しいの一言に尽きるソースになります。
ソースの再利用によって開発速度も上がって良いことばかりです。

おわりに

JUnitの機能は「Ignore」「Suite」「Exception取得」など他にもあるのですが、
単体開発者が押さえておかねばならない要点という意味では、ひとまずこんな所ではないかと思います。

そこで、ここから先はJUnit標準ではなく、その拡張プラグイン関連のご紹介にシフトしていこうと思います。

次回より新章開幕、「DBUnit編」です。

2013年6月10日月曜日

パラメータテスト TheoriesとFixture(前編)

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はテスト自動化シリーズと題しましてJUnit関連の紹介を連載中です。

さて、JUnitの開発では、「渡す引数が違うだけで同じメソッドを何度もテストする」というパラメータテストを実施することがあります。
例えば、「文字列の半角英数字チェック」のメソッドをテストしようとすれば、引数として渡すテスト対象文字列は以下のように色々なパターンが存在します。

  • 半角英字
  • 半角数字
  • 記号
  • 全角英字
  • 全角数字
  • ひらがな
  • 空白スペース
  • 以下続く……

これらのパターンに対してそれぞれテストを実行し、全て正常に期待した結果になるかどうかをチェックするJUnitソースを作成するというシチュエーションです。

では、まずはダメなパターンから行ってみましょう。

@Test
public void 半角英数字チェックのテスト() {
    assertThat(TheoriesSample.isHankaku("a"),is(true));
    assertThat(TheoriesSample.isHankaku("1"),is(true));
    assertThat(TheoriesSample.isHankaku(","),is(false));
    assertThat(TheoriesSample.isHankaku("A"),is(false));
    assertThat(TheoriesSample.isHankaku("あ"),is(false));
    assertThat(TheoriesSample.isHankaku("*"),is(false));
    assertThat(TheoriesSample.isHankaku(""),is(true));
    assertThat(TheoriesSample.isHankaku(null),is(true));
    assertThat(TheoriesSample.isHankaku(" "),is(true));
}

こういう書き方はJUnitの作法としてNGです。
この場合、一つのテストメソッドで複数パターンのテストを実施していることになってしまいます。

「一つのメソッドで一つのテストパターン」

これがJUnitの作法です。
みんなが正しく作法を守ることで可読性の高い良いソースが生まれるのです。
面倒でもテストパターン毎に別々のメソッドにして下さい。

その作法を守ったパターンが以下になります。

@RunWith(Enclosed.class)
public class TheoriesSampleTest {

    public static class 半角英数字チェックのテスト {

        @Test
        public void 半角英数字チェック_半角英字はtrue() {
            assertThat(TheoriesSample.isHankaku("a"),is(true));
        }

        @Test
        public void 半角英数字チェック_半角数字はtrue() {
            assertThat(TheoriesSample.isHankaku("1"),is(true));
        }

        @Test
        public void 半角英数字チェック_記号はfalse() {
            assertThat(TheoriesSample.isHankaku(","),is(true));
        }

        //以下続く
    }

}
これなら「一つのメソッドで一つのテストパターン」のルールを守れます。

前回の記事で紹介した「Enclosedアサーション」を使えば「半角英数字チェックのテスト」でグループ化出来るので、メソッドが増えてもソースの見通しは比較的良い状態を保てます。

とはいえ、いくら正しい書き方と言っても、これは面倒でしょう。
「引数が1コ違うだけで他は全部同じなんだから、引数以外は共通化したい」と思うのがプログラマー精神です。

そこでご紹介するのが、今回記事のテーマ「Theoriesアサーション」です。
この機能は、上記のように「テストを実行するメソッド部分は同じだが、渡すパラメータだけは変えたい」という要望に対応するものです。

まずは以下にサンプルを記載します。

@RunWith(Enclosed.class)
public class TheoriesSampleTest {

    @RunWith(Theories.class)
    public static class 半角英数字チェックのテスト_true系 {

        @DataPoint
        public static String 半角英字 = "a";
        @DataPoint
        public static String 半角数字 = "1";
        @DataPoint
        public static String 空白 = "";
        //省略

        @Theory
        public void 半角英数字チェックパラメータテスト(String str) {
            assertThat(TheoriesSample.isHankaku(str),is(true));
        }

    }

    @RunWith(Theories.class)
    public static class 半角英数字チェックのテスト_false系 {

        @DataPoint
        public static String 記号 = ",";
        @DataPoint
        public static String 全角英字 = "A";
        @DataPoint
        public static String ひらがな = "あ";
        //省略

        @Theory
        public void 半角英数字チェックパラメータテスト(String str) {
            assertThat(TheoriesSample.isHankaku(str),is(false));
        }

    }
}

ここで新登場のアサーションは三つです。

  • @RunWith(Theories.class):このテストクラスがTheoryによる繰り返しであることを示す
  • @DataPoint:繰り返し時に渡すパラメータ
  • @Theory:テストメソッド本体

今まで引数として渡していたパラメータは「@DataPointアサーション」をつけたフィールド変数として定義します。
そして、そのクラスに「@RunWith(Theories.class)」を、テストメソッドに「@Theory」を付与することで、定義した全ての@DataPointがテストメソッドに繰り返し渡されるという構造です。

つまり、「@DataPoint」の定義だけをペタペタと増やしていけば、大量にあるテストパターンのパラメータも全て網羅出来るわけです。

この結果がエラーになった場合は、以下のように表示されます。


なるほど。
普通のテストケースだと「assertThat」の箇所が表示されますが、こちらの場合はエラーになったパラメータが表示されるわけです。

つまり、テストメソッドの中にassertThatが複数あった場合、その中のどのassertThatがエラーになったかは一目では分からないということになります。
このため、テストメソッド本体の中に記述するassertThatは一行一発で済ませるのが良いわけです。

このような場合には、過去に紹介した「カスタムMatcher」を作ることで解決して下さい。今まで紹介したJUnitの機能を組み合わせることで、スタイリッシュなJUnitソースを開発出来るのです。

次回予告

今回の記事ではTheoriesアサーションの簡単な例を紹介しました。
しかし、

  • 「@DataPointでテストメソッドに渡している引数がString型1コしか無いけど、同時に複数個渡したい場合はどうすればいいの?」
  • 「期待する結果がtrue,falseの2パターンだけならこれでいいけど、引数毎に違う場合はどうするんだ?」

という疑問が沸いた方もいらっしゃるかと思います。

そう、上記の例では、テストメソッドに渡す引数は1コ限定、「assertThat」の期待値部分もtrue/falseの固定値と、柔軟性が無いのです。
プロジェクトの都合に合わせて柔軟に対応するにはもう一息、JUnitの機能と言うより、書き方のテクニックが必要になります。
次回はその書き方のテクニック「Fixture」についてご説明します。

2013年6月3日月曜日

テストケースをグループ化 Enclosedアノテーション

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在は「テスト自動化シリーズ」と題してJUnitについて紹介中です。
 さて、今までの私の記事に「コードを綺麗に」「効率的に」「コピペしない」などの文言が随所に出てきていることで、薄々感づいていらっしゃるかもしれません。
JUnit開発経験がある方なら、多くの方が身に覚えがあるでしょう。

JUnit開発は、ソースを綺麗に書くことの戦いである。

JUnit開発は、それ自体は決して難しいものではありません。しかし煩雑な作業ではあるのです。
 しかもテスト系ですので、メインソースと比べて重要度が低く、品質管理が甘くなりがち。
この結果、JUnitソースはどうしても可読性の低いものになりやすいのです。

今回の記事は、そんな問題に対して役に立つ便利機能をご紹介しましょう。
それが、テストソースのグループ化機能「@RunWith(Enclosed.class)」です。

実例紹介 @RunWith(Enclosed.class)

JUnitのテストケースの数が増えてくると、「テストケースを性質毎に分別して、グループ化出来ないかな?」と思うようになります。
どのように分類するかはプログラマーの裁量次第ですが、例としては以下です。

正常/異常によるグループ分け

  • 処理が最後まで正常完了するテスト
  • 処理が途中でエラーになるテスト

DBのセットアップによるグループ分け

  • DBが空の状態からのテスト
  • DBにレコードが1件だけある状態からのテスト
  • DBにレコードが大量に登録された状態からのテスト

一つのクラスにテストケースを全部並列にズラッと書いた場合、「あのテストケースはどこに書いたっけ?」と探そうと思っても、パッと見で似たようなソースばかりなので視線が滑り、目的のソースが見つからないということになりがちです。
しかし、こうしてグループ化することで見易く整理されます。
では、実例をお見せしましょう。

/**
 * @author 技術開発事業部 遠藤 太志郎
 *
 */
@RunWith(Enclosed.class)
public class EnclosedSampleTest {

 public static class テーブルが空の状態から始まるテスト {

  /**
   * テーブルを初期状態にセットアップする。
   */
  @Before
  public void setUp()  {
   //テーブルを空にする処理
  }

  @Test
  public void test_レコードの新規登録_最小() {
   //テストコードを書く
  }

  @Test
  public void test_レコードの新規登録_フル桁() {
   //テストコードを書く
  }

 }

 public static class テーブルにレコードが登録状態からのテスト {

  /**
   * テーブルを初期状態にセットアップする。
   */
  @Before
  public void setUp()  {
   //テーブルにレコードをセットアップする処理
  }

  @Test
  public void test_レコードの更新_主キーで更新() {
   //テストコードを書く
  }

  @Test
  public void test_レコードの更新_フラグが一致するものを更新() {
   //テストコードを書く
  }

 }

}

EnclosedSampleTestクラスに「@RunWith(Enclosed.class)」というアノテーションが付与されています。
このアノテーションが「EnclosedSampleTestはJUnitテストソース本体ではなく、複数のテストクラスを取りまとめ役のクラスである。」という目印となるわけです。
そして、テストソース本体は配下のローカルクラスが担当します。
EnclosedSampleTestクラスにペタペタと全テストケースを書いてしまうよりも分類分けがされたのでスッキリしましたね。

(余談ですが、Javaはクラス名、メソッド名、変数に日本語が使えます。本体ソースでは普通こんなことはしませんが、テストソースならば日本語で書いた方が分かりやすくなるのでオススメです)

さて、上記のサンプルソース。
実は単に見やすくなっただけではなくて、もう一つ、非常に重要な機能が隠されています。

それは、「@Before」が2回登場していることです。
このアノテーションは似たような機能がJUnit3の頃からある有名なものですので、特に説明するまでも無いでしょう。

  • @BeforeClass:テストクラスを実行する最初に一回だけ呼ばれる。
  • @Before:全てのテストメソッドを実行する前に毎回呼ばれる。
  • @After:全てのテストメソッドを実行した前に毎回呼ばれる。
  • @AfterClass:テストクラスを実行した最後に一回だけ呼ばれる。

この4種のアノテーションは、その性質上、1テストクラスにつき1つしか書くことが出来ません。

このため、「こっちのテストはテーブルが空の状態からスタートしたい」「こっちのテストはテーブルにレコードを入れた状態からスタートしたい」と、初期セットアップ条件が複数存在するテストクラスの場合、@Beforeメソッド1つではセットアップに対応出来ないのです。

この結果、「@Beforeの機能は使わず、全てのテストメソッドの頭の部分にそれぞれ初期セットアップ処理をベタ書きする」という実に愚直なやり方に走るプロジェクトが世の中に存在してしまうのです。

しかし、そんな愚直なやり方をしてしまうと、セットアップ処理とテスト本体が同じメソッドの中に書かれていることになりますので、可読性が下がってしまいます。

セットアップ処理とテスト本体を分離する。

これはJUnitソースの可読性を高める為の基本中の基本です。
この要望に対応出来る便利な機能、それが「Enclosedアノテーション」なのです。

終わりに

今回紹介した「@RunWith(Enclosed.class)」は、そんな派手な機能ではなく、テストソースを綺麗にするために便利なテクニックといった所でしょう。

次回も再び、テストケースをスッキリするための便利なテクニックを紹介したいと思います。