Testing

From JCFWiKi

Jump to: navigation, search

그림:check.gif

  • 산출물 : JCF3.0™ 단위테스트 및 통합테스트 적용 가이드
  • 작성자: 서경진
  • 작성일 : 2008/02/20
  • 버전 : 0.9
  • 개정이력 :

Copyright © 2007 Daewoo Information Systems Co., Ltd.

목차

[편집] Spring-Mock 기반 단위테스트

[편집] Spring-Mock 기반 컴포넌트 연계테스트

[편집] Spring-Mock 기반 Struts2 통합테스트

  • Spring-Mock 기반 Struts2 통합테스트 구조

Struts2에서는 일반적으로 action이 호출되기 전과 후에 다양한 interceptor가 호출된다. 일반적으로 Interceptor에 대한 설정은 struts.xml을 통해 이루어진다. 여기에서는 action을 호출하는 세 가지 다른 방법에 대하여 설명하고, 각 방법에 대한 예제 소스 코드를 제공하도록 하겠다.


  1. Request Parameter와 struts2, Interceptor 모두 사용 - MockHttpServletRequest의 addParameter 메소드를 활용하여 Action에 Parameter를 전달하고 전달된 Parameter가 Domain 객체에 매핑되어 테스트를 수행하는 방식, struts.xml에 설정된 모든 interceptor가 적용되기 때문에 interceptor가 수행하는 기능에 대한 검증도 가능하다.
  2. Struts2, Interceptor만 모두 사용 - Parameter를 사용하지 않고 Domain 객체에 값을 직접 입력하여 테스트를 수행하는 방식, MockHttpServletRequest와 MockHttpServletResponse를 사용하지 않고 Action에 선언된 Domain 객체의 setter 메소드를 사용하여 값을 입력하고 Base 클래스에 선언된 proxy를 통해 Action을 실행시켜서 테스트를 수행한다. 이 방법도 struts.xml에 설정된 모든 interceptor가 적용되기 때문에 interceptor가 수행하는 기능에 대한 검증도 가능하다.
  3. Struts2, Interceptor 모두 사용하지 않음 - Action에 선언된 Domain 객체에 값을 직접 입력하고 Action을 통해 테스트하고자 하는 메소드를 직접 호출하여 테스트를 수행한다. 이 테스트 방법은 struts2와 관련되 클래스와 interceptor를 모두 간과하고 Action, Service, DAO에 대한 연계와 데이터 무결성을 검증할 수 있다.
  • 통합테스트를 위한 BaseStrutsTestCase 클래스

어떤 시나리오를 가지고 테스트를 수행하느냐에 달려있지만, 위에서 언급한 세 가지 구조 중에 하나를 선택하여 시나리오를 구성해야 한다. 우선 Struts2 기반으로 모든 Action 클래스에 대한 통합테스트를 구성하기 위해서는 Struts2 환경을 구성할 수 있는 Base 클래스를 구현하는 것이 중요하다. 다음 소스 코드는 Java 1.4 이하에서 사용이 가능한 Base 클래스(BaseStrutsTestCase)이다.

Java 1.4 이하인 경우

package jcf.action;
 
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;
import junit.framework.TestCase;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.views.JspSupportServlet;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ContextLoader;
 
import java.util.HashMap;
 
/**
 * @author kyong94
 */
public abstract class BaseStrutsTestCase14 extends TestCase {
    //spring의 applicationContext 로딩을 위한 xml 파일의 경로를 설정한다.
    private static final String CONFIG_LOCATIONS = "META-INF/applicationContext-app.xml," +
                "META-INF/applicationContext-security.xml";
    private static ApplicationContext applicationContext;
    private Dispatcher dispatcher;
    protected ActionProxy proxy;
    protected static MockServletContext servletContext;
    protected static MockServletConfig servletConfig;
    protected MockHttpServletRequest request;
    protected MockHttpServletResponse response;
 
    public BaseStrutsTestCase14(String name) {
        super(name);
    }
 
    /**
     * Created action class based on namespace and name
     * @param clazz Class for which to create Action
     * @param namespace Namespace of action
     * @param name Action name
     * @return Action class
     * @throws Exception Catch-all exception
     */
    
    protected Object createAction(Class clazz, String namespace, String name)
            throws Exception {
 
        // create a proxy class which is just a wrapper around the action call.
        // The proxy is created by checking the namespace and name against the
        // struts.xml configuration
        proxy = dispatcher.getContainer().getInstance(ActionProxyFactory.class).
                createActionProxy(
                namespace, name, null, true, false);
 
        // by default, don't pass in any request parameters
        proxy.getInvocation().getInvocationContext().
                setParameters(new HashMap());
 
        // do not execute the result after executing the action
        proxy.setExecuteResult(true);
 
        // set the actions context to the one which the proxy is using
        ServletActionContext.setContext(
                proxy.getInvocation().getInvocationContext());
        request = new MockHttpServletRequest();
        response = new MockHttpServletResponse();
        ServletActionContext.setRequest(request);
        ServletActionContext.setResponse(response);
        ServletActionContext.setServletContext(servletContext);
        return proxy.getAction();
    }
 
