CI-CD para DWEC

De Jose Castillo Aliaga
Ir a la navegación Ir a la búsqueda

El módulo DWEC se centra en la creación de páginas web en el lado del cliente. Este enfoque implica, en su mayoría, la programación en Javascript para interactuar dinámicamente con el navegador y proporcionar una experiencia de usuario rica. Sin embargo, el desarrollo web moderno abarca más que simplemente escribir código Javascript.

Para abordar la complejidad y las múltiples etapas involucradas en el desarrollo y despliegue de una aplicación web, así como para mejorar la calidad del software resultante, se recurre a los procesos de Integración Continua y Despliegue Continuo (CI/CD). Estos procesos permiten automatizar y estandarizar muchas tareas asociadas con el desarrollo de software, desde la escritura de código hasta la entrega al usuario final.

Estos son algunos de los pasos típicos en el desarrollo web

  • Crear el proyecto: Iniciar un nuevo proyecto web utilizando herramientas como npm, lo que establece la estructura inicial del proyecto y crea el archivo `package.json` que contiene la información del proyecto y sus dependencias.
  • Instalar dependencias: Utilizar npm o yarn para instalar las dependencias necesarias para el desarrollo del proyecto, tanto las dependencias de desarrollo (por ejemplo, herramientas de construcción, linters, pruebas) como las dependencias específicas del proyecto (por ejemplo, frameworks o librerías JavaScript).
  • Instalar y configurar frameworks o librerías: Instalar y configurar cualquier framework o librería adicional necesaria para el desarrollo de la aplicación web, como React, Vue.js o Angular, junto con cualquier complemento o biblioteca asociada.
  • Escribir código correcto: Desarrollar el código de la aplicación web siguiendo las mejores prácticas y estándares de codificación, lo que implica escribir código limpio, modular y mantenible que cumpla con los requisitos del proyecto.
  • Utilizar linters: Configurar y utilizar linters como ESLint para asegurar que el código JavaScript cumpla con las convenciones de estilo y las reglas de calidad definidas, ayudando a detectar y corregir errores, así como a mantener la coherencia en el código base.
  • Escribir pruebas (Test-Driven Development - TDD): Implementar pruebas unitarias y de integración para validar el comportamiento del código y garantizar su correcto funcionamiento, utilizando el enfoque de desarrollo guiado por pruebas (TDD) para mejorar la calidad del software y su mantenibilidad.
  • Crear el repositorio git y gestionarlo: Inicializar un repositorio git para controlar el historial de cambios en el código fuente y facilitar la colaboración entre miembros del equipo, utilizando comandos git para crear commits, ramas y fusiones según sea necesario.
  • Realizar pruebas de integración: Ejecutar pruebas de integración para asegurar que los diferentes componentes de la aplicación funcionen correctamente juntos y que no se produzcan conflictos o errores de interoperabilidad.
  • Crear el bundle: Utilizar herramientas de construcción como Vite, Webpack o Parcel para crear un bundle optimizado de los activos de la aplicación, incluidos los archivos HTML, CSS, JavaScript y recursos estáticos.
  • Configurar el servidor: Configurar un servidor web para servir los archivos estáticos de la aplicación y manejar solicitudes HTTP, lo que puede incluir la configuración de servidores locales para desarrollo y pruebas, así como la configuración de servidores de producción para el despliegue.
  • Subir los archivos estáticos al servidor: Transferir los archivos estáticos resultantes del proceso de construcción (bundle) al servidor de producción o a un entorno de pruebas, lo que puede implicar la implementación manual o automatizada de los archivos en el servidor remoto.
  • Poner en producción: Realizar pruebas finales en el entorno de producción para asegurarse de que la aplicación web funcione como se espera y, si todo está correcto, lanzarla para que los usuarios finales la utilicen.

Como se puede ver, es muy difícil que los programadores lo hagan todo bien todo el tiempo.

La parte del despliegue se trata con más profundidad en el módulo de despliegue del ciclo, pero es preciso desplegar rudimentariamente o en alguna plataforma alternativa que simplifique esta tarea. De esta manera se puede ver todo el proceso sin perder demasiado tiempo. Una buena idea puede ser desplegar en Deta, como explico en este manual: Prototipo de projecto con JS, Bootstrap, Vite, Deta i Supabase.

En este manual vamos a explorar algunas técnicas para implementar el CI/CD en clase.

Linting y Formatting

El linting y el formateo son esenciales para mantener el código consistente y limpio. Cada miembro del equipo debe seguir las mismas reglas y convenciones al escribir código. La consistencia en la base de código es fundamental para:

  • No generar confusión sobre cómo escribir un fragmento de código específico en tu aplicación cuando incorporas a un nuevo miembro del equipo.
  • No tener que documentar múltiples formas de hacer lo mismo.

El linter más conocido de Javascript es ESLint. Para ponerlo en funcionamiento:

