Mostrando entradas con la etiqueta hamcrest. Mostrar todas las entradas
Mostrando entradas con la etiqueta hamcrest. Mostrar todas las entradas

sábado, 4 de mayo de 2019

REST Assured y como probar tus APIs REST


Hoy vamos a ver un software que nos permita hacer las pruebas a nuestra API REST, y este es: REST Assured. Para ello utilizaremos otra vez el servicio web  que ya hicimos con Jersey.

Para empezar decir que REST Assured esta basado en dos patrones de diseños: Fluent y Behaviour-driven development. Es decir:
  • Podemos concatenar la ejecución de los métodos. 
  • Nos proporciona métodos que nos aportan mayor legibilidad (aunque a efectos practicos no tengan una mayor trascendencia).
Rest Assured tiene una múltitud de métodos que nos ayudarán facilmente y junto con Hamcrest a testear completamente una API REST. Vamos a ir viendo su funcionamiento poco a poco y a través de ejemplos, pero centrandonos sobre todo en su funcionalidad JSON y dejando de lado su funcionalidad XML que también la tiene. 
  • Testear el cuerpo de la respuesta a través de body o content. Para navegar sobre el objeto JSON nos podemos ayudar de JSONPath. En este ejemplo también podemos ver los métodos BDD: given, when o then.
@Test
public void validateBody() {
 // http://localhost:8080/JerseyExample/rest/book
 given().when().get("/JerseyExample/rest/book").then().content("id[0]", equalTo(1));
 given().when().get("/JerseyExample/rest/book").then().body("id", hasItems(1, 2, 3));
 given().when().get("/JerseyExample/rest/book/list").then().body("library.findAll { it.id < 2 }.author", hasItems("Isaac Asimov"));
}
  • Testear una sección concreta del objeto JSON, y marcarla como el centro del objeto para ayudarnos en los JSONPATH a través del método root. También vemos el uso del método BDD,  and que nos ayuda a la hora de concatenar asserts. 
get("/JerseyExample/rest/book").then().root("[0]").body("author", equalTo("Isaac Asimov")).and().body("id", equalTo(1));
  • Indicar parámetros de forma dinámica en la llamada. 
get("/JerseyExample/rest/book/{id}", 1).then().body("id", equalTo(1));
  • Validar la respuesta HTTP: cabecera, status code, cookies o content type.
get("/JerseyExample/rest/book").then().contentType("application/json");
// get("/JerseyExample/rest/book").then().cookie("JSESSIONID");
get("/JerseyExample/rest/book").then().header("Content-Type", "application/json");
get("/JerseyExample/rest/book").then().statusCode(200);
  • Validar el esquema JSON de la respuesta.
get("/JerseyExample/rest/book/1").then().assertThat().body(matchesJsonSchemaInClasspath("book-schema.json"));
  • Extraer el contenido como un conjunto de objetos y realizar asercciones posteriormente
List<Map<String, Object>> books = get("/JerseyExample/rest/book").as(new TypeRef<List<Map<String, Object>>>() {});
assertThat(books, hasSize(3));
assertThat(books.get(0).get("author"), equalTo("Isaac Asimov"));
  • Extraer el contenido o parte del contenido en un formato concreto y otros valores de la respuesta.
String json = get("/JerseyExample/rest/book/1").asString();
assertThat(json, equalTo("{\"id\":1,\"author\":\"Isaac Asimov\",\"name\":\"Foundation\"}"));
Map<String, String> allCookies = get("/JerseyExample/rest/book/1").getCookies();
assertThat(allCookies.size(), equalTo(0));
String pathValue = get("/JerseyExample/rest/book/1").path("author");
assertThat(pathValue, equalTo("Isaac Asimov"));
  • Incluir logs, de todo o solo de partes concretas. O validar el tiempo que tarda en realizarse las llamadas.
given().log().all().get("/JerseyExample/rest/book/{id}", 1).then().time(lessThan(2000L));

Como veis, se pueden hacer muchas cosas. En estos ejemplos nos hemos centrado más en la parte de validar el JSON. Y hemos dejado de lado las validaciones XML o validaciones más complejas de tipo autenticación o cross-site, que también las tiene.

