現在はモックツール「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メソッドのモック化」についてご紹介します。
0 件のコメント:
コメントを投稿