Construir una Aplicacion Web utilizando HttpUnit y la Metodología Diriga al Test

La caracter�stica "new contact" est� accesible en http://localhost:8080/phonelist/new.do. Esta URL deber�a mostrar una p�gina con un formulario que acepte la entrada de las propiedades de un contacto: last name, first name, phone, fax, y email. Tambi�n necesitar�s una action que haga lo siguiente:

  1. Acepte los datos introducidos por el usuario en el navegador.
  2. Cree un nuevo ejemplar de ContactBean con un valor de ID �nico.
  3. Almacene el bean en el repositorio ContactDatabase.

La clase NewTest deber�a testear las siguientes condiciones o funcionalidades:

  1. En la p�gina mostrada por la acci�n /showList.do, hay un enlace titulado "Create New Contact" que apunta a /new.do.
  2. En la p�gina new contact, hay un formulario con los campos last name, first name, phone, fax, y email que tiene un atributo action apuntando a /save.do.
  3. Cuando se env�a un contacto con datos aleatorios para los campos, los datos recien creados se muestran en la p�gina que muestra la acci�n showList.do. Adem�s, el n�mero contactos se aumenta en uno.

Teclea el siguiente c�digo en un fichero llamado NewTest.java en el directorio src/WEB-INF/classes/com/abcinc/phonelist:


package com.abcinc.phonelist.test;

import com.meterware.httpunit.GetMethodWebRequest;
import com.meterware.httpunit.TableCell;
import com.meterware.httpunit.WebConversation;
import com.meterware.httpunit.WebLink;
import com.meterware.httpunit.WebRequest;
import com.meterware.httpunit.WebResponse;
import com.meterware.httpunit.WebTable;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class NewTest extends TestCase {

  private static String protocol = "http";
  private static String hostname = "localhost";
  private static int port = 8080;
  private static String showListPath = "/phonelist/showList.do";
  private static String newPath = "/phonelist/new.do";

  private WebConversation webConversation = new WebConversation();

  public static void main(String args[]) {
    junit.textui.TestRunner.run(suite());
  }      

  public static TestSuite suite() {
    return new TestSuite(NewTest.class);
  }      

  public NewTest(String s) {
    super(s);
  }      

  public void testShowListHasNewContactLink() throws Exception {
    String url = protocol + "://" + hostname + ":" + port + showListPath;
    WebRequest webRequest = new GetMethodWebRequest(url);
    WebResponse webResponse = webConversation.getResponse(webRequest);
    WebTable webTable = webResponse.getTableStartingWith("Phone List");
    assertNotNull("Phone List HTML table not found", webTable);
    int rowCount = webTable.getRowCount();
    TableCell lastTableCell = webTable.getTableCell(rowCount - 1, 0);
    WebLink webLink = lastTableCell.getLinkWith("New Contact");
    String linkUrl = webLink.getURLString();
    if (linkUrl.indexOf(";jsessionid=") >= 0) {
      linkUrl = linkUrl.substring(0, linkUrl.indexOf(";jsessionid="));
    }
    assertTrue("Phone List has no new contact link", linkUrl.endsWith(newPath));
  }

}

Este clase deber�a parecerte familar. Es similar a la clase ShowListTest que vimos en p�ginas anteriores. La clase NewTest realizar� los tests que hemos visto en el listado anterior. Ahora mismo s�lo realiza el primero de los tres tests. Podr�as comparar tu fichero NewTest.java con NewTest.java.v1 del archivo del c�digo de ejemplo. El fichero NewTest.java.v1 contiene comentarios �tiles, podr�as echarle un vistazo aunque pienses utilizar el fichero que acabas de crear.

Para el nuevo test, tambi�n necesitar�s a�adir un target al fichero build.xml incluido en el archivo del c�digo de ejemplo. Deber�as copiar build.xml.v2 desde el archivo del c�digo de ejemplo a tu directorio ~/projects/phonelist. Luego, a�ade la siguiente definici�n de target debajo del target test-showlist:

  <target name="test-new" depends="compile-tests, post-compile-tests-init"
      description="Run new test">
    <java classname="com.abcinc.phonelist.test.NewTest">
      <classpath path="${test.complete.classpath}"/>
    </java>
  </target>

