Cómo se hace: API testing con Postman

El testing, ese gran concepto que atemoriza, repele e interesa por partes iguales a muchos programadores. Es sin duda uno de los conceptos más necesarios para garantizar la calidad de nuestro software. Pero no nos engañemos, como muchas de las cosas de nuestra profesión y las pruebas no es una excepción, es un tema complicado. Además, como cualquier otra parte de nuestro software: las escribimos, las probamos, las reescribimos para mejorarlas, las mantenemos.

En el libro ¨El libro negro del programador¨ de Rafael Gómez Blanes, cita: ¨La naturaleza y diseño de un software testeable es completamente distinta de un código que se ha hecho sin el hábito continuo de respaldarlo con pruebas¨. Cuántas veces nos hemos encontrado situaciones que a raíz de un cambio hemos roto algo y hemos pensado: ¨Lo podría haber evitado si hubiera hecho el test de esto o lo otro¨.

Las excusas que nos decimos a nosotros mismos para no hacer pruebas son de lo más variadas. Os dejo un artículo sobre un libro que habla de ello: https://hackernoon.com/common-excuses-why-developers-dont-test-their-software-908a465e122c. El libro se llama ¨Excuses for Not Testing Software¨de James Jeffrey.

A lo largo de este artículo me gustaría enseñaros alguna de las cosas que he aprendido trabajando con Postman. Postman es un cliente HTTP que nos permite gestionar las peticiones a nuestras API´s. Postman tiene muchas funcionalidades para gestionar todo el ciclo de vida de nuestra API, pero una que encuentro especialmente interesante es la posibilidad de crear tests y automatizarlos para nuestras colecciones de peticiones. ¿Qué beneficios podemos obtener?:

  • Una vez escritas las pruebas para todas nuestras peticiones y lanzando las pruebas cada vez que haya un nuevo cambio en la API, garantizamos que las aplicaciones que dependen de nuestra API funcionen como se espera. Y si las pruebas no pasan, a la vez, tendremos información sobre lo que tenemos que arreglar.
  • Podemos hacer pruebas tanto como en el tipo de respuesta que damos a nuestras aplicaciones clientes, como del contenido. Cualquier cambio de esquema o de código HTTP puede ser detectado por nuestras pruebas.
  • Conforme nuestra API evolucione, las pruebas evolucionarán con ella, con lo que garantizamos su mantenimiento y reducción de errores.
  • Si tenemos el equipo de desarrollo de backend separado con el de front, tenemos en las pruebas en Postman una herramienta para: garantizar una documentación adecuada (single source of truth), ya que los dos equipos saben cual es el comportamiento de la API, pueden consultarlo y ejecutarlo siempre que quieran.

Para explicaros cómo funcionan las pruebas en Postman he montado una API en Laravel muy sencilla basada en la archiconocida Petstore de Swagger https://petstore.swagger.io/#/. Esta API no sigue los prácticas RESTful, pero para mi ejemplo sigue siendo válida. En mi caso, mi colección de Postman sólo va a constar de las siguientes peticiones relacionadas con el recurso pet:

   [GET] api/pet -> La lista de todos los pet de la tienda.
  [POST] api/pet  -> Crear un nuevo pet en la tienda.
   [GET] api/pet/{petId} -> Encuentra un pet por si identificador.
   [PUT] api/pet/{petId} -> Actualizar la información de un pet por si identificador
[DELETE] api/pet/{petId} -> Eliminar un pet de la tienda

Lo primero que vamos a hacer es crear y configurar nuestra colección. Para ello abrimos la opción ¨Create a new collection¨. Llamaremos a nuestra colección ¨Petstore¨.

Si nos fijamos en esta misma ventana hay dos aspectos interesantes relacionados con el testing. El primero, la pestaña de ¨Tests¨. Estos tests se ejecutarán cada vez que cualquier petición de esta colección se haya servido. En la pestaña ¨Variables¨ podremos crear variables que podremos reutilizar en todas nuestras peticiones, pero esta pestaña son variables que se definen para la colección actual, con lo que si queremos que las variables que utilicemos para nuestras peticiones cambien de valor dependiendo del entorno es mejor que configuremos entornos, que explicaré un poco más adelante.

Una vez ya tenemos la colección creada, ya podemos añadir los endpoints que queremos implementar y probar. Nos vamos a centrar en una sola petición, GET /pets, que nos permite obtener la lista de pets de nuestra tienda.