    protected void setUp() throws Exception {
        if( applicationContext == null ) {
            // this is the first time so initialize Spring context
            servletContext = new MockServletContext();
            servletContext.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
                    CONFIG_LOCATIONS);
            applicationContext = (new ContextLoader()).initWebApplicationContext(servletContext);
 
            // Struts JSP support servlet (for Freemarker)
            new JspSupportServlet().init(new MockServletConfig(servletContext));
        }
        // Dispatcher is the guy that actually handles all requests.  Pass in
        // an empty. Map as the parameters but if you want to change stuff like
        // what config files to read, you need to specify them here
        // (see Dispatcher's source code)
        dispatcher = new Dispatcher(servletContext,
                new HashMap<String, String>());
        dispatcher.init();
        Dispatcher.setInstance(dispatcher);
    }
}

다음 소스 코드는 Java 1.5 이상에서 사용이 가능한 Base 클래스(BaseStrutsTestCase)이다.

Java 1.5 이상인 경우

package jcf.action;
 
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;
import junit.framework.TestCase;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.views.JspSupportServlet;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ContextLoader;
 
import java.util.HashMap;
 
/**
 * @author kyong94
 */
public abstract class BaseStrutsTestCase extends TestCase {
    private static final String CONFIG_LOCATIONS = "META-INF/applicationContext-app.xml," +
                "META-INF/applicationContext-security.xml";
    private static ApplicationContext applicationContext;
    private Dispatcher dispatcher;
    protected ActionProxy proxy;
    protected static MockServletContext servletContext;
    protected static MockServletConfig servletConfig;
    protected MockHttpServletRequest request;
    protected MockHttpServletResponse response;
 
    public BaseStrutsTestCase(String name) {
        super(name);
    }
 
    /**
     * Created action class based on namespace and name
     * @param clazz Class for which to create Action
     * @param namespace Namespace of action
     * @param name Action name
     * @return Action class
     * @throws Exception Catch-all exception
     */
    @SuppressWarnings("unchecked")
    protected <T> T createAction(Class<T> clazz, String namespace, String name)
            throws Exception {
 
        // create a proxy class which is just a wrapper around the action call.
        // The proxy is created by checking the namespace and name against the
        // struts.xml configuration
        proxy = dispatcher.getContainer().getInstance(ActionProxyFactory.class).
                createActionProxy(
                namespace, name, null, true, false);
 
        // by default, don't pass in any request parameters
        proxy.getInvocation().getInvocationContext().
                setParameters(new HashMap());
 
        // do not execute the result after executing the action
        proxy.setExecuteResult(true);
 
        // set the actions context to the one which the proxy is using
        ServletActionContext.setContext(
                proxy.getInvocation().getInvocationContext());
        request = new MockHttpServletRequest();
        response = new MockHttpServletResponse();
        ServletActionContext.setRequest(request);
        ServletActionContext.setResponse(response);
        ServletActionContext.setServletContext(servletContext);
        return (T) proxy.getAction();
    }
 
    protected void setUp() throws Exception {
        if( applicationContext == null ) {
            // this is the first time so initialize Spring context
            servletContext = new MockServletContext();
            servletContext.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
                    CONFIG_LOCATIONS);
            applicationContext = (new ContextLoader()).initWebApplicationContext(servletContext);
 
            // Struts JSP support servlet (for Freemarker)
            new JspSupportServlet().init(new MockServletConfig(servletContext));
        }
        // Dispatcher is the guy that actually handles all requests.  Pass in
        // an empty. Map as the parameters but if you want to change stuff like
        // what config files to read, you need to specify them here
        // (see Dispatcher's source code)
        dispatcher = new Dispatcher(servletContext,
                new HashMap<String, String>());
        dispatcher.init();
        Dispatcher.setInstance(dispatcher);
    }
}

우선 org.apache.struts2.StrutsTestCase가 아닌 junit.framework.TestCase를 상속받은 것도 주목할만한 점이다. StrutsTestCase를 상속받지 않는 이유는 생성되는 핵심 Dispatcher 객체에 충분한 유연성을 제공하지 못하기 때문이다. 위의 Base 클래스에 대하여 간단히 설명하면, BaseStrutsTestCase 클래스는 struts2-core-2.x.x.jar, xwork-2.x.x.jar 라이브러리에 포함된 Struts2의 ActionProxy, ActionProxyFactory, Dispatcher와 spring.jar, spring-mock.jar에 포함된 ApplicationContext, MockHttpServletRequest, MockHttpServletResponse, MockServletContext, MockServletConfig 클래스를 활용하여 createAction 메소드에서 Struts2 기반 Action과 Request, Response를 생성하고 setUp 메소드를 통해 통합테스트를 위한 환경을 구성한다.

  • BaseStrutsTestCase 기반 세 가지 시나리오
