広告 |
---|
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 2010年11月21日 Java総合講座 - 初心者から達人へのパスポート vol.208 セルゲイ・ランダウ バックナンバー: http://www.flsi.co.jp/Java_text/ ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ ------------------------------------------------------- ・現在、このメールマガジンは以下の2部構成になっています。 [1] 当初からのコース:vol.xxx(xxxは番号)が振られています。 これは現在、中級レベルになっています。 [2] 2009年11月開講コース:xxx号(xxxは番号)が振られています。 これは現在、初心者向けのレベルになっています。 ・このメールマガジンは、画面を最大化して見てください。 小さな画面で見ていると、不適切な位置で行が切れてしまう など、問題を起すことがあります。 ・このメールマガジンに掲載されているソース・コード及び 文章は特に断らない限り、すべて筆者が著作権を所有してい ます。また、これらのソース・コードは学習用のためだけに 提供しているものです。 ------------------------------------------------------- ======================================================== ◆ 01.Strutsのアプリケーション開発(プロジェクト:StrutsShop) ======================================================== さて、テストには正常な動きを確認するものだけでなく、何らかの トラブル(問題)があったときにプログラムが適切に対処するかどうか を確認するテスト(いわゆる異常系のテスト)というものもあります。 たとえば、RDBMS(StrutsShopのアプリケーションではMySQL)がダウン していた、あるいは、起動されていなかった場合を考えてみましょう。 この場合は、アプリケーションがデータベースにアクセスすることが できませんから正常な処理は行えません。 このような場合には普通、アプリケーションはユーザーに -------------------------------------------------------- まことに申し訳ございません。 現在システムがダウンしています。 数時間程度で回復する予定ですので、数時間のちに再度 初めから操作をやり直してみて下さい。 あるいは、お急ぎの場合はTel:123-456-7890までお問合せ下さい。 -------------------------------------------------------- といった類のメッセージを返すとともに、ログに状況を記録して、オペ レーターに異常を通知(たとえば警告音を鳴らし、オペレーター画面に 異常のメッセージを表示する等)します。 また、通知を受けたオペレーターは直ちにログを見て問題箇所を洗い出し 対処します。 では、この異常系のテスト・メソッドを書いてみましょう。 MySQLが起動していなかったためにエラーが発生したときには、 forwardの値が"systemerror"になり、systemerror.jspというWebページ に進むことを確認します。 (実際にはここらへんの仕組みはまだ実装(コーディング)していませんが、 実装し忘れに気づかず、実装が終わっているつもりになっているという 想定で話を進めます。) では異常系のテスト・メソッドをtestDbConnectionError()というメソッド名 で用意することにし、ItemListActionTest.javaを以下のように編集して みましょう。 -------------------------------------------------------- package jp.co.flsi.lecture.struts; import java.io.File; import java.util.Vector; import jp.co.flsi.lecture.struts.db.Item; import jp.co.flsi.lecture.struts.db.ItemDbManager; import jp.co.flsi.lecture.struts.db.StruShopDbException; import servletunit.struts.MockStrutsTestCase; public class ItemListActionTest extends MockStrutsTestCase { public void setUp() throws Exception { super.setUp(); File contextDir = new File("D:\\JavaWorkSpace\\StrutsShop\\WebContent"); setContextDirectory(contextDir); } public void tearDown() throws Exception { super.tearDown(); } /* public void testExecuteActionMappingActionFormHttpServletRequestHttpServletResponse() throws StruShopDbException { setRequestPathInfo("/itemlist"); addRequestParameter("keyword", "製品001"); addRequestParameter("categoryNum", "00001"); actionPerform(); verifyForward("success"); verifyForwardPath("/itemList.jsp"); ItemDbManager itemDbManager = new ItemDbManager(); itemDbManager.connect(); Vector<Item> itemList = itemDbManager.getDataByItemNameAndCatNum("製品001", "00001"); Vector<Item> itemList2 = (Vector<Item>) getRequest().getAttribute("itemList"); assertEquals(itemList.get(0).getNum(), itemList2.get(0).getNum()); itemDbManager.disconnect(); verifyNoActionErrors(); } // 前回(vol.207)最後の自由課題に対する回答例 public void testExecuteActionMappingActionFormHttpServletRequestHttpServletResponse2() throws StruShopDbException { setRequestPathInfo("/itemlist"); addRequestParameter("keyword", "製品100"); addRequestParameter("categoryNum", "00001"); actionPerform(); verifyForward("success"); verifyForwardPath("/itemList.jsp"); ItemDbManager itemDbManager = new ItemDbManager(); itemDbManager.connect(); Vector<Item> itemList = itemDbManager.getDataByItemNameAndCatNum("製品100", "00001"); Vector<Item> itemList2 = (Vector<Item>) getRequest().getAttribute("itemList"); assertEquals(itemList.size(), 0); assertEquals(itemList2.size(), 0); itemDbManager.disconnect(); verifyNoActionErrors(); } */ /** * RDBMS(MySQL)がダウンしている(起動されていない)場合の検証 * (このテストの実行前にはRDBMS(MySQL)を停止しておくこと) */ public void testDbConnectionError() throws StruShopDbException { setRequestPathInfo("/itemlist"); addRequestParameter("keyword", "製品001"); addRequestParameter("categoryNum", "00001"); actionPerform(); verifyForward("systemerror"); verifyForwardPath("/systemerror.jsp"); verifyNoActionErrors(); } } -------------------------------------------------------- 編集&保管が終わったら、MySQLを停止してからこのテスト・メソッドを実行 してみることにしましょう。 MySQLを停止するには (1) (Windows XPの場合) 「スタート」ボタン→「コントロールパネル」を選択し、 「コントロールパネル」において、「パフォーマンスとメンテナンス」→「管理ツール」 を選択し、「サービス」をダブル・クリックして開きます。 (2) 「サービス」ウインドウの右側のリストの中で MySQL を探して選択(クリック)し、(現在「開始」の状態になっているはずなので) 左側の「サービスの停止」というところをクリックします。 (逆に停止後に再度起動する場合は「サービスの開始」をクリックすることになります。) これでMySQLが停止しましたので、JUnitテストを実行してみましょう。 プロジェクト・エクスプローラーの中の「StrutsShop」(プロジェクト)配下の 「Javaリソース: src」配下のjp.co.flsi.lecture.struts(パッケージ)配下の ItemListActionTest.javaを右クリックし、「実行」→「JUnitテスト」を 選択します。 すると、「JUnit」タブ配下のテスト結果を見てみると、テスト結果は 失敗だったことがわかりますね。 そこには、 junit.framework.AssertionFailedError: Cannot find forward 'systemerror' - it is possible that it is not mapped correctly. というメッセージが表示されていますね。 つまり、forwardの値"systemerror"に対するマッピングの指定が正しくない 可能性があります。 これはテスト・メソッドの中の verifyForward("systemerror"); という行を実行した結果、検証結果が失敗だったことを示すエラーです。 実は、RDBMSのダウンなどの例外発生に対する対応はまだstruts-config.xmlファイル にも記述していないし、コーディングもしていなかったんですね。 という訳で、例外発生時にはforwardの値を"systemerror"にし、systemerror.jsp のWebページに進むなどということは、struts-config.xmlファイルにも書いて いないし、それに対応したコーディングもしていなかったんです。 (実際、単体テストの段階で、コーディングをし忘れていた箇所に気づくことは 時々あることで、Eclipseではコーディングし忘れを防ぐための機能も用意 されています。これを後ほどお話します。) では、ログを見てみましょう。 「コンソール」タブ配下のログを見てみると、 java.net.ConnectException MESSAGE: Connection refused: connect というメッセージがあって、(RDBMSへの)接続(connect)に失敗していることが わかります。 さらに、ずっと見ていくと at jp.co.flsi.lecture.struts.db.DbManager.connect(DbManager.java:25) という行があって、その中のDbManager.java:25のところをクリックすると、 DbManager.javaの25行目、すなわち conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/STRUSHOP", "root", "rootpass"); という行に跳びますから、ここでDriverManagerのgetConnection()メソッドで (JDBCドライバーを使って)データベースに接続しようとしたときにエラーになった ことがわかります。 今回のテストはRDBMS(MySQL)を停止して行っているので、ここらへんのエラーの発生 は意図した通りです。 ところが、ログの一番最後のほうを見ると jp.co.flsi.lecture.struts.ItemListAction.execute: Method return: <<<<< success というメッセージがあることに気づきます。 これは、ItemListActionのexecute()メソッドの実行においてforwardの値を"success" にしてメソッドを終了するときにログに書かせるようにコーディングしておいたもの ですね。 つまり、RDBMSへの接続に失敗しているにもかかわらず、forwardの値を"success"に しているわけで、forwardの値の設定に問題があることがわかります。 実際は、問題があるというよりも、例外発生時に対処するためのコーディングを し忘れていたということなのですが、現実の開発現場ではコーディングする量が とても多いために、ときどきコーディングのし忘れが発生することがあります。 多量の設計の仕様書を見ながら(オブジェクト指向方法論やシステムの分析・設計手法 については、ずっとのちに詳しくお話することにし、ここでは設計の話は省略します) 「これもコーディングしておかなくては。あれもコーディングしておかなくては。」 と忙しく考えているうちに、後回しにした部分を忘れてしまったりするのです。 そこで、後回しにした部分など、あとでコーディングしなければならない所には、 TODOというタグ(tag=名札,荷札)を付けたコメントを入れておくようにします。 たとえば、 // TODO Exceptionが投げられたときの処理をここに書く というふうにメモ書き程度にコメントを入れておきます。 (TODOというのは「やるべきこと(to do)」という意味です。) このTODOというコメントを入れておくと、Eclipseがその行の先頭にチェック・マーク をつけて、目立たさせてくれるのです。また、右端のスクロール・バーの右側に 水色のマークを表示することによってTODOが存在する場所を示してくれます。 (これまでにEclipseが自動生成したソース・コードの中にTODOというコメントが 自動的に組み込まれていることで、すでに気づいている読者もいることと思います。) そして、一通りコーディングが終わったと思ったときには、 メニュー・バーの「ウインドウ」→「ビューの表示」→「その他」を選択し、 表示された「ビューの表示」ウインドウの中で「一般」の配下の「タスク」を 選択し、「OK」ボタンをクリックします。 すると、画面の下のほうに「タスク」タブが表示されるので、それをクリック することによって「タスク」ビューを開きます。 すると、そこにはTODOがリストされているはずです。 そのどれかの行をクリックすると該当するソース・コードの箇所が表示され、 TODOのコメント部分が反転するので直ぐに該当箇所がわかります。 こうして、TODOの残っている箇所を探しては未実装の部分をコーディング していき、コーディングが終わった箇所はTODOのコメントを削除します。 最終的に「タスク」ビューの中にTODOが無くなったら、完璧に実装が終わった ということになります。 ┌補足─────────────────────────┐ TODO以外にもタグ(名札)を付けることができます。 メニュー・バーの「ウインドウ」→「設定」を選択し、 「Java」配下の「コンパイラー」配下の「タスク・タグ」 を選択してみて下さい。 すると、新規にタグを設定することもできることがわか るでしょう。 また、すでにTODO以外にFIXMEとXXXというタグが用意されて いることもわかりますね。 ちなみにFIXMEというのは優先度が高になっていますが、 このタグを使うと「タスク」ビューの中でどのように 表示されるか、実際に自分で試して確認してみて下さい。 また、(デフォルト)というのはソース・コードが自動生成 されるときに自動で組み込まれるタグを意味します。 └───────────────────────────┘ では、実際にこの例外に対する処理(handling)のコーディングを行って いきましょう。 ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ (次回に続く) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ★ホームページ: http://www.flsi.co.jp/Java_text/ ★このメールマガジンは 「まぐまぐ(http://www.mag2.com)」 を利用して発行しています。 ★バックナンバーは http://www.flsi.co.jp/Java_text/ にあります。 ★このメールマガジンの登録/解除は下記Webページでできます。 http://www.mag2.com/m/0000193915.html ★このメールマガジンへの質問は下記Webページにて受け付けて います。わからない所がありましたら、どしどしと質問をお寄 せください。 http://www.flsi.co.jp/Java_text/ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Copyright (C) 2010 Future Lifestyle Inc. 不許無断複製 |