Añadir esta petición a la colección es tan sencillo como abrir una nueva pestaña en la pantalla principal, poner la url completa de la petición, indicar que la petición es GET y como en este caso no tenemos que añadir ningún parámetro extra, simplemente pulsamos ¨Send¨ y obtendremos nuestro listado de pets.

Para añadir la petición a la colección presionamos el desplegable de ¨Save¨ y pulsamos ¨Save As¨. Nos aparecerá una pantalla donde nos preguntará a que colección queremos añadir esta petición. Desde ese momento tendremos la petición accessible en nuestra colección, la que podemos acceder siempre desde la barra lateral

Veamos una funcionalidad que nos ayudará durante nuestro desarrollo y durante el testing: los ¨Environments¨. Si os fijáis en la parte superior derecha, ahora mismo no estamos utilizando ningún ¨Environment¨. Por eso en el desplegable de la parte superior vemos seleccionado ¨No Environment¨. Imaginémonos el caso más simple, en el que el desarrollo de la API está avanzando y tenemos un entorno de pruebas en un servidor real. Instalamos una instancia de nuestra aplicación en el servidor y configuramos el servidor para que nuestra aplicación sirva peticiones en la dirección http://staging.petstore.com. Ahora si quisiéramos probar nuestra petición GET pets, en staging y luego en local, tendríamos que ir reescribiendo todas las veces la dirección en la barra de la URL. Con los ¨Environments¨ podemos crear configuraciones de entornos que usen las mismas variables pero que tengan valores diferentes dependiendo del entorno. A continuación vamos a crear un entorno ¨Pet store local¨ y otro ¨Pet store staging¨. Ambos tendrán una variable definida que llamada base_url pero en cada caso su valor será diferente (http://dev.petstore.com , http://staging.petstore.com respectivamente). Para crear los entornos, presionamos el icono de la rueda en la parte superior derecha y en la ventana de ¨Manage environments¨ presionamos ¨Add¨. Como hemos mencionado crearemos primero el entorno local llamado ¨Pet store local¨ y en la tabla que nos aparecerá más abajo indicaremos que la ¨Variable¨ será “base_url”, el ¨Initial value¨ y el ¨Current value¨ será http://dev.petstore.com. Haremos lo mismo con el ¨Pet store staging¨. Por último, editamos nuestra petición GET pet para que la url sea: {{base_url}}/pet (base_url tiene que ser este formato de template para que Postman reemplace por su valor)

Ahora cuando queremos que nuestras peticiones se sirven por nuestra aplicación local cambiaremos en el desplegable de entornos de ¨No Environment¨ a ¨Pet store local¨ y lo mismo si queremos probar en staging, pero sin necesidad de cambiar nada de nuestras peticiones. Además podemos descargarnos esta configuración de entornos en JSON, lo que será de mucha utilidad para montar tests automatizados con Newman, del que hablaremos en un momento.

Hagamos tests para nuestra petición GET pet.

Lo primero que tenemos que saber es que vamos a escribir los scripts de testing en Javascript, cosa que me parece genial, por que depende de nuestro nivel de Javascript, puede acelerar el desarrollo y mantenimiento de tests. Además, tenemos a nuestra disposición una gran variedad de snippets para empezar de inmediato a incluir tests.

Escribir un test tendrá básicamente el siguiente formato:

pm.test('nombre_del_test', function() {
// closure function que contendrá todos los asserts a ejecutar
});

Vemos un ejemplo:

pm.test("Payload must be valid and have a body", function () { 
pm.response.to.be.ok; // equivalente a decir que valide que sea 200
pm.response.to.be.withBody; // body tiene que exitir
pm.response.to.be.json; // además que sea un JSON válido
});

¿Qué pasa si guardamos este test en nuestra ventana de petición y ejecutamos otra vez la petición?

Cuando la petición se sirve, se ejecuta el test que hemos escrito y el resultado lo podemos ver en la pestaña de ¨Test Results¨. Este test se lanzará cada vez que se sirva una petición enviada GET /pet.

Añadamos más tests.

// Pedimos a nuestro response payload que tengo Content-Type en el header
pm.test("Content-Type is present", function () {
pm.response.to.have.header("Content-Type");
});
// Y comprobamos que la petición se haya servido en un determinado tiempo
pm.test("Response time is less than 200ms", function () {
pm.expect(pm.response.responseTime).to.be.below(200);
});

