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メソッドのモック化」についてご紹介します。

0 件のコメント:

コメントを投稿