Adem�s, necesitas modificar la definici�n existente del target test para adjuntarle la definici�n de test-new. Reemplaza la definici�n existente del target test con �sta:

  <target name="test"
          depends="compile-tests, post-compile-tests-init, test-showlist, test-new"
          description="Run all tests"/>

Tu fichero build.xml deber�a parecerse al fichero build.xml.v3 del archivo del c�digo de ejemplo, con la excepci�n de los comentarios y posiblemente el formateo. Podr�as adoptar build.xml.v3 como tu fichero build.xml si quieres asegurarte de que has realizado todos los cambios descritos arriba.

Ejecuta los test tecleando ant test. Este comando ejecutar� tanto el test showlist como el nuevo test para segurarse que se prueban las viejas funcionalidades. Cualquier cambio en las funcionalidades no solo provoca que se testeen las nuevas funcionalidades sino que tambi�n evita la rotura de las viejas funcionalidades.

Deber�a pasarse con �xito el test showlist. En este punto tambi�n deber�a pasarse el nuevo test, pero no hemos finalizado la codificci�n de los otros dos tests de la suite de tests new contact. Despu�s de implementar los otros dos tests, la suite de tests NewTest deber�a fallar porque todav�a no has implementado la funcionalidad new contact. Implementa los dos tests restantes para entonces poder implementar la funcionalidad que deben testear estos tests.

A�ade esto en la parte superior del fichero NewTest.java junto a las otras sentencias import:

import com.meterware.httpunit.SubmitButton;
import com.meterware.httpunit.WebForm;
import java.util.Random;

A�ade tambi�n esto a NewTest.java despu�s de la l�nea que dice: private static String newPath = "/phonelist/new.do":

  private static String savePath = "/phonelist/save.do";
  private static String randomCharacterSet =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

La variable randomCharacterSet se utiliza para generar strings aleatorios que se utilizan para verificar que la aplicaci�n est� grabando los nuevos contactos. A�ade tambi�n la siguiente declaraci�n de variable est�tica a la declaraci�n de webConversation:

  private static Random random = new Random();

