0Pricing
DevOps Bootcamp · Lección

Ejecución de pruebas de shell en pipelines de CI

Integre ShellCheck y Bats en GitHub Actions para exigir comprobaciones correctas en cada cambio del shell.

Ejecución de pruebas de shell en pipelines de CI es una lección gratuita de DevOps Bootcamp en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de DevOps Bootcamp, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de DevOps Bootcamp incluye 4 lecciones en total.

Por qué es importante CI para los scripts de shell

Los scripts de shell son código y, como todo código, merecen controles de calidad automatizados. Sin CI, un error tipográfico en un script de despliegue puede llegar silenciosamente a producción y provocar una interrupción del servicio a las 3 de la mañana.

Una canalización de CI sólida para proyectos de Bash aplica dos controles en cada solicitud de incorporación de cambios:

  • Análisis estático mediante ShellCheck: detecta errores de sintaxis, patrones inseguros y problemas de portabilidad POSIX antes de que el script llegue a ejecutarse.
  • Pruebas unitarias/de integración mediante Bats (Bash Automated Testing System): ejecuta sus funciones y comprueba que su comportamiento sea correcto.

Juntos forman una red de seguridad que permite refactorizar sin temor y agiliza la incorporación de nuevos colaboradores. En esta lección se integran ambas herramientas en GitHub Actions, la plataforma de CI gratuita más habitual para proyectos de código abierto y equipos pequeños.

Introducción a GitHub Actions para proyectos de shell

GitHub Actions es una solución de CI/CD basada en eventos e integrada en GitHub. Un workflow es un archivo YAML almacenado en .github/workflows/. Se activa mediante eventos (push, pull_request, etc.) y ejecuta jobs en ejecutores alojados.

Estos son los conceptos clave que necesita:

  • on: — el activador (por ejemplo, push, pull_request)
  • jobs: — unidades de trabajo paralelas, cada una en una máquina virtual nueva
  • steps: — comandos de shell secuenciales o actions reutilizables dentro de un job
  • runs-on: — la imagen del ejecutor (usamos ubuntu-latest)

Los archivos de workflow deben confirmarse en el repositorio. GitHub los detecta automáticamente; no se requiere configuración externa.

# Minimal skeleton — .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  shell-checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: echo "Steps go here"

Instalación de ShellCheck en un workflow

ShellCheck viene preinstalado en los ejecutores ubuntu-latest, por lo que en la mayoría de los casos no necesita ningún paso de instalación. Sin embargo, la versión preinstalada puede estar atrasada respecto a la versión más reciente. Para obtener compilaciones reproducibles, fije una versión específica.

Hay dos estrategias de instalación:

  • Usar el binario preinstalado — es la opción más sencilla y suficiente para la mayoría de los proyectos.
  • Instalar una versión fijada mediante el archivo tar de la versión oficial de GitHub — garantiza la misma versión del linter en el entorno local y en CI.

El paso siguiente muestra el enfoque con una versión fijada mediante una cadena de versión constante almacenada como variable de entorno, de modo que actualizarla solo requiera cambiar una línea.

# .github/workflows/ci.yml  — ShellCheck install step
- name: Install ShellCheck
  env:
    SC_VERSION: v0.10.0
  run: |
    curl -sSfL \
      "https://github.com/koalaman/shellcheck/releases/download/${SC_VERSION}/shellcheck-${SC_VERSION}.linux.x86_64.tar.xz" \
      | tar -xJf - --strip-components=1 -C /usr/local/bin shellcheck-${SC_VERSION}/shellcheck
    shellcheck --version

Ejecución de ShellCheck en todos los scripts

Después de la instalación, necesita un paso que encuentre y analice todos los scripts de shell del repositorio. Use find para localizar los archivos y, después, páselos mediante una tubería a shellcheck.

Estas son las opciones importantes:

  • -e SC2034 — excluye una regla específica (úselo con moderación y acompañada de un comentario).
  • --severity=warning — hace que falle solo ante advertencias o errores, ignorando las sugerencias de style.
  • -x — sigue las directivas source para analizar también los archivos incluidos.

Si shellcheck encuentra algún problema, termina con un código distinto de cero, lo que hace que el paso de CI falle automáticamente; no se necesita lógica adicional.