jueves, 16 de noviembre de 2017

Hamcrest nivel intermedio. Como hacer tu propio Matcher

Ya hemos hablado anteriormente de Hamcrest y sus posibilidades aquí. Ahora daremos un paso más y veremos como hacer nuestros propios Matchers. Es verdad que Hamcrest tiene multitud de métodos pero puede sernos necesario algún método concreto, bien porque sea una cualidad específica de nuestras pruebas o por que dicha comprobación se realice multiples veces. 

Es posible hacerlo de varias formas y empezaremos con la más básica: BaseMatcher. Debemos extender la clase para crear nuestra prueba. Para los ejemplos usaremos un objeto 'Car' que tiene un atributo entero 'wheels' y un atributo String 'colour'. 

@Test
public void equal() {
 Car car = new Car(3, "white");
 assertThat(car, hasEnoughWheels());
}
private Matcher<Car> hasEnoughWheels() {
 return new BaseMatcher<Car>() {
  public boolean matches(final Object item) {
   final Car car = (Car) item;
   return car.getWheels() >= 4;
  }
  public void describeTo(final Description description) {
   description.appendText("A car must have at least 4 wheels");
  }
  public void describeMismatch(final Object item, final Description description) {
   description.appendValue(((Car) item).getWheels() + " are not enough.");
  }
 };
}

Si ejecutamos la prueba, tendremos la siguiente salida obtendremos:

java.lang.AssertionError: 
Expected: A car must have at least 4 wheels
     but: "3 are not enough."
 at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
 ...

La diferencia entre BaseMatcher y nuestra siguiente clase, TypeSafeMatcher, es que esta última verificará el objeto que le pasamos y lo casteará por nosotros. Pero a cambio deberemos implementar otros métodos (matchesSafely y describeMismatchSafely por matches y describeMismatch).

@Test
public void equal() {
 Car car = new Car(3, "white");
 assertThat(car, hasCorrectColour("black"));
}
private Matcher<Car> hasCorrectColour(final String colour) {
 return new TypeSafeMatcher<Car>() {
  protected boolean matchesSafely(final Car car) {
   return colour.equals(car.getColour());
  }
  public void describeTo(final Description description) {
   description.appendText("A car should have the colour " + colour);
  }
  protected void describeMismatchSafely(final Car item, final Description mismatchDescription) {
   mismatchDescription.appendText("But was: " + item.getColour());
  }
 };
}

La salida sería:

java.lang.AssertionError: 
Expected: A car should have the colour black
     but: But was: white
 at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
 ...

Luego hay dos clases más DiagnosingMatcher y TypeSafeDiagnosingMatcher que siguen siendo básicamente lo mismo, pero con la cualidad que puedes crear el objeto description dentro del pripio método 'matches'. De esta forma implementamos un único método y no dos.

Y por último está CustomMatcher y su versión TypeSafe, que estan pensadas principalmente para el uso de clases internas. Es decir crear Matcher no reutilizables.

@Test
public void equal() {
 Car car = new Car(3, "white");
 Coche coche = new Coche();
 Matcher<Integer> aPositiveInt = new CustomMatcher<Integer>("a positive int") {
  public boolean matches(final Object object) {
   return object instanceof Integer && !((Integer) object < 0);
  }
 };
 assertThat(-1, aPositiveInt);
}

martes, 7 de noviembre de 2017

Como importar clases estáticas automaticamente en eclipse


Ultimamente estamos hablando mucho de software relacionado con el testing. Estas librerias tienen además una particularidad compartida. Y es que suelen hacer uso de clases y métodos estaticos. Con eclipse podemos importar las clases de forma automatica, tanto las normales como las que no. Y aquí explicaremos como.

Por si alguien no lo sabe, primero indicaremos como realizar la importacion automática de las clases que no son estáticas. Para ello debemos ir a 'Eclipse>Preferences>Java>Editor>Save Actions' y marcar las opciones:
  • Perform the selected actions on save
  • Organize imports
Esta sección sirve para mucho más. Pero solo con esto cada vez que guardemos nuestro fichero .java, eclipse se encargará de ordenar e importar todas las clases utilizadas.

