Creación de un flujo de trabajo de prueba de integración continua mediante acciones de GitHub

Publicado: 2022-03-10
Resumen rápido ↬ Con la ayuda de este tutorial, puede aprender cómo crear un flujo de trabajo de integración continua para su API REST de Node JS mediante GitHub Actions y cómo informar la cobertura de prueba con Coveralls.

Al contribuir a proyectos en plataformas de control de versiones como GitHub y Bitbucket, la convención es que existe una rama principal que contiene el código base funcional. Luego, hay otras ramas en las que varios desarrolladores pueden trabajar en copias de la principal para agregar una nueva función, corregir un error, etc. Tiene mucho sentido porque se vuelve más fácil monitorear el tipo de efecto que tendrán los cambios entrantes en el código existente. Si hay algún error, se puede rastrear y corregir fácilmente antes de integrar los cambios en la rama principal. Puede llevar mucho tiempo revisar cada línea de código manualmente en busca de errores o fallas, incluso para un proyecto pequeño. Ahí es donde entra la integración continua.

¿Qué es la integración continua (CI)?

“La integración continua (CI) es la práctica de automatizar la integración de cambios de código de múltiples colaboradores en un solo proyecto de software”.

— Atlassian.com

La idea general detrás de la integración continua (CI) es garantizar que los cambios realizados en el proyecto no "rompan la compilación", es decir, arruinen la base de código existente. La implementación de la integración continua en su proyecto, dependiendo de cómo configure su flujo de trabajo, crearía una compilación cada vez que alguien realice cambios en el repositorio.

Entonces, ¿qué es una compilación?

Una compilación, en este contexto, es la compilación del código fuente en un formato ejecutable. Si tiene éxito, significa que los cambios entrantes no tendrán un impacto negativo en el código base, y están listos para comenzar. Sin embargo, si la compilación falla, los cambios deberán volver a evaluarse. Por eso es recomendable realizar cambios en un proyecto trabajando en una copia del proyecto en una rama diferente antes de incorporarlo al código base principal. De esta manera, si la compilación falla, sería más fácil averiguar de dónde proviene el error y tampoco afectará su código fuente principal.

“Cuanto antes detecte los defectos, más barato será repararlos”.

— David Farley, Entrega continua: Versiones de software confiables a través de la automatización de compilación, prueba e implementación

Hay varias herramientas disponibles para ayudar a crear una integración continua para su proyecto. Estos incluyen Jenkins, TravisCI, CircleCI, GitLab CI, GitHub Actions, etc. Para este tutorial, usaré GitHub Actions.

Acciones de GitHub para la integración continua

CI Actions es una característica bastante nueva en GitHub y permite la creación de flujos de trabajo que ejecutan automáticamente la compilación y las pruebas de su proyecto. Un flujo de trabajo contiene uno o más trabajos que se pueden activar cuando ocurre un evento. Este evento podría ser un envío a cualquiera de las sucursales en el repositorio o la creación de una solicitud de extracción. Explicaré estos términos en detalle a medida que avancemos.

¡Empecemos!

requisitos previos

Este es un tutorial para principiantes, por lo que hablaré principalmente sobre GitHub Actions CI en un nivel superficial. Los lectores ya deberían estar familiarizados con la creación de una API REST de Node JS utilizando la base de datos PostgreSQL, Sequelize ORM y la escritura de pruebas con Mocha y Chai.

También debe tener lo siguiente instalado en su máquina:

  • NodoJS,
  • postgresql,
  • MNP,
  • VSCode (o cualquier editor y terminal de su elección).

Usaré una API REST que ya creé llamada countries-info-api . Es una API simple sin autorizaciones basadas en roles (como en el momento de escribir este tutorial). Esto significa que cualquiera puede agregar, eliminar y/o actualizar los detalles de un país. Cada país tendrá una identificación (UUID autogenerado), nombre, capital y población. Para lograr esto, utilicé Node js, express js framework y Postgresql para la base de datos.

Explicaré brevemente cómo configuro el servidor, la base de datos antes de comenzar a escribir las pruebas para la cobertura de la prueba y el archivo de flujo de trabajo para la integración continua.

Puede clonar el repositorio de countries-info-api para seguir o crear su propia API.

Tecnología utilizada : Node Js, NPM (un administrador de paquetes para Javascript), base de datos Postgresql, Sequelize ORM, Babel.

¡Más después del salto! Continúe leyendo a continuación ↓

Configuración del servidor

Antes de configurar el servidor, instalé algunas dependencias de npm.

 npm install express dotenv cors npm install --save-dev @babel/core @babel/cli @babel/preset-env nodemon

Estoy usando Express Framework y escribiendo en formato ES6, por lo que necesitaré Babeljs para compilar mi código. Puedes leer la documentación oficial para saber más sobre cómo funciona y cómo configurarlo para tu proyecto. Nodemon detectará cualquier cambio realizado en el código y reiniciará automáticamente el servidor.

Nota : los paquetes Npm instalados con el --save-dev solo son necesarios durante las etapas de desarrollo y se ven en devDependencies en el archivo package.json .