npm install eslint --save-dev
npm init @eslint/config@latest

Y luego instalamos la extensión de ESLint en el VSCode.

El linter marca como errores también los fallos en estilo. Desde cosas como no declarar correctamente variables que pueden provocar problemas en el futuro a reglas de estilo como los espacios que hay que dejar. Esas reglas de estilo están definidas en su configuración y se pueden modificar al gusto. Algunas colecciones de reglas conocidas y recomendables son las de Google AirBnB que están codificadas en EsLint por defecto.

Con la configuración anterior, tenemos el código lleno de "errores", pero nos deja usarlo. No obstante, si queremos que no se pueda poner código en producción que no cumpla las reglas de estilo, podemos crear un Hook de pre-commit que evalue el linter y no deje ejecutar el push si no se cumplen.

Los "hooks" en Git son scripts personalizados que se ejecutan automáticamente en respuesta a eventos específicos, como confirmar cambios o fusionar ramas, proporcionando automatización y personalización del flujo de trabajo de Git. Se almacenan en la carpeta .git/hooks y pueden realizar acciones antes o después de eventos como pre-commit, post-commit, pre-push, entre otros.

Hay una aplicación que simplifica la creación de estos Hooks, se llama Husky.

npm install --save-dev husky
npx husky init

Estos comandos instalan Husky y crean una carpeta .husky donde hay un archivo pre-commit en el que podemos añadir comandos a ejecutar en ese Hook. Es similar a los scrips que se pueden poner en package.json. Con estos comandos podemos ejecutar Eslint, tests o lo que se necesite.

En nuestro caso

npx eslint

Al automatizar el Linter, estamos evitando tener que comprobarlo cada vez, lo cual nos aproxima a un ciclo CI/CD.

Además de pasar el linter, podemos formatear el código con Prettier: Instalar:

npm install --save-dev --save-exact prettier

Comando para el pre-commit:

npx prettier . --write


Unit Testing

Siendo totalmente rigurosos con estas metodologías, deberíamos aplicar técnicas TDD en nuestro desarrollo. Por tanto, los test unitarios forman parte de la etapa de desarrollo, incluso son anteriores a la creación del código funcional. Generalmente se configuran y programan los test i se mantiene una terminal o una web abierta mientras se programa. De esta manera, cualquier cambio en el código se testa y de un vistazo vemos si sigue funcionando o si todavía no funciona nuestro código.

Por otra parte, no podemos confiar en que todos los desarrolladores van a hacer caso siempre a los tests o que no se van a equivocar justo antes de hacer un "commit". Por tanto, podemos añadir los tests a un hook como en la sección anterior para evitar hacer el commit o podemos configurar github actions para testar en el propio repositorio. Esto nos permitirá también mantener un registro de los "commits" y hacer otras acciones como hacer el build y posteriormente poner el producción.

Instalar los tests

En este ejemplo, vamos a instalar Vitest, ya que proporciona un soporte nativo a módulos ESM y una interfaz por terminal cómoda para lo que necesitamos. Además, funciona perfectamente en Github Actions.

npm install -D vitest

en un fichero que tenga la palabra .test.js pondremos los test que nos interesan.

import { expect, test } from 'vitest'
...
test('test description', () => {
 ...
})

Si queremos ejecutar los tests en la terminal local, pondremos en el package.json:

 {
  "scripts": {
    "test": "vitest"
  }
 }

Luego ya lo ejecutamos:

npm run test

Configurar los tests en Github Actions

Seguiremos este tutorial: [1] Si lo hacemos más o menos hasta el final, los push provocarán que se ejecute el action y muestre el resultado, pero no hace ningún test. En nuestro caso, para hacer los test, podemos modificar el .yaml para añadirlos:

name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
      - name: Check out repository code
        uses: actions/checkout@v4
      - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: List files in the repository
        run: |
          ls ${{ github.workspace }}
      - run: echo "🍏 This job's status is ${{ job.status }}."
  Test:
    runs-on: ubuntu-latest
    strategy:
     matrix:
       node-version: ['20.x']
    steps:
    - uses: actions/checkout@v4
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node-version }}  
    - name: Install dependencies frontend
      working-directory: .
      run: npm ci
    - name: nmp test
      working-directory: .
      run: npm test

Si no pasa el test mostrará un error en la pestaña de actions i si lo pasa, el push se ejecuta.

Integration tests

La herramientas para hacer tests de integración pueden ser las mismas que los unitarios o alguna más especializada. Estas herramientas, en el frontend pueden probar cosas como:

  • La interacción y navegación del usuario.
  • El funcionamiento de los formularios.
  • La renderización de vistas.

En este caso, normalmente es necesario falsear (mocking) las peticiones a la red. Para ello, podemos usar herramientas como msw. También se pueden mockear funciones y eventos, así como usar espías para ver si se han ejecutado y cuantas veces.