Con la opción anterior no se importarán las clases o métodos que se hagan de ellas una invocación estáticas. Para ello necesitaremos una modificación de las preferencias más. Debemos acceder a 'Eclipse>Preferences>Java>Editor>Content Assist'. En esta sección debemos ir añadiendo 'New Types', es decir indicar la paquetería de las clases estáticas que queremos importar de forma automática (sin el asterisco, este lo pone tras la creacion automáticamente).



Las importaciones más comunes  para poner aqui serían las de JUnit o Hamcrest. Tal como se ven en el ejemplo.


lunes, 6 de noviembre de 2017

Hamcrest: Nivel básico

Siguiendo con el tema del testing, hoy vamos a ver Hamcrest. Es un framework que nos permitirá mejorar nuestras pruebas con JUnit, aumentando la legibilidad de las pruebas. 

Es considerado como un framework de testing de 3 generación. Primera generación es cuando haciamos tests del tipo 'assert(logical statement)'. Segunda generación, cuando haciamos test algo más legibles del tipo 'assertTrue(boolean condition)' en los cuales se indica a través del método que es aquello que se quiere probar. Tercera generación es cuando hacemos aún más legibles esos métodos, del tipo 'assertThat(a, is(b))'. 

Puedes importar distintas librerias de Hamcrest, pero la mas comunes son:
  • Hamcrest-core, la cual contiene el nucleo principal y los métodos más comunes.
  • Hamcrest-library, la cual contiene un conjunto amplio y variado de métodos para pruebas. 
Hamcrest a través del método 'assertThat' de JUnit monta la base para lo que serán sus diferentes pruebas. A continuación vamos a mostrar un ejemplo básico pero teniendo en cuenta que substrayendonos de la complejidad, todos serán similares a este:

import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class HamcrestTests {
 String uno = "uno";
 String dos = "dos";
 @Test
 public void equal() {
  assertThat(dos, equalToIgnoringCase(uno));
 }
}

Hamcrest tiene varias ventajas como son:
  • Descripción del error muy intuitiva
java.lang.AssertionError: 
Expected: equalToIgnoringCase("uno")
     but: was dos
 at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
 at org.junit.Assert.assertThat(Assert.java:956)
 at org.junit.Assert.assertThat(Assert.java:923)
 at es.home.example.test.HamcrestTests.equal(HamcrestTests.java:14)
 ...
  • Hacer uso del 'method chaining' con lo cual nos permite encadenar de forma sencilla varias invocaciones.
assertThat(50L, is(both(greaterThan(40L)).and(lessThan(60L))));
  • Permitir el uso de patrones
PatternMatcher dateRange = new PatternMatcher(separatedBy(whitespace(), capture("from", date), "-", capture("to", date)));
String input = "31 Dec 2003 - 16 aug 2008";
Parse parse = dateRange.parse(input);

assertThat(parse.get("from"), equalTo("31 Dec 2003")); 
assertThat(parse.get("to"), equalTo("16 aug 2008")); 
assertThat(parse.get("from.day"), equalTo("31"));
  • Gran cantidad de método ya implementados. Los más comunes son:
    • allOf - busca que coincidan todos los elementos
    • anyOf - busca que al menos uno coincida
    • not - busca que los objetos de la comparación no sean iguales 
    • equalTo ó is - busca que ambos objetos sean iguales
    • hasToString - comprueba que tenga Object.toString
    • instanceOf, isCompatibleType - comprueba el tipo del objeto
    • notNullValue, nullValue - busca que sea nulo
    • sameInstance - compara si son la misma instancia
    • hasEntry, hasKey, hasValue - busca en ojbetos de tipo mapa
    • hasItem, hasItems - busca que una colección tenga determinado elemento
    • hasItemInArray - busca que un array tenga determinado elemento
    • closeTo - busca valores 'float' cercanos
    • greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - permite busqueda de numeros
    • equalToIgnoringCase ó equalToIgnoringWhiteSpace - permite comparar Strings
    • containsString, endsWith, startsWith - permite comparar partes de una cadena