# .github/workflows/ci.yml  — ShellCheck lint step
- name: Lint shell scripts
  run: |
    # Find all .sh files and files with a bash/sh shebang
    mapfile -t scripts < <(
      find . -type f -name '*.sh' -not -path './.git/*'
    )
    if [[ ${#scripts[@]} -eq 0 ]]; then
      echo 'No shell scripts found — skipping.'
      exit 0
    fi
    echo "Linting ${#scripts[@]} file(s)..."
    shellcheck --severity=warning -x "${scripts[@]}"

¿Qué es Bats y cómo funciona?

Bats (Bash Automated Testing System) es un framework de pruebas para Bash compatible con TAP. Cada archivo de pruebas es un archivo .bats que contiene bloques @test.

Una prueba se aprueba cuando el cuerpo termina con el código 0 y falla cuando termina con un código distinto de cero. Bats proporciona variables y funciones auxiliares:

  • $status — código de salida del último comando run.
  • $output — salida estándar y de error combinadas del último comando run.
  • $lines — matriz de líneas de salida.
  • run <cmd> — ejecuta un comando sin hacer que la prueba falle cuando termina con un código distinto de cero.

La función auxiliar run es esencial: sin ella, un comando fallido abortaría la prueba antes de que pudiera inspeccionar $status.

#!/usr/bin/env bats
# tests/greet.bats

setup() {
  # Runs before every @test block
  source "${BATS_TEST_DIRNAME}/../lib/greet.sh"
}

@test "greet outputs hello with the given name" {
  run greet "Alice"
  [ "$status" -eq 0 ]
  [ "$output" = "Hello, Alice!" ]
}

@test "greet fails when no argument is provided" {
  run greet
  [ "$status" -eq 1 ]
  [[ "$output" == *"Usage"* ]]
}

Instalación de Bats-Core mediante un submódulo de Git

La forma canónica de añadir Bats a un proyecto es mediante un submódulo de Git. Esto fija un commit específico, mantiene idéntica la versión del ejecutor y la del entorno de desarrollo local, y evita depender de gestores de paquetes.

Ejecute estos comandos una vez en el entorno local y, después, confirme el resultado:

  • git submodule add https://github.com/bats-core/bats-core test/bats
  • git submodule add https://github.com/bats-core/bats-support test/test_helper/bats-support
  • git submodule add https://github.com/bats-core/bats-assert test/test_helper/bats-assert

En CI, restaure los submódulos con actions/checkout@v4 y la opción submodules: recursive. El paso siguiente muestra la configuración completa del checkout.

# .github/workflows/ci.yml  — checkout with submodules
- name: Checkout repository
  uses: actions/checkout@v4
  with:
    submodules: recursive   # restores bats-core + helpers

Ejecución de pruebas de Bats en CI

Una vez que Bats esté disponible (mediante un submódulo o la instalación de un paquete), ejecutar las pruebas requiere un solo comando. Indique un directorio y Bats descubrirá de forma recursiva todos los archivos .bats con la opción --recursive.

La opción --formatter tap genera el formato TAP (Test Anything Protocol), que muchos sistemas de CI pueden analizar para elaborar informes de pruebas. El formateador pretty predeterminado es más adecuado para la lectura humana en los registros sin procesar.

Use --timing para detectar pronto las pruebas lentas: una prueba que tarda más de 5 segundos suele indicar una llamada de red no deseada o la ausencia de un mock.

# .github/workflows/ci.yml  — Bats test step
- name: Run Bats tests
  run: |
    # If installed as a submodule:
    ./test/bats/bin/bats \
      --recursive \
      --timing \
      tests/

    # If installed via apt or brew (alternative):
    # bats --recursive --timing tests/

Un workflow completo: ShellCheck + Bats

Ahora combine todo en un único archivo de workflow listo para producción. Estas son las prácticas recomendadas aplicadas:

  • Dos jobs independientes (lint y test) se ejecutan en paralelo, lo que proporciona comentarios más rápidos.
  • El job test declara needs: lint, por lo que las pruebas solo se ejecutan después de que el análisis haya terminado correctamente; así se evita desperdiciar minutos del ejecutor en código obviamente defectuoso.
  • Las versiones fijadas de las actions (@v4) evitan fallos inesperados causados por actualizaciones ascendentes.
  • Un bloque permissions: restringe el token del workflow al mínimo necesario.
# .github/workflows/ci.yml
name: Shell CI

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

jobs:
  lint:
    name: ShellCheck
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run ShellCheck
        run: |
          mapfile -t scripts < <(find . -name '*.sh' -not -path './.git/*')
          [[ ${#scripts[@]} -gt 0 ]] && shellcheck --severity=warning -x "${scripts[@]}"

  test:
    name: Bats Tests
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - name: Run tests
        run: ./test/bats/bin/bats --recursive --timing tests/

Almacenamiento en caché de dependencias para acelerar las ejecuciones

Cuando los auxiliares de Bats u otras herramientas se instalan mediante un gestor de paquetes dentro del workflow, el almacenamiento en caché acelera considerablemente las ejecuciones posteriores. GitHub Actions proporciona la action actions/cache para este fin.

Aspectos clave para un almacenamiento en caché eficaz:

  • Use una clave de caché que incluya el sistema operativo, el nombre de la herramienta y un hash del archivo de bloqueo, de modo que la caché se invalide automáticamente cuando cambien las dependencias.
  • Un valor alternativo en restore-keys permite que el workflow use una caché obsoleta en lugar de empezar desde cero cuando no hay coincidencias.
  • Para los submódulos de Git, la caché rara vez es necesaria porque su checkout es rápido. La caché resulta más útil para instalaciones de npm, pip o herramientas compiladas.
# .github/workflows/ci.yml  — cache step example
- name: Cache Bats npm helpers
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-bats-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-bats-

- name: Install helpers
  run: npm ci  # uses cache when available

Protección de ramas: imposición de comprobaciones correctas

Un workflow de CI que no bloquea las fusiones es, como mucho, meramente informativo. Las reglas de protección de ramas de GitHub convierten sus comprobaciones en controles obligatorios.

Para configurarlas, vaya a Settings → Branches → Add rule para main y, después, active:

  • Require status checks to pass before merging — seleccione ShellCheck y Bats Tests por nombre.
  • Require branches to be up to date before merging — evita que una solicitud de incorporación de cambios que superó las comprobaciones sobre una base obsoleta introduzca código defectuoso.
  • Do not allow bypassing the above settings — aplica las reglas incluso a los administradores del repositorio.

Con estas reglas activadas, la única forma de fusionar cambios es mediante una solicitud de incorporación de cambios con todos los jobs de CI en verde, exactamente la red de seguridad que necesita.

Depuración local de pasos de CI fallidos

Cuando una ejecución de CI falla, la forma más rápida de corregirla es reproducir el fallo localmente antes de enviar otro commit. Hay dos técnicas:

  • Ejecute los comandos exactos del paso fallido en su terminal: CI ejecuta un shell normal, por lo que los comandos se pueden reproducir copiándolos y pegándolos.
  • Use act: una herramienta que ejecuta workflows de GitHub Actions localmente dentro de Docker y ofrece la coincidencia más cercana posible con el entorno del ejecutor alojado.

Una causa habitual de fallos que solo aparecen en CI es la discrepancia entre versiones de herramientas en su Mac (por ejemplo, find de BSD en macOS frente a find de GNU en Ubuntu). Pruebe siempre con opciones --posix o use act para ejecutar localmente la imagen de Ubuntu.

#!/usr/bin/env bash
# run_ci_locally.sh  — mimic the CI lint step on your machine
set -euo pipefail

echo '=== ShellCheck ==='
mapfile -t scripts < <(find . -name '*.sh' -not -path './.git/*')
if [[ ${#scripts[@]} -eq 0 ]]; then
  echo 'No .sh files found.'
else
  shellcheck --severity=warning -x "${scripts[@]}"
  echo "Linted ${#scripts[@]} file(s) — OK"
fi

echo '=== Bats ==='
./test/bats/bin/bats --recursive --timing tests/

Comprobación de conocimientos: conceptos de la canalización de CI

Compruebe su comprensión de la integración de ShellCheck y Bats en GitHub Actions.

Recapitulación: CI de shell con ShellCheck y Bats

En esta lección ha creado una canalización de CI completa para proyectos de Bash mediante GitHub Actions. Estos son los temas cubiertos:

  • Conceptos básicos de GitHub Actions — el YAML del workflow se encuentra en .github/workflows/, se activa mediante push y pull_request, y ejecuta jobs en ejecutores ubuntu-latest.
  • ShellCheck — viene preinstalado en los ejecutores de Ubuntu; use find para descubrir scripts y --severity=warning -x como control práctico de lint.
  • Bats mediante un submódulo — fije bats-core y sus auxiliares como submódulos de Git; restáurelos en CI con submodules: recursive en la action de checkout.
  • Orden de los jobs — use needs: para que las pruebas se ejecuten solo después de que el análisis haya terminado correctamente, manteniendo una respuesta rápida y evitando desperdiciar recursos de cómputo.
  • Protección de ramas — imponga comprobaciones de estado en la configuración de GitHub para que ninguna solicitud de incorporación de cambios pueda fusionarse sin un CI en verde.
  • Reproducción local — copie directamente los comandos de CI en su terminal o use act para depurar fallos sin commits adicionales.

Con esta canalización implementada, cada cambio en el shell se valida automáticamente antes de llegar a su rama principal.

Preguntas frecuentes

¿La lección «Ejecución de pruebas de shell en pipelines de CI» es gratis?

Sí — el texto completo de «Ejecución de pruebas de shell en pipelines de CI» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de DevOps Bootcamp, actualiza a CoddyKit PRO. El curso de DevOps Bootcamp incluye 4 lecciones en total.

¿Qué aprenderé en «Ejecución de pruebas de shell en pipelines de CI»?

Integre ShellCheck y Bats en GitHub Actions para exigir comprobaciones correctas en cada cambio del shell. Practicas DevOps Bootcamp con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar DevOps Bootcamp?

No se requiere experiencia previa. DevOps Bootcamp en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «Ejecución de pruebas de shell en pipelines de CI»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de DevOps Bootcamp?

Sí. Cada lección de DevOps Bootcamp incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Pruebas unitarias de funciones con Bats-core
  2. Simulación de comandos y sustitución de herramientas externas
  3. Fixtures, entornos temporales y cobertura
  4. Ejecución de pruebas de shell en pipelines de CI
← Volver a DevOps Bootcamp