Docker en la ejecución de test de integración en NodeJS
Motivación
El objetivo de este tutorial es permitir al desarrollador de NodeJS tener unas nociones básicas de como realizar el despliegue de su aplicación a través de contenedores (docker
) y llevando la orquestación de estos con docker-compose
. Del mismo modo, este tutorial nos enseñará como tomar ventaja del uso de contenedores a la hora de ejecutar nuestros tests de integración, mejorando de este modo la calidad de nuestros desarrollos.
Requisitos
Aunque no es imprescindible, se aconseja tener conocimientos básicos de NodeJS para el seguimiento de este tutorial.
Propósito
Aunque el tutorial este basado en una aplicación desarrollada con NodeJS que se conecta a una base de datos MongoDB, la finalidad de este tutorial no es enseñar como desarrollar aplicaciones javascript
sino mostrar al desarrollador como puede tomar ventaja del uso de docker y docker-compose en el desarrollo de sus aplicaciones
Detalle del tutorial
En el tutorial desarrollaremos una API rest
desde con haciendo uso de NodeJS
y de la librería mongoose
. Usaremos express
como servidor de aplicaciones.
Estructura del proyecto
El código fuente de este proyecto, como el del resto de tutoriales desarrollados en Abirtone, puede ser descargado desde nuestro repositorio público de Github →. A continuación explicamos la estructura de directorio que encontrarás en este proyecto:
- abirtone-blog-agenda-api
- etc: Contioene los ficheros de configuración de
docker-compose
- config: Ficheros de configuración de las rutas y de neustro servior express.
- app:
- models: Modelos de datos (Definidos con mongoose)
- routes: Implementación de lógica de negocio usada por nuestra aplicación.
- test: Tests de integración implementados con
supertest
yshould
. - application.js: Fichero de arranque de nuestra aplicación.
- pacakge.json
- nodemon.json
- Dockerfile: Fichero utilizado para construir una imagen de Docker de nuestra aplicación.
- etc: Contioene los ficheros de configuración de
Implementando una API Rest con NodeJS y Mongoose
Partimos de la base de que contamos con NodeJS instalado en nuestro sistema. Para cualquier duda os sugerimos visitar la página oficial de nodejs en NodeJS Official Site
Definiendo nuestro modelo de datos
Para almancenar los datos usaremos MongoDB
como base de datos y la librería mongoose
para la definición de nuestras colecciones así como para la lectura/escritura en nuestro sistema. A continuación se muestra nuestro modelo de datos que esta definido en los siguientes ficheros:
app/models/contact.js
app/models/agenda.js
Como se puede ver en el ejemplo anterior, la definición de nuestras colecciones se realizada de modo muy sencillo, aunque para desarrollos más complejos os animamos a que realicéis nuestro cruso de especialista en nodejs.
Mongoose nos permite definir la estructura de nuestras colecciones, definiendo tipo de datos, indices, así como nodos de nuestros documentos. Para desarrollos haciendo uso de MongoDB, os recomendamos que os forméis crusos de especialista en MongoDB, dónde conseguiréis una formación que os permitirá liderar desarrollos con MongoDB.
CONFIGURACIÓN DE EXPRESS
Tal y como hemos comentado anteriormente, haremos uso de express para desplegar nuestra aplicación. Una buena práctica para organizar nuestro código es tener la configuración de express
aislada del resto de código, por eso podemos ver la configuración de nuestro servidor a continuación.
config/express.js
DEFINICIÓN DE RUTAS
Nuestra API implementará los servicios descritos a continuación.
config/routes.js
IMPLEMENTACIÓN DE LA LÓGICA DE NEGOCIO
La implementación de estos servicios puede ser encontrada en el directorio app/routes, allí encontramos 2 ficheros llamados agenda y contact que tienen la implementación de la lógica de negocio que se encargará de la comunicación con nuestra base de datos.
app/routes/agenda.js
app/routes/contact.js
Despliegue de aplicación con contenedores
Para el uso de Docker y Docker-Compose en sistemas operativos distintos a Linux utilizaremos boot2docker. Si aún no tenemos instalado en nuestro sistema Docker o boot2docker podéis echar un vistazo a los siguientes links:
En el directorio etc
encontraremos un fichero llamado docker-compose.yml
, esté, será quien orqueste los contenedores requeridos para el despliegue del sistema. Si echamos un vistazo a este fichero veremos lo siguiente:
Como podemos observar en el fichero anterior docker-compose.yml, contamos con dos contenedores:
- mongo: Se trata de una instancia de mongo que será utilizada por nuestra aplicación.
- application: Se trata de la “contenerización” de nuestra propia aplicación.
Para mongo usamos la imagen oficial, y lo indicamos así: image: mongo:latest
, mientras que para la aplicación haremos que docker-compose cree una imagen y la almacene en nuestro repositorio local build: ../
, este referencia al fichero Dockerfile
que podemos encontrar en nuestro proyecto.
Para desplegar nuestra aplicación ejecutaremos los siguientes comandos desde nuestro directorio etc
:
- docker-compose build: Creará la imagen correspondiente a nuestra aplicación.
- docker-compose up -d: Este comando lanzará los contenedores definidos en nuestro fichero
docker-compose.yml
- docker-compose ps: Muestra el estado de los contenedores definidos. Deberíamos ver 2 registros que se corresponden con el contenedor de nuestra aplicación y con la mongo definida.
- docker-compose logs: Visualizaremos los logs escrito por los contenedores. De este modo podremos visualizar los logs escritas por nuestra aplicación a través de
winston
.
Para conocer Docker en profundidad y ser capaces de hacer desarrollos de calidad usandolo os recomendamos visitéis nuestro curso de Especialista en desarrollos con Docker.
A través del comando curl podríamos verificar que nuestra aplicación ha sido desplegada correctamente, por ejemplo veamos lo siguientes comandos:
- Listado de agendas:
- Crear una nueva agenda:
- Añadir un contacto a una agenda:
Ejecución de tests de integración con contenedores
Desde un punto de vista personal, el mayor logro de Docker es ofrecernos la posibilidad de hacer testing de gran calidad, ofrenciéndonos ejecutar nuestros tests contra un entorno real y no contra uno de pruebas. En el direcorio etc
de nuestro proyecto podemos observar que tenemos otro fichero llamado docker-compose-test.yml
. Este fichero es idéntico al que utilizamos para desplegar nuestra aplicación y la única diferencia entre ambos es el valor de la variable de entorno RUN_MODE
que utilizamos para identificar que script debemos ejecutar.
Los pasos a seguir para lanzar nuestros tests haciendo uso de Docker
serían los siguientes (Suponiendo que nos encontramos en el directorio etc
):
- docker-compose -f docker-compose-test.yml build: Creará la imagen correspondiente a nuestra aplicación.
- docker-compose -f docker-compose-test.yml up -d: Este comando lanzará los contenedores definidos en nuestro fichero
docker-compose.yml
Por defecto docker-compose
utiliza el fichero llamado docker-compose-yml
, como vemos en el párrafo anterior le indicamos un fichero diferente a través del parámetro -f
.
Al ejecutar nuestros tests se creará un fichero llamado result.spec
en nuestro directorio etc/log
. En realidad, cuando ejecutamos nuestra batería de tests de integración lo que queremos saber es el resultado de nuestros tests y no preocuparnos de los problemas derivados por las herramientas. De este modo levantaremos nuestros contenedores y tendremos un report
con los resultados de nuestros tests. Esto es muy útil para herramientas de C.I. (Integración Contina) tales como Jenkins o Bamboo.
Conclusiones
- Docker nos da la posibilidad de hacer uso de la
infraestructura como código fuente
. - Docker nos permite a los desarrolladores tener
un entorno idéntico/similar a producción e identico al de cualquiera de nuestros compañeros
. Nota: Decimos similar a producción, porque probablemente en producción definamos clusteres en nuestrodocker-compose
- Garantiza la
calidad de nuestros Tests de Integración y de Aceptación
desde el momento que estamos haciendo uso de un sistema real, y no de mocks o servidores de prueba diferentes a producción. Ahorro en máquinas
que están siempre en ejecución para que podamos ejecutar nuestros tests. De este modo nosotros mismos seremos capaces de levantar y detener nuestros contenedores- Como resumen podríamos decir que los contenedores son actualmente la pieza fundamental en los procesos de
Integración continua
yDespliegue continuo
.
Deberes
Todos sabemos que los tutoriales son muy útiles, aunque realmente aprendemos cuando nos enfrentamos a desarrollos por nosotros mismos. Por eso desde este tutorial nos gustaría sugeriros las siguientes prácticas que os ayudarían a fortalecer los conocimientos adquiridos en este tutorial.
- Implementar los
tests
para el resto de servicios implementados. - Hacer uso de
ngingx
que haga deproxy
de nuestra aplicación. - ¿Porque no probar un desarrollo dnde usamos un base de datos relacional y usemos
sequealize
en lugar de una mongo?
Codigo fuente
El código fuente de este proyecto puede ser encontrado en nuestro repositorio público de Github, en el siguiente enlace: Abirtone Blog Agenda API**
Sobre el autor de este post
El autor de este post es Iván Corrales especialista en sistemas de Q&A y DevOps, además de ser el profesor de en Abirtone de cursos como Cucumber →.