StrutsTestCase
Introducción
StrutsTestCase es una extensión de junit que permite realizar pruebas sobre
aplicaciones basadas en Struts.
Provee dos maneras diferentes de realizar las pruebas: mediante mock objects
(las clases ServletRequest, ServletResponse y aquellas manejadas por el
servidor de aplicaciones son mock objects), o mediante Cactus (un contender de
servlets ligero). El cambiar entre cualquiera de las dos aproximaciones es tan
sencillo como cambiar la clase de la que heredará nuestro test, sin que el test
en sí cambie.
Personalmente prefiero usar mock objects en lugar de Cactus. Usar Cactus
implica que nuestro proyecto tiene más dependencias y configuraciones. Con mock
objects todo queda más sencillo, aunque dependediendo del tipo de aplicación
necesitaremos realizar ciertas inicializaciones, como veremos en el ejemplo que
sigue.
Podéis descargarlo y acceder a la documentación en http://strutstestcase.sourceforge.net/.
Otras opciones para realizar este tipo de pruebas es utilizar otras
herramientas que nos permiten simular navegaciones en cualquier aplicación web
(ya no nos estaríamos casando con struts), y como lo normal es que cada acción
funcional este asociada a un action de struts, realizaríamos pruebas sobre los
action (con salvedades como que ya dependemos de un servidor de aplicaciones y
la configuración que todo ellos conlleva). Una herramienta muy útil para estos
menesteres, Selenium, la podéis ver en el tutorial de mi compañero Víctor: https://adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=seleniumIDE.
El código de ejemplo
Veremos cómo usar StrutsTestCase para una aplicación que usa Struts 1.3,
contruida con Maven.
La aplicación trata de una sencilla librería por internet, donde se necesita
estar logado para realizar las acciones, y finalmente se pueden realizar
búsquedas filtradas de los libro.
Veamos como ejemplo una clase de acción de la que vamos a realizar tests:
public class LoginAction extends GenericAction { private static final Log log = LogFactory.getLog(LoginAction.class); @Override public ActionForward executeAction(ActionMapping aM, ActionForm aF, HttpServletRequest request, HttpServletResponse response) throws Exception { final LoginForm form = (LoginForm) aF; String forw = "error"; final String name = form.getName(); final String pwd = form.getPwd(); final ActionErrors errors = new ActionErrors(); if (log.isDebugEnabled()) { log.debug("pasando por loginAction"); } if ("test".equals(name) && "test".equals(pwd)) { final User logged = new User(); logged.setLogin("test"); logged.setPassword("test"); logged.setNombre("German"); logged.setApellido("Jimenez"); logged.setEmail("gjimenez@autentia.com"); request.getSession().setAttribute("loggedUser", logged); forw = "success"; } else { errors.add("errors.login.fail", new ActionMessage( "errors.login.fail")); addErrors(request, errors); forw = "error"; } return aM.findForward(forw); } }
Y la clase de la que hereda, que verifica que un usuario esté o no logado
para todas las acciones:
public abstract class GenericAction extends Action { private static final Log log = LogFactory.getLog(GenericAction.class); private boolean validateUser(ActionMapping aM, HttpServletRequest req, HttpServletResponse res) throws Exception { return req.getSession(false).getAttribute("loggedUser") != null; } /* * (non-Javadoc) * * @seeorg.apache.struts.action.Action#execute(org.apache.struts.action. * ActionMapping, org.apache.struts.action.ActionForm, * javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ @Override public ActionForward execute(ActionMapping aM, ActionForm aF, HttpServletRequest request, HttpServletResponse response) throws Exception { boolean isLogged = true; if (!(request.getContextPath() + "/login.do").equals(request .getRequestURI())) { isLogged = validateUser(aM, request, response); } if (log.isDebugEnabled()) { log.debug("Pasando por la validacion de usuario: " + isLogged); } return isLogged ? executeAction(aM, aF, request, response) : new ActionForward("/pages/login.jsp"); } public abstract ActionForward executeAction(ActionMapping aM, ActionForm aF, HttpServletRequest request, HttpServletResponse response) throws Exception; }
Por último nos quedaría ver la clase de test de junit con su extensión de
StrutsTestCase (veréis que es un test de JUnit 3, ya que no podemos usar test
de JUnit 4 con StrutsTestCase) (aunque JUnit 4 es capaz de ejecutar tests de
JUnit 3):
public class LibraryTest extends MockStrutsTestCase { @Override protected void setUp() throws Exception { super.setUp(); final DAOdao = new BookDAO(); final List editoriales = Arrays.asList(Editorial.EDITORIALS); final List author = Arrays.asList(Author.AUTHORS); // Debemos establecer para cada prueba el contextPath, puesto que la // request es un // mock object y nadie la establece getMockRequest().setContextPath("/library"); // establecemos ciertas variables en el contexto de aplicacion que se // supone // que inicializa un listener getSession().getServletContext().setAttribute("editorialesSelect", editoriales); getSession().getServletContext().setAttribute("autoresSelect", author); try { // Anadimos los datos de prueba a nuestro DAO // lo ideal seria utilizar mock objects e integrar con Spring dao.update(new Book("El fuego", "", Integer.valueOf(1), "", "", Float.valueOf(0), editoriales.get(0), author.get(0))); dao.update(new Book("Luna nueva", "", Integer.valueOf(1), "", "", Float.valueOf(0), editoriales.get(1), author.get(1))); dao.update(new Book("Crepusculo", "", Integer.valueOf(1), "", "", Float.valueOf(0), editoriales.get(2), author.get(1))); dao.update(new Book("El club de las malas madres", "", Integer .valueOf(1), "", "", Float.valueOf(0), editoriales.get(5), author.get(3))); dao.update(new Book( "El señor de los anillos: la comunidad del anillo", "", Integer.valueOf(1), "", "", Float.valueOf(0), editoriales .get(3), author.get(4))); dao.update(new Book("El señor de los anillo: las dos torres", "", Integer.valueOf(1), "", "", Float.valueOf(0), editoriales .get(3), author.get(4))); dao.update(new Book("El señor de los anillo: el retorno del Rey", "", Integer.valueOf(1), "", "", Float.valueOf(0), editoriales.get(3), author.get(4))); } catch (DAOException e) { } // el dao se encuentra en el contexto de la aplicacion getSession().getServletContext().setAttribute("bookDao", dao); } public void testShouldLogin() { // Establecemos la url de la request nosotros mismos puesto // que la request es un mock object y nadie lo establece getMockRequest().setRequestURI("/library/login.do"); // indicamos el action a probar setRequestPathInfo("/login"); // andimos los parametros necesarios, que sera como // si enviaramos en un formulario addRequestParameter("name", "test"); addRequestParameter("pwd", "test"); // ejecutamos la accion actionPerform(); // verificamos que recibimos el forward success verifyForward("success"); // obtenemos el usuario logado en sesion y comprobamos su existencia final User logged = (User) getSession().getAttribute("loggedUser"); assertNotNull(logged); // comprobamos que sea valido assertEquals("test", logged.getLogin()); assertEquals("test", logged.getPassword()); assertEquals("German", logged.getNombre()); assertEquals("Jimenez", logged.getApellido()); assertEquals("gjimenez@autentia.com", logged.getEmail()); // verificamos que no existan ActionErrors verifyNoActionErrors(); } public void testShouldLoginFail() { // Establecemos la url de la request nosotros mismos puesto // que la request es un mock object y nadie lo establece getMockRequest().setRequestURI("/library/login.do"); // indicamos el action a probar setRequestPathInfo("/login"); // anadimos los parametros necesarios, que sera como // si enviaramos en un formulario addRequestParameter("name", "testtt"); addRequestParameter("pwd", "test"); // ejecutamos la accion actionPerform(); // verificamos que recibimos el forward success (pues usuario o // contrasena son invalidos) verifyForward("error"); // debemos tener errores de ActionErrors verifyActionErrors(new String[] { "errors.login.fail" }); } }
Veamos ciertas características del test:
- Si reescribimos el método SetUp, debemos explícitamente llamar a
super.setUp(). Si no lo hacemos StrutsTestCase no se
inicializará correctamente. - Hereda de MockStrutsTestCase (si quisieramos Cactus debe heredar de
CactusStrutsTestCase, y configurar Cactus). - Al usar mock objects en la ServletRequest y ServletResponse, hay ciertos
atributos que nadie setea (como contextPath o requestURI), pero que podemos
o tendremos que establecer nosotros, accediendo a los objetos derequest y
response mediante getMockRequest() y getMockResponse() y realizar las
operaciones necesarias. - Sin embargo todos aquellos atributos que nuestro action ponga en sesión o
cualquier otro contexto se mantendrá durante toda la prueba (cada uno de
los métodos del test de JUnit), por tanto si queremos probar una búsqueda y
necesitamos estar logados, en la prueba de búsqueda debemos logarnos
antes. - Por defecto busca el fichero struts-config.xml. Si tuviéramos diferentes
nombres se pueden indicar con el método setConfigFile(pathname). - Los ficheros web.xml y struts-config.xml deben estar en
el classpath para que StrutsTestCase los pueda encontrar y procesar. Una
solución, si se realiza desde eclipse, es añadir la carpeta webapp al build
path (StrutsTestCase busca realmente WEB-INF/web.xml y
WEB-INF/struts-config.xml). Desde maven hemos de configurar el plugin
surefire, como veremos a continuación
Por último veamos cómo quedaría nuestro fichero de pom, para ver las
dependencias necesarias y la configuración del plugin de surefire para añadir
la carpeta webapp al classpath:
4.0.0 com.autentia.training library war 1.0-SNAPSHOT library-core Maven Webapp http://maven.apache.org org.apache.maven.plugins maven-surefire-plugin /webapp org.apache.struts struts-core 1.3.10 org.apache.struts struts-taglib 1.3.10 org.apache.struts struts-tiles 1.3.10 commons-collections commons-collections 3.2.1 strutstestcase strutstestcase 2.1.4-1.2-2.4
Conclusiones
Ya veis que en Autentia nos gusta
trabajar bien, y con las últimas herramientas, que nos ahorren tiempo y
esfuerzo, asi que no dudéis en contactar con nosotros para más información.