背景
我们现在项目包含了很多的子项目,其中包含了单纯的war应用,它的特征是适用struts1.2作为其mvc组件,没有自己的数据库操作dao层和service层,它的所有数据操作都是通过调用服务层模块的ejb接口来实现的。前一阶段一直在构建持续集成的环境,对于这样的模块的测试一直没有做,因为没有找到好的方式来做,前几天看了一下cactus和strutstestcase,发现这正是我所需要的。其实我本来对单元测试没有什么意见,但是因为其繁琐性和单一性以及费时费力,所以我更喜欢集成测试,而cactus和strutstestcase恰巧有容器内集成测试的功能。
cactusstrutstestcase相当于在测试客户端和测试服务端架起的一座桥梁,它将客户端测试的运行委托给已部署在服务端的测试用例来执行,执行结果又通过其传导给测试客户端,因此测试代码在测试服务端和测试客户端基本上各自要保持一份对等的测试用例,这样就很好的解决了容器内测试。从其名字上可以看出,它很依赖cactus和struts,因此其中的很多细节都跟这两个组件有关联。
另外,cactus也可以容器内测试servlet,ejb等组件,cactusstrutstestcase只是对于cactus测试servlet的一个包装。
依赖
cactus 1.4
stutstestcase2.1.4
其他
其jar包的依赖,如下
图1 来自cactus站点
各自还要依赖strutstest-2.1.4.jar
其配置文件依赖如下图
图2 来自cactus站点
这些依赖的jar包和配置文件在strutstestcase的下载文件中都能找到,虽然cactus已经更新到了1.8版本,但是strutstestcase最新版本因为还在使用1.4版本,所以我也适用其1.4版本。
实现
一个action
public class LoginAction extends Action { public final static String FORWARD_SUCCESS = "success"; public final static String FORWARD_FAILURE = "failure"; public final static String FORWARD_ERROR = "error"; public ActionForward login(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception { LoginForm loginForm = (LoginForm) form; String msisdn = loginForm.getMsisdn(); String pwd = loginForm.getPwd(); ServiceFactory serviceFactory = ServiceFactory.getInstance(); TestPortalService selfCareService = serviceFactory.getSelfCareService(); SelfCareUser selfCareUser = selfCareService.login(msisdn, pwd); req.getSession().setAttribute("user", selfCareUser); return mapping.findForward(FORWARD_SUCCESS); } }
一个actionform
public class LoginForm extends ActionForm{ private String msisdn; private String pwd; public String getMsisdn() { return msisdn; } public void setMsisdn(String msisdn) { this.msisdn = msisdn; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } }
一个servicefactory
public class ServiceFactory { private static boolean local = true; private static ServiceFactory instance; private static InitialContext initialContext; private final static String factory_initial = "weblogic.jndi.WLInitialContextFactory"; private static Map homeMap = new HashMap(); private static Map servicesMap = new HashMap(); private static Logger logger = Logger.getLogger(ServiceFactory.class); public static ServiceFactory getInstance() { if (instance == null) { syncServiceFactory(); } return instance; } public synchronized static void syncServiceFactory() { if (instance == null) instance = new ServiceFactory(); } public InitialContext setupInitialContext() { if (initialContext == null) { syncInitialContext(); } return initialContext; } public synchronized void syncInitialContext() { if (initialContext == null) { if (local == true) { try { initialContext = new InitialContext(); } catch (NamingException e) { logger.error("serviceFacotry localEJB" + e.toString()); } } else { Hashtable serverEnv = new Hashtable(); serverEnv.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory"); serverEnv.put("java.naming.provider.url", "t3://16.158.48.31:8001"); try { initialContext = new InitialContext(serverEnv); } catch (NamingException e) { logger.error("serviceFacotry remoteEJB" + e.toString()); } } } } private Object getServices(String jndi, Class homeClass) { setupInitialContext(); Object service = servicesMap.get(jndi); try { if (service == null) { Object home = homeMap.get(jndi); if (home == null) { Object obj = initialContext.lookup(jndi); home = PortableRemoteObject.narrow(obj, homeClass); homeMap.put(jndi, home); } service = homeClass.getMethods()[0].invoke(home, null); servicesMap.put(jndi, service); } } catch (Exception e) { logger.error("Can't get remote selfcare service!" + e.toString()); // throw new Exception("Can't get remote selfcare service!", e); } return service; } public TestPortalService getSelfCareService() { return (TestPortalService) getServices("ejb/TestPortalService", TestPortalServiceHome.class); } }
一个struts-config。xml
<struts-config> <!-- ========== Form Bean Definitions =================================== --> <form-beans> <form-bean name="loginForm" type="LoginForm"/> </form-beans> <!-- ========== Global Forward Definitions ============================== --> <global-forwards> <forward name="login" path="/login/login.jsp"/> <forward name="success" path="/main/success.jsp"/> </global-forwards> <!-- ========== Action Mapping Definitions ============================== --> <action-mappings> <!-- Process a user logon --> <action path="/login" type="LoginAction" name="loginForm" input="/login/login.jsp" scope="request" /> </action-mappings> </struts-config>
一个testcase
public class TestportalTest extends CactusStrutsTestCase { public TestportalTest(String s) { super(s); } public void testSuccessfulLogin() throws Exception{ addRequestParameter("msisdn","15800001000"); addRequestParameter("pwd","111111"); setRequestPathInfo("/login"); actionPerform(); verifyForward("success"); verifyForwardPath("/index.jsp"); verifyNoActionErrors(); } }
一个web。xml部分
<servlet> <servlet-name>ServletRedirector</servlet-name> <servlet-class>org.apache.cactus.server.ServletTestRedirector</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletRedirector</servlet-name> <url-pattern>/ServletRedirector</url-pattern> </servlet-mapping>
客户端运行测试
public class TestportalTest extends CactusStrutsTestCase { public TestportalTest(String s) { super(s); System.setProperty("cactus.contextURL", "http://16.158.48.31:8001/testportal"); System.setProperty("cactus.servletRedirectorName", "ServletRedirector"); System.setProperty("cactus.enableLogging", "true"); } public void testSuccessfulLogin() throws Exception{ addRequestParameter("msisdn","15800001000"); addRequestParameter("pwd","111111"); setRequestPathInfo("/login"); actionPerform(); verifyForward("success"); verifyForwardPath("/index.jsp"); verifyNoActionErrors(); } }
其他
如果多个struts配置文件是通过分模块分目录如下配置
<servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>config/1</param-name> <param-value>/WEB-INF/struts-config-1.xml</param-value> </init-param> <init-param> <param-name>config/2</param-name> <param-value>/WEB-INF/struts-config-2.xml</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>3</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet>
时,在测试其中非主struts配置中的action时,可以通过CactusStrutsTestCase的setConfigFile来指定模块,譬如setConfigFile("2", "/WEB-INF/struts-config-2.xml");