시나리오1. Request Parameter와 struts2, Interceptor 모두 사용
시나리오2. Struts2, Interceptor만 모두 사용
시나리오3. Struts2, Interceptor 모두 사용하지 않음
public class UserActionTest extends BaseStrutsTestCase {    
  
 /**  
  * 시나리오1
  * Invoke all interceptors and specify value of action class'  
  * domain objects through request parameters.  
  * @throws Exception Exception  
  */  
 public void testInterceptorsBySettingRequestParameters()   
                     throws Exception {   
  createAction(UserAction.class, "/test", "deleteUser");   
  request.addParameter("id", "123");   
  String result = proxy.execute();   
  assertEquals(result, "success");   
 } 
 
 /**  
  * 시나리오2
  * Invoke all interceptors and specify value of the action  
  * class' domain objects directly.  
  * @throws Exception Exception  
  */  
 public void testInterceptorsBySettingDomainObjects()   
         throws Exception {   
  UserAction action = createAction(UserAction.class,   
                "/test", "deleteUser");   
  action.setId(123);   
  String result = proxy.execute();   
  assertEquals(result, "success");   
 }   
  
 /**  
  * 시나리오3
  * Skip interceptors and specify value of action class'  
  * domain objects by setting them directly.  
  * @throws Exception Exception  
  */  
 public void testActionAndSkipInterceptors() throws Exception {   
  UserAction action = createAction(UserAction.class,   
                  "/test", "deleteUser");   
  action.setId(123);   
  String result = action.deleteUser();   
  assertEquals(result, "success");   
 }   
}
  • BaseStrutsTestCase 기반 통합테스트 시나리오 구성

Struts2의 ActionProxy 생성 시 struts.xml을 Configuration Manager가 참조하므로 테스트 대상이 되는 Action에 해당하는 설정을 struts.xml에 추가하여야 한다. 다음은 TestCase를 구성하기 위해 설정된 struts.xml 파일의 예제입니다.

테스트를 위한 Struts2 설정 파일: struts.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
 
<struts>
     ......
     <!-- file을 include하는 방식은 Configuation Manager가 인식하지 못한다.-->
     <include file="../config-action/struts-config-zzum.xml"/>
     ......
 
    <!-- 따라서 다음과 같이 테스트를 위해 필요한 struts-config-zzum.xml 파일의 설정내용을 struts.xml에 복사하여 다음과 같이 설정해야 한다.-->     
    <package name="ZZUMUM" extends="struts-default" namespace="/zz/um/um">
                ......
		<action name="findUser" class="mil.trn.deliis.web.action.zz.um.um.UserAction" method="findList">
			<result name = "F_DupCheckUserID">/zz/um/um/zzumum_010102_IF01.jsp</result>
			<result name = "F_UserList_zzumum_010201_IF01">/zz/um/um/zzumum_010201_IF01.jsp</result>
			<result name = "F_UserList_zzumum_010301_IF01">/zz/um/um/zzumum_010301_IF01.jsp</result>
			<result name = "GetMyInfo">/zz/um/um/zzumum_010401.jsp</result>
			<result name = "F_UserList_tramsr_010102_IF01">/tr/am/sr/tramsr_010102_IF01.jsp</result>
		</action>
		......
    </package>
</struts>

테스트 시나리오를 기반으로 구현할 UserActionTest 클래스 예제는 다음과 같이 구현할 수 있다.

package 패키지명;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import jcf.action.BaseStrutsTestCase;
 
public class UserActionTest extends BaseStrutsTestCase {
 
	public UserActionTest(String name) {
		super(name);
	}
 
	public void testFindList() throws Exception {
		//조회결과를 담을 list 객체를 생성
                List list = new ArrayList();
                //조회된 list에서 하나의 결과를 Map으로 받기 위한 객체를 생성
		Map map = new HashMap();
                //Action을 생성 
                //createAction("해당 Action 클래스명", "네임스페이스", "액션명")
		createAction(UserAction.class, "/mj/zz/um", "findUser");
                //Request 파라미터에 입력값을 설정
		request.addParameter("task", "F_GetMyInfo");
		request.addParameter("user_id", "sun");
                //Proxy를 실행하여 결과를 받음
		String result = proxy.execute();
		assertNotNull(result);
                //struts.xml에서 설정한 result name이 GetMyInfo인지 검증
		assertEquals(result, "GetMyInfo");
		list = (List) request.getAttribute("myInfo");
		assertNotNull(list);
		map = (Map) list.get(0);
		assertNotNull(map);
                //결과 list에서 추출한 첫 번째 값이 기대한 데이터와 일치하는지 검증
		assertEquals((String)map.get("USER_NM"), "김선환");
	}
}


[편집] 참조