株式会社ジェニシス 技術開発事業部の遠藤 太志郎(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について解析を行います。