A�ade los m�todos del siguiente listado a NewTest.java:


    public void testNewContactForm() throws Exception {
        String url = protocol + "://" + hostname + ":" + port + newPath;
        WebRequest webRequest = new GetMethodWebRequest(url);
        WebResponse webResponse = webConversation.getResponse(webRequest);
        WebForm webForm = webResponse.getFormWithID("contactForm");
        // test that the contact form exists.
        assertNotNull("Contact form not found", webForm);
        // test that the required fields exist on the form.
        assertTrue("Last Name field (lastName) missing from contact form", 
                  webForm.hasParameterNamed("lastName"));
        assertTrue("First Name field (firstName) missing from contact form",
                  webForm.hasParameterNamed("firstName"));
        assertTrue("Phone field (phone) missing from contact form", 
                  webForm.hasParameterNamed("phone"));
        assertTrue("Fax field (fax) missing from contact form", 
                  webForm.hasParameterNamed("fax"));
        assertTrue("Email field (email) missing from contact form", 
                  webForm.hasParameterNamed("email"));
        // test that the form action points to the save action.
        String formAction = webForm.getAction();
        // strip off session state information in the URL if it is there
        // because we want to compare the path of the URL.
        if (formAction.indexOf(";jsessionid=") >= 0) {
            formAction = formAction.substring(0, formAction.indexOf(";jsessionid="));
        }
        assertTrue("Contact form points to wrong action", formAction.endsWith(savePath));
    }

    // test that we can save a new contact.
    public void testSaveNewContact() throws Exception {
        String url;
        WebRequest webRequest;
        WebResponse webResponse;
        
        // first get the existing list of contacts so we can count how
        // many contacts there are. We use the information later to
        // compare the number of contacts after saving a new contact.
        url = protocol + "://" + hostname + ":" + port + showListPath;
        webRequest = new GetMethodWebRequest(url);
        webResponse = webConversation.getResponse(webRequest);
        WebTable webTable = webResponse.getTableStartingWith("Phone List");
        int rowCount = webTable.getRowCount();
        // subtract the three rows for title, column headings, and buttons.
        int numContacts = rowCount - 3;
        TableCell lastBodyCell = webTable.getTableCell(rowCount - 2, 0);
        if (lastBodyCell.getColSpan() > 1) {
            // this means the message that there were no contacts was
            // displayed.
            numContacts = 0;
        }
        // now retrieve the new contact form page.
        url = protocol + "://" + hostname + ":" + port + newPath;
        webRequest = new GetMethodWebRequest(url);
        webResponse = webConversation.getResponse(webRequest);
        WebForm webForm = webResponse.getFormWithID("contactForm");
        // test that the contact form exists.
        assertNotNull("Contact form not found", webForm);
        String lastName = getRandomString(8);
        String firstName = getRandomString(8);
        String phone = getRandomString(8);
        String fax = getRandomString(8);
        String email = getRandomString(8);
        webForm.setParameter("lastName", lastName);
        webForm.setParameter("firstName", firstName);
        webForm.setParameter("phone", phone);
        webForm.setParameter("fax", fax);
        webForm.setParameter("email", email);
        // find the save button (rather than the cancel button).
        SubmitButton saveButton = webForm.getSubmitButton("submitButton", 
             "        Save     ");
        // make sure the save button exists.
        assertNotNull("Could not find save button", saveButton);
        webResponse = webForm.submit(saveButton);
        // now parse the results after saving the contact.
        webTable = webResponse.getTableStartingWith("Phone List");
        int newRowCount = webTable.getRowCount();
        // subtract the three rows for title, column headings, and buttons.
        int newNumContacts = newRowCount - 3;
        lastBodyCell = webTable.getTableCell(rowCount - 2, 0);
        if (lastBodyCell.getColSpan() > 1) {
            // this means the message that there were no contacts was
            // displayed.
            newNumContacts = 0;
        }
        assertTrue("Saving new contact did not increase contact count by 1", 
               (newNumContacts == (numContacts + 1)));
        // test that the random data we generated matches the contact
        // information by trying to find a row with matching field values.
        boolean foundMatchingContact = false;
        for(int i = 0; i < newNumContacts; i++) {
            // first two rows are table title and heading columns
            int rowIndex = i + 2;
            // get the last name
            TableCell tableCell = webTable.getTableCell(rowIndex, 2);
            String columnValue = tableCell.asText();
            if (columnValue.equals(lastName)) {
                tableCell = webTable.getTableCell(rowIndex, 3);
                columnValue = tableCell.asText();
                if (!columnValue.equals(firstName)) continue;
                tableCell = webTable.getTableCell(rowIndex, 4);
                columnValue = tableCell.asText();
                if (!columnValue.equals(phone)) continue;
                tableCell = webTable.getTableCell(rowIndex, 5);
                columnValue = tableCell.asText();
                if (!columnValue.equals(fax)) continue;
                // email has an extra space at the beginning and an extra
                // space at the end, so we need to strip those.
                tableCell = webTable.getTableCell(rowIndex, 6);
                columnValue = tableCell.asText();
                columnValue = columnValue.substring(1, columnValue.length() - 1);
                if (!columnValue.equals(email)) continue;
                foundMatchingContact = true;
                break;
            }
        }
        assertTrue("Newly created contact was not saved properly", foundMatchingContact);
    }

    // generate a random string of the specified length.
    private String getRandomString(int length) {
        StringBuffer randomStringBuffer = new StringBuffer();
        for(int i = 0; i < length; i++) {
            int index = random.nextInt(randomCharacterSet.length());
            randomStringBuffer.append(randomCharacterSet.substring(index, index + 1));
        }
        return randomStringBuffer.toString();
    }

El m�todo testNewContactForm verifica que existe el formulario new contact, que contiene los campos que se requieren, y que tiene configurada la action apropiada.

