AKA: Horarios se ha complicado mucho.
Si hay que cambiar líneas de código en nuestro proyecto, nos arriesgamos a un...
Nos comprueban que un módulo de código hace lo que se espera, y lo hace bien.
Ejemplo: funcion multiplicaPorDos
Test: comprueba que la funcion multiplicaPorDos(3) devuelve: 6.
Nos comprueban que todos los elementos que componen un proceso funcionan bien.
Ejemplo: Test que hace una llamada al servicio que obtiene la tabla de multiplicar del 2 completa del 1 al 1000.
En nuestro caso, los tests de integración atraviesan varias capas: Servicio, modelo, y DAO.
Cambiaremos nuestro applicationContext.xml para los tests, enlazando con una base de datos en memoria.
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="H2" />
<property name="showSql" value="true" />
<property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" />
</bean>
</property>
En la definición de nuestras clases de test cambiaremos el contexto a la base de datos en Memoria.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext-test.xml" })
@TransactionConfiguration(defaultRollback = false)
public class TablaDeMultiplicarTest {
}
El esquema de la base de datos se generará sólo a partir de las anotaciones JPA, pero los datos los tendremos que construir nosotros a mano. Para facilitar estre proceso utilizaremos Builders
Nos facilitar el proceso poblar nuestra base de datos en memoria con datos controlados para simular una ejecución de los tests con datos reales.
Hay que implementar una nueva clase Builder que creará nuestros objetos, ya sea en memoria o en la base de datos.
public class UsuarioBuilder
{
private Usuario usuario;
private UsuarioDAO usuarioDAO;
public UsuarioBuilder(UsuarioDAO usuarioDAO)
{
this.usuarioDAO = usuarioDAO;
usuario = new Usuario();
}
public UsuarioBuilder()
{
this(null);
}
public UsuarioBuilder withNombre(String nombre)
{
usuario.setNombre(nombre);
return this;
}
public Usuario build()
{
if (usuarioDAO != null)
{
usuario = usuarioDAO.insertUsuario(usuario);
}
return usuario;
}
}
Una vez tenemos creadas nuestras clases Builder que recubren a los modelos, ya podemos utilizarlas para poblar de datos nuestro entorno de testing.
@Autowired
private CentroDAO centroDAO;
@Autowired
private AulaDAO aulaDAO;
@Before
@Transactional
public void rellenaDatos()
{
Centro centro = new CentroBuilder(centroDAO).withNombre("Centro de prueba").build();
centroId = centro.getId();
Edificio edificio = new EdificioBuilder().withNombre("Edificio 1").withCentro(centro).build();
PlantaEdificio plantaEdificio = new PlantaEdificioBuilder().withNombre("Planta 1").withEdificio(edificio).build();
PlantaEdificio plantaEdificio2 = new PlantaEdificioBuilder().withNombre("Planta 2").withEdificio(edificio).build();
AreaEdificio areaEdificio = new AreaEdificioBuilder().withNombre("Area 1").withEdificio(edificio).build();
AreaEdificio areaEdificio2 = new AreaEdificioBuilder().withNombre("Area 2").withEdificio(edificio).build();
TipoAula tipoAula = new TipoAulaBuilder().withNombre("Tipo Aula 1").withEdificio(edificio).build();
TipoAula tipoAula2 = new TipoAulaBuilder().withNombre("Tipo Aula 2").withEdificio(edificio).build();
new AulaBuilder(aulaDAO).withNombre("Aula 1").withArea(areaEdificio).withTipo(tipoAula).withPlanta(plantaEdificio).withEdificio(edificio).build();
new AulaBuilder(aulaDAO).withNombre("Aula 2").withArea(areaEdificio2).withTipo(tipoAula).withPlanta(plantaEdificio2).withEdificio(edificio).build();
new AulaBuilder(aulaDAO).withNombre("Aula 3").withArea(areaEdificio).withTipo(tipoAula2).withPlanta(plantaEdificio).withEdificio(edificio).build();
new AulaBuilder(aulaDAO).withNombre("Aula 4").withArea(areaEdificio).withTipo(tipoAula).withPlanta(plantaEdificio).withEdificio(edificio).build();
new AulaBuilder(aulaDAO).withNombre("Aula 5").withArea(areaEdificio).withTipo(tipoAula).withPlanta(plantaEdificio2).withEdificio(edificio).build();
new AulaBuilder(aulaDAO).withNombre("Aula 6").withArea(areaEdificio2).withTipo(tipoAula2).withPlanta(plantaEdificio).withEdificio(edificio).build();
}
Una vez tenemos cargados los datos controlados de testing en la base de datos en memoria, vamos a ejecutar nuestros tests de la capa REST.
public class MainTest extends JerseyTest {
public MainTest()throws Exception {
super("com.sun.jersey.samples.helloworld.resources");
}
@Test
public void testHelloWorld() {
WebResource webResource = resource();
String responseMsg = webResource.path("helloworld").get(String.class);
assertEquals("Hello World", responseMsg);
}
}
En la UJI: extendemos es.uji.commons.testing.jersey.JerseySpringTest
En el proyecto Horarios hemos utilizado las llamadas de la siguiente manera:
// - GET
ClientResponse response = resource.path("grupo").queryParams(getDefaulQueryParams()).accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);
List<UIEntity> listaGrupos = response.getEntity(new GenericType<List<UIEntity>>() { };
// - POST
resource.path("calendario/eventos/generica/divide/" + evento_id).accept(MediaType.APPLICATION_JSON_TYPE).post(ClientResponse.class);
// - PUT
MultivaluedMap params = new StringKeyStringValueIgnoreCaseMultivaluedMap();
params.putSingle("aulaId", String.valueOf(aulaPlanificacionId));
params.putSingle("tipoAccion", "F");
resource.path("calendario/eventos/aula/evento/" + eventoId).accept(MediaType.APPLICATION_JSON).put(ClientResponse.class, params);
// - DELETE
resource.path("calendario/eventos/generica/" + eventoId).accept(MediaType.APPLICATION_JSON_TYPE).delete(ClientResponse.class);
Partiendo de unos datos controlados, hemos hecho una llamada nuestra capa REST y hemos obtenido unos resultados. ¿Son los esperados?
Las aserciones con HamCrest son más comprensibles, fácilmente entendibles en un vistazo rápido.
public class UIEntityHasTitle extends TypeSafeMatcher
{
@Override
public void describeTo(Description description)
{
description.appendText("having a title");
}
@Override
protected boolean matchesSafely(UIEntity entity)
{
String entity_title = entity.get("title").replace("\"", "");
return entity_title != null && entity_title.length() > 0;
}
@Factory
public static Matcher hasTitle()
{
return new UIEntityHasTitle();
}
}
¿Cuánto de nuestro código tiene protección anti-desastres?
Sonar nos proporciona mucha información:
INCONVENIENTE: Esperar a que Jenkins lance el análisis (cada 10 minutos si hay commit).
Plugin para eclipse: http://ecobertura.johoop.de/
var steps = [
{ action : 'click', target: '>>splitbutton' },
{ action : 'click', target :'>>menuitem[action="asignacion-aulas]' },
{ waitFor : 'componentQuery', args : '>>combobox[alias=widget.comboCentros]'},
{ action : 'click', target :'>>combobox[alias=widget.comboCentros]' },
function (next) {
var combo_centros = Ext.ComponentQuery.query('>>combobox[alias=widget.comboCentros]')[0];
var combo_estudio = Ext.ComponentQuery.query('>>combobox[alias=widget.comboEstudios]')[0];
var combo_semestre = Ext.ComponentQuery.query('>>combobox[alias=widget.comboSemestre]')[0];
t.ok(! combo_centros.disabled, 'El combo de centros NO está deshabilitado');
t.ok(combo_estudio.disabled, 'El combo de estudios está deshabilitado');
t.ok(combo_semestre.disabled, 'El combo de semestres está deshabilitado');
t.click(combo_centros.getPicker().getNode(centro_seleccionado),next);
},
Pincha aquí para la demo.