Agregué lo siguiente a mi archivo index.js :

 import express from "express"; import bodyParser from "body-parser"; import cors from "cors"; import "dotenv/config"; const app = express(); const port = process.env.PORT; app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); app.get("/", (req, res) => { res.send({message: "Welcome to the homepage!"}) }) app.listen(port, () => { console.log(`Server is running on ${port}...`) })

Esto configura nuestra API para que se ejecute en lo que esté asignado a la variable PORT en el archivo .env . Aquí también es donde declararemos variables a las que no queremos que otros tengan acceso fácilmente. El paquete dotenv npm carga nuestras variables de entorno desde .env .

Ahora, cuando ejecuto npm run start en mi terminal, obtengo esto:

Servidor en ejecución
Servidor en funcionamiento en el puerto 3000. (Vista previa grande)

Como puede ver, nuestro servidor está en funcionamiento. ¡Hurra!

Este enlace https://127.0.0.1:your_port_number/ en su navegador web debería devolver el mensaje de bienvenida. Es decir, mientras el servidor esté funcionando.

Navegador
Página principal. (Vista previa grande)

A continuación, Base de datos y modelos.

Creé el modelo de país usando Sequelize y me conecté a mi base de datos de Postgres. Sequelize es un ORM para Nodejs. Una gran ventaja es que nos ahorra el tiempo de escribir consultas SQL sin formato.

Dado que estamos usando Postgresql, la base de datos se puede crear a través de la línea de comando psql usando el comando CREATE DATABASE database_name nombre_base_de_datos. Esto también se puede hacer en su terminal, pero prefiero PSQL Shell.

En el archivo env, configuraremos la cadena de conexión de nuestra base de datos, siguiendo este formato a continuación.

 TEST_DATABASE_URL = postgres://<db_username>:<db_password>@127.0.0.1:5432/<database_name>

Para mi modelo, seguí este tutorial de secuela. Es fácil de seguir y explica todo sobre la configuración de Sequelize.

A continuación, escribiré pruebas para el modelo que acabo de crear y configuraré la cobertura en Coverall.

Redacción de exámenes y cobertura de informes

¿Por qué escribir pruebas? Personalmente, creo que escribir pruebas lo ayuda como desarrollador a comprender mejor cómo se espera que su software funcione en manos de su usuario porque es un proceso de intercambio de ideas. También te ayuda a descubrir errores a tiempo.

Pruebas:

Existen diferentes métodos de prueba de software, sin embargo, para este tutorial, utilicé pruebas unitarias y de extremo a extremo.

Escribí mis pruebas utilizando el marco de pruebas de Mocha y la biblioteca de aserciones de Chai. También instalé sequelize-test-helpers para ayudar a probar el modelo que creé usando sequelize.define .

Cobertura de prueba:

Es recomendable verificar la cobertura de su prueba porque el resultado muestra si nuestros casos de prueba realmente cubren el código y también cuánto código se usa cuando ejecutamos nuestros casos de prueba.

Usé Istanbul (una herramienta de cobertura de prueba), nyc (cliente CLI de Instabul) y Coveralls.

De acuerdo con los documentos, Istanbul instrumenta su código JavaScript ES5 y ES2015+ con contadores de línea, para que pueda rastrear qué tan bien sus pruebas unitarias ejercitan su base de código.

En mi archivo package.json , el script de prueba ejecuta las pruebas y genera un informe.

 { "scripts": { "test": "nyc --reporter=lcov --reporter=text mocha -r @babel/register ./src/test/index.js" } }

En el proceso, creará una carpeta .nyc_output que contiene la información de cobertura sin procesar y una carpeta de coverage que contiene los archivos del informe de cobertura. Ambos archivos no son necesarios en mi repositorio, así que los coloqué en el archivo .gitignore .

Ahora que hemos generado un informe, tenemos que enviarlo a Coveralls. Una cosa interesante sobre Overoles (y otras herramientas de cobertura, supongo) es cómo informa la cobertura de su prueba. La cobertura se desglosa archivo por archivo y puede ver la cobertura relevante, las líneas cubiertas y perdidas, y qué cambió en la cobertura construida.

Para comenzar, instale el paquete overol npm. También debe iniciar sesión en overoles y agregarle el repositorio.

repositorio de overoles
Repo conectado a Overoles. (Vista previa grande)

Luego configure overoles para su proyecto javascript creando un archivo coveralls.yml en su directorio raíz. Este archivo contendrá su repo-token obtenido de la sección de configuración para su repositorio en overoles.

Otro script necesario en el archivo package.json son los scripts de cobertura. Este script será útil cuando estemos creando una compilación a través de Acciones.

 { "scripts": { "coverage": "nyc npm run test && nyc report --reporter=text-lcov --reporter=lcov | node ./node_modules/coveralls/bin/coveralls.js --verbose" } }

Básicamente, ejecutará las pruebas, obtendrá el informe y lo enviará a overoles para su análisis.

Ahora al punto principal de este tutorial.

Crear archivo de flujo de trabajo de Node JS