El m�todo testSaveNewContact es un poco m�s complejo. Calcula el n�mero filas de la lista de contactos, luego recupera el formulario new contact y simula el proceso de rellenado de los campos del formulario con strings completamente aleatorios de ocho caracteres para los campos last name, first name, phone, fax, y email. Luego simula que el usuario ha pulsado el bot�n Save. Como existen los botones Save y Cancel, es importante simular la pulsaci�n del bot�n apropiado. Finalmente, analiza la respuesta de saving the contact, que deber�a ser la p�gina showList. Chequea que el n�mero de contactos se ha incrementado en uno y que uno de los contactos corresponde con el recientemente introducido.

El m�todo getRandomString simplemente es un m�todo de ayuda que genera un string aleatorio de la longitud especificada por un par�metro. El m�todo testSaveNewContact lo llama para generar los valores de los diferentes campos.

En este punto, tu fichero NewTest.java deber�a parecerse al NewTest.java.v2 del archivo del c�digo de ejemplo, con la excepci�n de los comentarios y posiblemente el formateo.

Ejecuta los tests ahora. La suite showlist deber�a pasarse completamente, pero el test new s�lo se pasar� parcialmente. Dos de los tres test de la suite fallar�n debido a la falta de la acci�n /new.do. Recbir�s un mensaje de error similar a Este:

 
     [java] 2) testSaveNewContact(com.abcinc.phonelist.test.NewTest)com.meterware.httpunit.HttpException:
 Error on HTTP request: 400 Invalid path /new was requested [http://localhost:8080/phonelist/new.do]

.�Implementar la Funci�n "New Contact"

Implementa la funcionalidad new contact para que la aplicaci�n pase la suite de tests NewTest adem�s de ShowListTest. Los ficheros que tendr�s que crear o modificar son:

  1. NewAction.java
  2. EditForm.java
  3. edit.jsp
  4. struts-config.xml
  5. tiles-defs.xml
  6. ApplicationResources.properties

La clase NewAction simplemente rellena la clase EditForm con valores por defecto y luego muestra el JSP que contiene el HTML del formulario. Algunos formularios contienen valores por defecto. En nuestro caso no existen los valores por defecto.

La clase EditForm es un contenedor para pasar datos entre la capa de persistencia (las clases JaveBean que modelan los datos en la base de datos) y la capa de presentaci�n (los formularios JSP o HTML que muestran la informaci�n y aceptan entrada desde un navegador). La raz�n por la que esto es �til en las clases de formularios de Struts es porque la capa de persistencia muchas veces tiene diferencias con los tipos de datos o su orden en la capa de presentaci�n. Como ejemplo concreto, consideremos el caso de un valor de fecha. En la mayor�a de bases de datos, una fecha se almacena como un �nico campo que contiene el mes, el d�a y el a�o. En Java, se almacena una fecha en un objeto java.util.Date. La mayor�a de gente piensa que es m�s f�cil introducir las fechas con tres campos desplegables o con dos campos desplegables y un campo de texto. El mes y el d�a usualmente son campos desplegables. Y ocasionalmete el a�o tambi�n es un campo desplegable, pero algunas veces es un campo de texto. Incluso si le pides a un usuario que introduzca la fecha como un �nico campo, todav�a tendr�s que tratar con la conversi�n de tipos porque los formularios Web est�ndars s�lo pueden transmitir datos strings.

El fichero edit.jsp contiene el HTML para el formulario, incluyendo la secci�n superior para mostrar los mensajes de error y las etiquetas de Struts para mostrar campos de entrada HTML. La raz�n de utilizar edit.jsp en lugar de utilizar un fichero separado llamado new.jsp es que el formulario para crear un nuevo contacto es exactamente el mismo que el formulario para crear un contacto existente, con algunas excepciones:

  1. El formulario new contact no tiene un valor para el campo oculto ID que identifica el registro que se est� editando.
  2. El formulario new contact podr�a tener un t�tulo diferentes o una cabecera de tabla diferente al formulario edit contact (por ejemplo, "New Contact" vs. "Edit Contact").

Realiza los siguiente cambios:

  1. Copia NewAction.java.v1 desde el archivo del c�digo de ejemplo a src/WEB-INF/classes/com/abcinc/phonelist y llamalo NewAction.java.
  2. Copia tambi�n EditForm.java.v1 a src/WEB-INF/classes/com/abcinc/phonelist y llamalo EditForm.java.
  3. Copia edit.jsp.v1 a src/web y llamalo edit.jsp.
  4. A�ade la siguiente secci�n al fichero struts-config.xml en la secci�n form-bean justo despu�s de la entrada para manageForm:
        <form-bean name="editForm"
                   type="com.abcinc.phonelist.EditForm"/>
    
  5. A�ade las siguientes secciones al fichero struts-config.xml en la secci�n action-mapping:
        <action path="/new"
                type="com.abcinc.phonelist.NewAction"
                name="editForm"
                scope="request"
                input="success"
                validate="false">
          <forward name="success" path="edit"/>
        </action>
        
        <action path="/save"
                type="com.abcinc.phonelist.SaveAction"
                name="editForm"
                input="inputProblem"
                scope="request"
                validate="true">
          <forward name="inputProblem" path="edit"/>
          <forward name="success" path="showList"/>
          <forward name="cancel" path="showList"/>
        </action>
    

    La raz�n por la que necesitas definir ahora /save es porque lo utilizas en el atributo action del formulario HTML de edit.jsp.

  6. A�ade la siguiente secci�n a tiles-defs.xml:
      <definition name="edit" extends="mainLayout">
        <put name="titleString" value="Phone List :: Edit Contact"/>
        <put name="body" value="/edit.jsp"/>
        <put name="bodyParam1" value="Edit" direct="true"/>
      </definition>
    
  7. Copia ApplicationResources.properties.v1 desde el archivo del c�digo de ejemplo a src/WEB-INF/classes/com/abcinc/phonelist y llamalo ApplicationResources.properties. Este fichero almacena strings de recursos para Struts, principalmente etiquetas de campos y mensajes de error. Tener todas estas etiquetas, mensajes de error y otros textos visibles en un fichero hace m�s f�cil internacionalizar la aplicaci�n. Para poder crear una versi�n en Ruso, Frances, o Alem�n, por ejemplo, todo lo que tienes que hacer es traducir los textos y crear un fichero ApplicationResources.properties separado (con un sufijo que denote la localidad). La aplicaci�n Web usar� autom�ticamente el fichero apropiado bas�ndose en la localidad.

En este punto, tu fichero struts-config.xml deber�a ser equivalente a struts-config.xml.v3 del archivo del c�digo de ejemplo. Al igual que tu fichero tiles-defs.xml deber�a ser equivalente a tiles-defs.xml.v3.

Ahora realiza los siguientes pasos:

  1. Para Tomcat tecleando ./shutdown.sh en el subdirectorio bin de la distribuci�n de Tomcat.
  2. Ejecuta ant undeploy.
  3. Ejecuta ant deploy.
  4. Arranca Tomcat tecleando ./startup.sh en el subdirectorio bin de la distribuci�n de Tomcat.

Pasa de nuevos los tests tecleando ant test. La mayor�a de los tests deber�an pasarse, con la excepci�n de testSaveNewContact en la suite NewTest. Esto es natural ya que todav�a no has implementado SaveAction. Los tests est�n dise�ados para capturar cosas como estas.

Vuelve atr�s y remedia el problema realizando los siguientes pasos:

  1. Copia SaveAction.java.v1 desde el archivo del c�digo de ejemplo a src/WEB-INF/classes/com/abcinc/phonelist y llamalo SaveAction.java.
  2. Copia tambi�n validation.xml.v2 a src/WEB-INF y llamalo validation.xml, reemplazanto tu versi�n existente de validation.xml. Aunque no es necesario, esto ayuda con el chequeo de entradas en los formularios para asegurarnos que se introducen los valores correctos. La validaci�n en este momento s�lo consiste en comprobar que se han introducido el nombre y los apellidos.

Ahora vuelve a ejecutar los tests teclando ant test en el directorio del proyecto phonelist. Todos los tests deber�an pasarse con �xito.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
SIGUIENTE ARTÍCULO