2010年1月15日金曜日

Servlet/jMock

サーブレットのユニットテストのやり方。

次のようなサーブレトの doGet() が、JavaDoc の仕様どおりに動作する事を、JUnit で検証するにはどうするか。

public class HelloServlet extends HttpServlet {
   private static final long serialVersionUID = 1L;

   @EJB
    private HelloBean helloBean;

   /**
    * req から 取得した GET パラメータ user を、helloBean の 
    * sayHello()メソッドに渡し、その戻り値を res から取得した
    * PrintWriter に書き出す。
    */
   protected void doGet(
         HttpServletRequest req, 
         HttpServletResponse res) 
         throws ServletException, IOException {

      String greeting = helloBean.sayHello(req.getParameter("user"));
      response.getWriter().println(greeting);
   }
}


以下の事がポイントになる。
  • テスト対象外のコードまでカバレッジに含まれてしまわないように、doGet() 以外のコードパスにはなるべく触れない。
  • doGet の仕様には、入出力のみに着目したブラックボックス処理の仕様というより、むしろ、サーブレットと関連オブジェクト群との協調を記述しているという側面がある。
  • APサーバ上で動かす場合、helloBean には 実装クラス HelloBeanImpl がインジェクトされるが、HelloServlet から見えているのは I/F だけであり、実装クラスを意識する必要はないし、それを前提としたテストを書くべきでもない。
  • HttpServletRequest など他の関連するオブジェクトについても、I/F を介したオブジェクトの協調にのみ注意を集中したい。

これらを踏まえてテストコードを書くと以下のようになる。
package hello.servlet;

import hello.ejb.HelloBean;

import java.io.PrintWriter;
import java.lang.reflect.Field;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMock.class)
public class HelloServletTest {
   HelloServlet target;
   Mockery context = new JUnit4Mockery() {{
        setImposteriser(ClassImposteriser.INSTANCE);
   }};
   @Before
   public void setup() {
      try {
         target = new HelloServlet();
         Field f = HelloServlet.class.getDeclaredField("helloBean");
         f.setAccessible(true);
         f.set(target, helloBean);
      } catch (Exception ex) {
         throw new RuntimeException(ex);
      }
   }
   @Test
   public void testDoGet() throws Exception {
      final String USER = "ABC";
      final String GREETING = "DEF";
      HelloBean helloBean = context.mock(HelloBean.class);
      HttpServletRequest request = context.mock(HttpServletRequest.class);
      HttpServletRequest response = context.mock(HttpServletResponse.class);
      PrintWriter writer = context.mock(PrintWriter.class);
      context.checking(new Expectations(){{
         oneOf(request).getParameter("user");will(returnValue(USER));
         oneOf(helloBean).sayHello(USER);will(returnValue(GREETING));
         oneOf(response).getWriter();will(returnValue(writer));
         oneOf(writer).println(GREETING);
      }});
      target.doGet(request, response);
   }
}

以上、jMock の使い方の一例として Servlet を引き合いに出してみた。

昔、jMock とか無くて、ユニットテストの意味もよく分かってなかった頃は、データベースにテストレコードまで用意してからダミーのリクエストとレスポンスで doGet()を実行して、レスポンスのバッファに貯めた文字列を検証したりしてたっけ・・・。

testDoGet() をたった一発実行しただけで、驚くほどのカバレッジがでるけど正味のユニットテストの実態とはかけ離れてるみたいな・・・。あと DummyRequest みたいなクラスを毎度毎度書いていたり・・・

0 件のコメント:

コメントを投稿