Genial. Podemos hacer test sobre el HTTP code de la respuesta, sobre sus Headers, sobre la velocidad en la que se sirve. Tenemos múltiples opciones. Otra parte que me parece super interesante de nuestras pruebas es probar que el contenido del payload de la respuesta se ajusta a cierto esquema, ya que queremos que pase lo que pase, nuestra API siempre sirva los recursos con el mismo esquema, con los mismos tipos, y si no es así, que nuestros tests lo detecten.

Postman integra la librería Tiny Validator que usa las especificaciones de JSON schema v4 para validar objetos JSON, lo cual nos facilita mucho el trabajo. Lo único que tenemos que hacer es crear una única vez el esquema contra el que vamos a validar nuestros recursos.

En nuestro caso, crearemos un esquema que representa la petición de GET /pet

var schema = {
"type": "array",
"items": { "$ref": "petSchema" }
};
var petSchema = {
"properties": {
"id" : { "type": "integer" },
"category": { "$ref": "categorySchema" },
"name": { "type": "string" },
"photoUrls" : { "$ref": "photosUrlSchema" },
"tags": { "$ref": "tagsSchema" },
"status": { "type": "string" }
}
};
var categorySchema = {
"properties": {
"id" : { "type": "integer" },
"name": { "type": "string" }
}
};
var photosUrlSchema = {
"type": "array",
"items": { "type": "string" }
};
var tagsSchema = {
"type": "array",
"items": { "$ref": "tagSchema" }
};
var tagSchema = {
"properties": {
"id" : { "type": "integer" },
"name": { "type": "string" }
}
};
tv4.addSchema('petSchema', petSchema);
tv4.addSchema('categorySchema', categorySchema);
tv4.addSchema('photosUrlSchema', photosUrlSchema);
tv4.addSchema('tagsSchema', tagsSchema);
tv4.addSchema('tagSchema', tagSchema);
pm.test('Pets schema is valid', function() {
pm.expect(tv4.validate(pm.response.json(), schema)).to.be.true;
});

Hemos creado los esquemas de cada propiedad de nuestro objeto pet, le hemos cargado esos esquemas al validador y lo comparemos con el contenido de la petición.

Ahora cada vez que nosotros o nuestros compañeros usen esta colección de Postman se ejecutarán todas las pruebas, ganando en calidad y en detección de errores. Pero ahora queremos ir un paso más haya y hacer que esas pruebas se lancen por líneas de comandos. Así por ejemplo, en cada deployment de nuestra API, que nuestro build server tenga una herramienta por línea de comandos para probar que nuestra API sigue sirviendo las peticiones como se espera. Para ello, utilizaremos la librería Nodejs Newman, una herramienta extensible y configurable de línea de comandos para Postman que ejecuta las peticiones de nuestra colección.

Vamos a probar cómo funciona en desarrollo local. Primero instalamos la herramienta:

npm install -g newman

Ahora lo que queremos es lanzar newman sobre nuestra colección Petstore, utilizando alguno de los entornos que hemos configurado. Para ello, tendremos que seguir los siguientes pasos:

  1. Exportamos la colección que queremos probar a JSON. Para ello iremos a la opción ¨Export collection¨, seleccionaremos la versión recomendada por Postman y descargamos la colección en JSON, por ejemplo, llamando el archivo ¨Petsore.postman_collection.json”
  2. Exportamos la configuración de nuestros ¨Environments¨, en mi caso sólo voy probar el ¨Pet store local¨. Vamos a ¨Manage Environments¨, bu buscamos nuestro ¨Pet store local¨ y pulsamos ¨Download environment¨. He llamado al archivo “Petstoredev.postman_environment.json”
  3. Probemos nuestra colección y ejecutamos nuestros tests desde línea de comandos:
newman run -e /path/Petstoredev.postman_environment.json /path/Petsore.postman_collection.json

Ya tenemos nuestro informe completo del resultado de los tests.

Como hemos visto Postman y la integración con Newman es una herramienta perfecta para incluir en nuestro workflows de desarrollo, de integración continua o de despliegues.

Espero que os haya gustado. Si queréis profundizar un poco más en el tema, darme feedback y escribiré más artículos explicando opciones y configuraciones más avanzadas. Me encanta el diseño de API´s y me gustaría compartir con vosotros lo que voy aprendiendo.

Happy Testing! :D

Me podéis contactar en Twitter: César Izquierdo