En este punto, hemos configurado los trabajos necesarios que ejecutaremos en nuestra acción de GitHub. (¿Se pregunta qué significan los "trabajos"? Siga leyendo).

GitHub ha facilitado la creación del archivo de flujo de trabajo al proporcionar una plantilla de inicio. Como se ve en la página Acciones, hay varias plantillas de flujo de trabajo que sirven para diferentes propósitos. Para este tutorial, usaremos el flujo de trabajo de Node.js (que GitHub ya sugirió amablemente).

página de acciones
Página de acciones de GitHub. (Vista previa grande)

Puede editar el archivo directamente en GitHub, pero yo crearé manualmente el archivo en mi repositorio local. La carpeta .github/workflows que contiene el archivo node.js.yml estará en el directorio raíz.

Este archivo ya contiene algunos comandos básicos y el primer comentario explica lo que hacen.

 # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node

Le haré algunos cambios para que, además del comentario anterior, también tenga cobertura.

Mi archivo .node.js.yml :

 name: NodeJS CI on: ["push"] jobs: build: name: Build runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm run build --if-present - run: npm run coverage - name: Coveralls uses: coverallsapp/github-action@master env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} COVERALLS_GIT_BRANCH: ${{ github.ref }} with: github-token: ${{ secrets.GITHUB_TOKEN }}

¿Qué significa esto?

Vamos a desglosarlo.

  • name
    Este sería el nombre de su flujo de trabajo (NodeJS CI) o trabajo (compilación) y GitHub lo mostrará en la página de acciones de su repositorio.
  • on
    Este es el evento que desencadena el flujo de trabajo. Esa línea en mi archivo básicamente le dice a GitHub que active el flujo de trabajo cada vez que se realiza un envío a mi repositorio.
  • jobs
    Un flujo de trabajo puede contener al menos uno o más trabajos y cada trabajo se ejecuta en un entorno especificado por runs-on . En el ejemplo de archivo anterior, solo hay un trabajo que ejecuta la compilación y también ejecuta la cobertura, y se ejecuta en un entorno de Windows. También puedo separarlo en dos trabajos diferentes como este:

Archivo Node.yml actualizado

 name: NodeJS CI on: [push] jobs: build: name: Build runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm run build --if-present - run: npm run test coverage: name: Coveralls runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: coverallsapp/github-action@master env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} with: github-token: ${{ secrets.GITHUB_TOKEN }}
  • env
    Contiene las variables de entorno que están disponibles para todos o trabajos y pasos específicos en el flujo de trabajo. En el trabajo de cobertura, puede ver que las variables de entorno se han "ocultado". Se pueden encontrar en la página de secretos de su repositorio en la configuración.
  • steps
    Esto básicamente es una lista de los pasos a seguir cuando se ejecuta ese trabajo.
  • El trabajo de build hace varias cosas:
    • Utiliza una acción de pago (v2 significa la versión) que literalmente verifica su repositorio para que su flujo de trabajo pueda acceder a él;
    • Utiliza una acción de configuración de nodo que configura el entorno de nodo que se utilizará;
    • Ejecuta scripts de instalación, compilación y prueba que se encuentran en nuestro archivo package.json.
  • coverage
    Esto utiliza una acción de coverallsapp que publica los datos de cobertura LCOV de su conjunto de pruebas en coveralls.io para su análisis.
trabajos exitosos
Todos los trabajos se ejecutan correctamente. (Vista previa grande)

Inicialmente presioné mi rama feat-add-controllers-and-route y olvidé agregar el repo_token de Coveralls a mi archivo .coveralls.yml , así que recibí el error que puede ver en la línea 132.

compilación fallida
Trabajo fallido debido a un error en el archivo de configuración de los overoles. (Vista previa grande)
 Bad response: 422 {"message":"Couldn't find a repository matching this job.","error":true}

Una vez que agregué repo_token , mi compilación pudo ejecutarse correctamente. Sin este token, los overoles no podrían informar correctamente mi análisis de cobertura de prueba. Menos mal que nuestro GitHub Actions CI señaló el error antes de que se enviara a la rama principal.

Construcción exitosa
Error arreglado, trabajo exitoso. ¡Hurra! (Vista previa grande)

NB: Estos fueron tomados antes de separar el trabajo en dos trabajos. Además, pude ver el resumen de cobertura y el mensaje de error en mi terminal porque agregué el indicador --verbose al final de mi script de cobertura.

Conclusión

Podemos ver cómo configurar la integración continua para nuestros proyectos y también integrar la cobertura de prueba utilizando las Acciones disponibles por GitHub. Hay muchas otras formas en que esto se puede ajustar para adaptarse a las necesidades de su proyecto. Aunque el repositorio de muestra utilizado en este tutorial es un proyecto realmente menor, puede ver cuán esencial es la integración continua incluso en un proyecto más grande. Ahora que mis trabajos se ejecutaron correctamente, confío en fusionar la rama con mi rama principal. Aún así, le recomendaría que también lea los resultados de los pasos después de cada ejecución para ver que es completamente exitoso.