Patrón Modelo-Vista-Presenter (MVP)

De ChuWiki

¿Cual es el problema?[editar]

Cuando hacemos test automáticos de prueba para nuestra aplicación y, sobre todo, si seguimos una metodología TDD (Test Driven Development), es muy importante hacer el código de forma que pueda ser fácilmente testeado por estas pruebas automáticas. En estas situaciones, nos encontramos con una parte concreta de código que tradicionalmente es muy difícil de testear de forma automática: La interfaz gráfica de usuario. Testear la interfaz gráfica de usuario es difícil porque debemos simular los clicks de botones y los eventos de teclado desde nuestro código de test. También debemos desde nuestro código de test comprobar el contenido de cajas de texto, de check box, si se han lanzado ventanas de aviso o de error, etc.

En java se puede testear una interfaz gráfica de usuario "buscando" los distintos componentes. Si tenemos una ventana, su método getComponents() nos da qué componentes tiene dentro y a su vez, el getComponents() de estos componentes nos permite ir navegando hacia abajo, para buscar un botón concreto, una caja de texto concreta o una ventana concreta. Pero realizar este código en los test automáticos es tedioso y hace el código de test más complejo de lo necesario.

Hay librerías, como Fest-Swing que se basan en esta filosofía y nos facilitan todo ese código de búsqueda de botones y cajas de texto por las ventanas.

Pero hay también otras soluciones, una de ellas consiste en codificar siguiendo el patrón MVP (Modelo-Vista-Presenter). La idea de este patrón es codificar la interfaz de usuario muy, muy tonta, que tenga el menor código posible, de forma que no merezca la pena testearla. En su lugar, toda la lógica de la interfaz de usuario, la hacemos en una clase separada (que se conoce como Presenter), que no dependa en absoluto de los componentes de la interfaz gráfica y que, por tanto, es más fácil de testear.

Vamos a ver aquí una explicación detallada de cómo programar según este patrón. Al final hay un ejemplo cuyos fuentes completos están en ejemplo-modelo-vista-presenter

El patrón MVP Modelo-Vista-Presenter[editar]

La idea básica es que la clase Presenter haga de intermediario entre la Vista (la interfaz gráfica de usuario) y el modelo de datos.


La Vista[editar]

La vista debe ser lo más tonta posible:

  • La vista tiene métodos en los que le pasan los datos que debe pintar ya "mascados". Únicamente debe meter esos datos en los componentes gráficos (cajas de texto, checkbox, etc). También métodos get para obtener el contenido de esos componentes.
public class Vista {
   ...
   private JTextField textfield = new JTextField();
   ...
   public void setTextoParaElTextField (String texto) {
      textField.setText(texto);
   }
   public String getTextoDelTextField() {
      return textField.getText();
   }
}
  • La vista hace un new del Presenter, pasándose a ella misma en el constructor del Presenter, de forma que el Presenter pueda llamar a los métodos de la vista para pasarle datos.
public class Vista {
  ...
  private Presenter presenter;
  ...
  public Vista (Modelo modelo) {
     presenter = new Presenter (modelo, this);
  }
  ...
}


  • La vista añade los actionListener a los botones, checkbox, menús, etc, pero en ese listener únicamente debe llamar a un método del Presenter sin pasarle ningún parámetro.
public class Vista {
   private JButton boton = new JButton("Pulsame");
   private Presenter presenter;

   public Vista (Modelo modelo) {
      presenter = new Presenter(modelo,this);
      boton.addActionListener(new ActionListener(){
         public void actionPerformed(ActionEvent e) {
            presenter.seHaPulsadoBoton(); // Llamada sin parametros
         }
      });
   }
}

Vemos que haciéndolo de esta forma, el código de la Vista (interfaz gráfica de usuario) es bastante simple y no deberíamos perder mucho si no le hacemos test automáticos de prueba.


El Presenter[editar]

El Presenter, en su constructor, recibirá el Modelo y la Vista, de forma que pueda hacer de enlace entre ambos y dote de inteligencia a la vista. Como el objetivo es poder testearlo fácilmente, lo ideal es que en vez de Modelo y Vista directamente, reciba interfaces que deben implementar el modelo y la vista, con los métodos públicos a los que el Presenter debe llamar.

public class Vista implements IfzVista {
...
}
public class Modelo implements IfzModelo {
...
}
public class Presenter {
   public Presenter (IfzModelo modelo, IfzVista vista) {
   ...
   }
}

Haciéndolo así, en nuestro test, en vez de pasar la vista real, podemos pasar un mock object (un objeto que implemente la IfzVista y cuyo código esté hecho a posta para ayudarnos a testear lo que queramos, este objeto sólo sirve para el test).


Haciendo el test automático[editar]

Testear ahora el Presenter debe ser una tarea fácil. Podemos hacer un mock object para simular la Vista. Basta con que la clase MockVista implemente IfzVista y verifique que en los métodos set recibe lo esperado y podemos hacer que en los métodos get devuelva lo que queramos y nos sea útil para el test.

public MockVista implements IfzVista {
   public void setTextParaElTextField(String texto) {
      assertEquals("espero este texto", texto); // método de testeo.
   }
   public String getTextoDelTextField() {
      return "Esto es lo que se supone ha escrito el usuario";
   }
}

Igualmente, simular los clicks de botón es fácil en nuestro test, ya que un click de botón real llama a un método del Presenter

public void testNumeroUno () {
   IfzVista mockVista = new MockVista();
   IfzModelo mockModelo = new MockModelo();
   Presenter presenter = new Presenter(mockModelo, mockVista);

   //simulacion del click.
   presenter.seHaPulsadoBoton(); 
}


Un ejemplo concreto[editar]

En el código completo de ejemplo, la vista es un panel con dos JComboBox. El primer JComboBox permite elegir entre "pares" e "impares". Al seleccionar uno de ellos, el contenido del segundo JComboBox se rellena con 2,4,6 o con 1,3,5. Al usar el patrón modelo-vista-presenter, la vista no sabe nada de qué items tiene que mostrar ni sabe cambiar de uno a otro. Sólo tiene métodos para:

  • Pasarle los items posibles tanto para el JComboBox primero, como para el segundo.
  • Recoger el valor seleccionado en cualquier de los JComboBox.

El Modelo del ejemplo es el que sabe qué items son pares o impares. Ni la vista le pide al modelo los posibles items, ni el modelo se los pasa directamente a la vista. Para eso está el Presenter.

El Presenter recibe en el constructor tanto el modelo como la vista y se encarga de todo. En el constructor, por ejemplo, tiene un trozo de código como este

// Pasa a la vista los items para el primer combo.
vista.setPosiblesTipos(new String[] { Modelo.IMPARES, Modelo.PARES });

// Pide a la vista que item ha quedado seleccionado en el primer combo
String tipo = (String) vista.getTipo();

// Pide al modelo los posibles items para el segundo combo según la selección en el primero
int[] valores = modelo.getPosiblesValores(tipo);

// y se los pasa a la vista, para el segundo combo
vista.setPosiblesValores(valores);

Está todo el código en ejemplo-modelo-vista-presenter